933b07a169a8286737bd5dae8b09e6f6dafc7641
[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, handSize;
265
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 int endPV = -1;
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
273 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
277 Boolean partnerUp;
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
289 int chattingPartner;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border;       /* [HGM] width of board rim, needed to size seek graph  */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
302
303 /* States for ics_getting_history */
304 #define H_FALSE 0
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
310
311 /* whosays values for GameEnds */
312 #define GE_ICS 0
313 #define GE_ENGINE 1
314 #define GE_PLAYER 2
315 #define GE_FILE 3
316 #define GE_XBOARD 4
317 #define GE_ENGINE1 5
318 #define GE_ENGINE2 6
319
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
322
323 /* Different types of move when calling RegisterMove */
324 #define CMAIL_MOVE   0
325 #define CMAIL_RESIGN 1
326 #define CMAIL_DRAW   2
327 #define CMAIL_ACCEPT 3
328
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
333
334 /* Telnet protocol constants */
335 #define TN_WILL 0373
336 #define TN_WONT 0374
337 #define TN_DO   0375
338 #define TN_DONT 0376
339 #define TN_IAC  0377
340 #define TN_ECHO 0001
341 #define TN_SGA  0003
342 #define TN_PORT 23
343
344 char*
345 safeStrCpy (char *dst, const char *src, size_t count)
346 { // [HGM] made safe
347   int i;
348   assert( dst != NULL );
349   assert( src != NULL );
350   assert( count > 0 );
351
352   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353   if(  i == count && dst[count-1] != NULLCHAR)
354     {
355       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356       if(appData.debugMode)
357         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
358     }
359
360   return dst;
361 }
362
363 /* Some compiler can't cast u64 to double
364  * This function do the job for us:
365
366  * We use the highest bit for cast, this only
367  * works if the highest bit is not
368  * in use (This should not happen)
369  *
370  * We used this for all compiler
371  */
372 double
373 u64ToDouble (u64 value)
374 {
375   double r;
376   u64 tmp = value & u64Const(0x7fffffffffffffff);
377   r = (double)(s64)tmp;
378   if (value & u64Const(0x8000000000000000))
379        r +=  9.2233720368547758080e18; /* 2^63 */
380  return r;
381 }
382
383 /* Fake up flags for now, as we aren't keeping track of castling
384    availability yet. [HGM] Change of logic: the flag now only
385    indicates the type of castlings allowed by the rule of the game.
386    The actual rights themselves are maintained in the array
387    castlingRights, as part of the game history, and are not probed
388    by this function.
389  */
390 int
391 PosFlags (int index)
392 {
393   int flags = F_ALL_CASTLE_OK;
394   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395   switch (gameInfo.variant) {
396   case VariantSuicide:
397     flags &= ~F_ALL_CASTLE_OK;
398   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399     flags |= F_IGNORE_CHECK;
400   case VariantLosers:
401     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
402     break;
403   case VariantAtomic:
404     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405     break;
406   case VariantKriegspiel:
407     flags |= F_KRIEGSPIEL_CAPTURE;
408     break;
409   case VariantCapaRandom:
410   case VariantFischeRandom:
411     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412   case VariantNoCastle:
413   case VariantShatranj:
414   case VariantCourier:
415   case VariantMakruk:
416   case VariantASEAN:
417   case VariantGrand:
418     flags &= ~F_ALL_CASTLE_OK;
419     break;
420   case VariantChu:
421   case VariantChuChess:
422   case VariantLion:
423     flags |= F_NULL_MOVE;
424     break;
425   default:
426     break;
427   }
428   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
429   return flags;
430 }
431
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
434
435 /*
436     [AS] Note: sometimes, the sscanf() function is used to parse the input
437     into a fixed-size buffer. Because of this, we must be prepared to
438     receive strings as long as the size of the input buffer, which is currently
439     set to 4K for Windows and 8K for the rest.
440     So, we must either allocate sufficiently large buffers here, or
441     reduce the size of the input buffer in the input reading part.
442 */
443
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
448
449 ChessProgramState first, second, pairing;
450
451 /* premove variables */
452 int premoveToX = 0;
453 int premoveToY = 0;
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
457 int gotPremove = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
460
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
463
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
491
492 int have_sent_ICS_logon = 0;
493 int movesPerSession;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
505
506 /* animateTraining preserves the state of appData.animate
507  * when Training mode is activated. This allows the
508  * response to be animated when appData.animate == TRUE and
509  * appData.animateDragging == TRUE.
510  */
511 Boolean animateTraining;
512
513 GameInfo gameInfo;
514
515 AppData appData;
516
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int   initialRulePlies, FENrulePlies;
523 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 int loadFlag = 0;
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
527
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int storedGames = 0;
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
537
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
543
544 ChessSquare  FIDEArray[2][BOARD_FILES] = {
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548         BlackKing, BlackBishop, BlackKnight, BlackRook }
549 };
550
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555         BlackKing, BlackKing, BlackKnight, BlackRook }
556 };
557
558 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561     { BlackRook, BlackMan, BlackBishop, BlackQueen,
562         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
563 };
564
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569         BlackTower, BlackKing, BlackAngel, BlackAlfil }
570 };
571
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
577 };
578
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 };
585
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackMan, BlackFerz,
590         BlackKing, BlackMan, BlackKnight, BlackRook }
591 };
592
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackMan, BlackFerz,
597         BlackKing, BlackMan, BlackKnight, BlackRook }
598 };
599
600 ChessSquare  lionArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackLion, BlackBishop, BlackQueen,
604         BlackKing, BlackBishop, BlackKnight, BlackRook }
605 };
606
607
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
614 };
615
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
621 };
622
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
628 };
629
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
635 };
636
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
642 };
643
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
649 };
650
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
656 };
657
658 #ifdef GOTHIC
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
664 };
665 #else // !GOTHIC
666 #define GothicArray CapablancaArray
667 #endif // !GOTHIC
668
669 #ifdef FALCON
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
675 };
676 #else // !FALCON
677 #define FalconArray CapablancaArray
678 #endif // !FALCON
679
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
686
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 };
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695     { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697     { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698       BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699     { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700       WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701     { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702       BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703     { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705     { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
707 };
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
712
713
714 Board initialPosition;
715
716
717 /* Convert str to a rating. Checks for special cases of "----",
718
719    "++++", etc. Also strips ()'s */
720 int
721 string_to_rating (char *str)
722 {
723   while(*str && !isdigit(*str)) ++str;
724   if (!*str)
725     return 0;   /* One of the special "no rating" cases */
726   else
727     return atoi(str);
728 }
729
730 void
731 ClearProgramStats ()
732 {
733     /* Init programStats */
734     programStats.movelist[0] = 0;
735     programStats.depth = 0;
736     programStats.nr_moves = 0;
737     programStats.moves_left = 0;
738     programStats.nodes = 0;
739     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
740     programStats.score = 0;
741     programStats.got_only_move = 0;
742     programStats.got_fail = 0;
743     programStats.line_is_book = 0;
744 }
745
746 void
747 CommonEngineInit ()
748 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749     if (appData.firstPlaysBlack) {
750         first.twoMachinesColor = "black\n";
751         second.twoMachinesColor = "white\n";
752     } else {
753         first.twoMachinesColor = "white\n";
754         second.twoMachinesColor = "black\n";
755     }
756
757     first.other = &second;
758     second.other = &first;
759
760     { float norm = 1;
761         if(appData.timeOddsMode) {
762             norm = appData.timeOdds[0];
763             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764         }
765         first.timeOdds  = appData.timeOdds[0]/norm;
766         second.timeOdds = appData.timeOdds[1]/norm;
767     }
768
769     if(programVersion) free(programVersion);
770     if (appData.noChessProgram) {
771         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772         sprintf(programVersion, "%s", PACKAGE_STRING);
773     } else {
774       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
777     }
778 }
779
780 void
781 UnloadEngine (ChessProgramState *cps)
782 {
783         /* Kill off first chess program */
784         if (cps->isr != NULL)
785           RemoveInputSource(cps->isr);
786         cps->isr = NULL;
787
788         if (cps->pr != NoProc) {
789             ExitAnalyzeMode();
790             DoSleep( appData.delayBeforeQuit );
791             SendToProgram("quit\n", cps);
792             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
793         }
794         cps->pr = NoProc;
795         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
796 }
797
798 void
799 ClearOptions (ChessProgramState *cps)
800 {
801     int i;
802     cps->nrOptions = cps->comboCnt = 0;
803     for(i=0; i<MAX_OPTIONS; i++) {
804         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805         cps->option[i].textValue = 0;
806     }
807 }
808
809 char *engineNames[] = {
810   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 N_("first"),
813   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
815 N_("second")
816 };
817
818 void
819 InitEngine (ChessProgramState *cps, int n)
820 {   // [HGM] all engine initialiation put in a function that does one engine
821
822     ClearOptions(cps);
823
824     cps->which = engineNames[n];
825     cps->maybeThinking = FALSE;
826     cps->pr = NoProc;
827     cps->isr = NULL;
828     cps->sendTime = 2;
829     cps->sendDrawOffers = 1;
830
831     cps->program = appData.chessProgram[n];
832     cps->host = appData.host[n];
833     cps->dir = appData.directory[n];
834     cps->initString = appData.engInitString[n];
835     cps->computerString = appData.computerString[n];
836     cps->useSigint  = TRUE;
837     cps->useSigterm = TRUE;
838     cps->reuse = appData.reuse[n];
839     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
840     cps->useSetboard = FALSE;
841     cps->useSAN = FALSE;
842     cps->usePing = FALSE;
843     cps->lastPing = 0;
844     cps->lastPong = 0;
845     cps->usePlayother = FALSE;
846     cps->useColors = TRUE;
847     cps->useUsermove = FALSE;
848     cps->sendICS = FALSE;
849     cps->sendName = appData.icsActive;
850     cps->sdKludge = FALSE;
851     cps->stKludge = FALSE;
852     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853     TidyProgramName(cps->program, cps->host, cps->tidy);
854     cps->matchWins = 0;
855     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856     cps->analysisSupport = 2; /* detect */
857     cps->analyzing = FALSE;
858     cps->initDone = FALSE;
859     cps->reload = FALSE;
860     cps->pseudo = appData.pseudo[n];
861
862     /* New features added by Tord: */
863     cps->useFEN960 = FALSE;
864     cps->useOOCastle = TRUE;
865     /* End of new features added by Tord. */
866     cps->fenOverride  = appData.fenOverride[n];
867
868     /* [HGM] time odds: set factor for each machine */
869     cps->timeOdds  = appData.timeOdds[n];
870
871     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872     cps->accumulateTC = appData.accumulateTC[n];
873     cps->maxNrOfSessions = 1;
874
875     /* [HGM] debug */
876     cps->debug = FALSE;
877
878     cps->drawDepth = appData.drawDepth[n];
879     cps->supportsNPS = UNKNOWN;
880     cps->memSize = FALSE;
881     cps->maxCores = FALSE;
882     ASSIGN(cps->egtFormats, "");
883
884     /* [HGM] options */
885     cps->optionSettings  = appData.engOptions[n];
886
887     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888     cps->isUCI = appData.isUCI[n]; /* [AS] */
889     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
890     cps->highlight = 0;
891
892     if (appData.protocolVersion[n] > PROTOVER
893         || appData.protocolVersion[n] < 1)
894       {
895         char buf[MSG_SIZ];
896         int len;
897
898         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899                        appData.protocolVersion[n]);
900         if( (len >= MSG_SIZ) && appData.debugMode )
901           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902
903         DisplayFatalError(buf, 0, 2);
904       }
905     else
906       {
907         cps->protocolVersion = appData.protocolVersion[n];
908       }
909
910     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
911     ParseFeatures(appData.featureDefaults, cps);
912 }
913
914 ChessProgramState *savCps;
915
916 GameMode oldMode;
917
918 void
919 LoadEngine ()
920 {
921     int i;
922     if(WaitForEngine(savCps, LoadEngine)) return;
923     CommonEngineInit(); // recalculate time odds
924     if(gameInfo.variant != StringToVariant(appData.variant)) {
925         // we changed variant when loading the engine; this forces us to reset
926         Reset(TRUE, savCps != &first);
927         oldMode = BeginningOfGame; // to prevent restoring old mode
928     }
929     InitChessProgram(savCps, FALSE);
930     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
931     DisplayMessage("", "");
932     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
933     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
934     ThawUI();
935     SetGNUMode();
936     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
937 }
938
939 void
940 ReplaceEngine (ChessProgramState *cps, int n)
941 {
942     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
943     keepInfo = 1;
944     if(oldMode != BeginningOfGame) EditGameEvent();
945     keepInfo = 0;
946     UnloadEngine(cps);
947     appData.noChessProgram = FALSE;
948     appData.clockMode = TRUE;
949     InitEngine(cps, n);
950     UpdateLogos(TRUE);
951     if(n) return; // only startup first engine immediately; second can wait
952     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
953     LoadEngine();
954 }
955
956 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
957 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
958
959 static char resetOptions[] =
960         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
961         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
962         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
963         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
964
965 void
966 FloatToFront(char **list, char *engineLine)
967 {
968     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
969     int i=0;
970     if(appData.recentEngines <= 0) return;
971     TidyProgramName(engineLine, "localhost", tidy+1);
972     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
973     strncpy(buf+1, *list, MSG_SIZ-50);
974     if(p = strstr(buf, tidy)) { // tidy name appears in list
975         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
976         while(*p++ = *++q); // squeeze out
977     }
978     strcat(tidy, buf+1); // put list behind tidy name
979     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
980     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
981     ASSIGN(*list, tidy+1);
982 }
983
984 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
985
986 void
987 Load (ChessProgramState *cps, int i)
988 {
989     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
990     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
991         ASSIGN(currentEngine[i], engineLine);
992         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
993         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
994         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
995         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
996         appData.firstProtocolVersion = PROTOVER;
997         ParseArgsFromString(buf);
998         SwapEngines(i);
999         ReplaceEngine(cps, i);
1000         FloatToFront(&appData.recentEngineList, engineLine);
1001         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1002         return;
1003     }
1004     p = engineName;
1005     while(q = strchr(p, SLASH)) p = q+1;
1006     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1007     if(engineDir[0] != NULLCHAR) {
1008         ASSIGN(appData.directory[i], engineDir); p = engineName;
1009     } else if(p != engineName) { // derive directory from engine path, when not given
1010         p[-1] = 0;
1011         ASSIGN(appData.directory[i], engineName);
1012         p[-1] = SLASH;
1013         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1014     } else { ASSIGN(appData.directory[i], "."); }
1015     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1016     if(params[0]) {
1017         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1018         snprintf(command, MSG_SIZ, "%s %s", p, params);
1019         p = command;
1020     }
1021     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1022     ASSIGN(appData.chessProgram[i], p);
1023     appData.isUCI[i] = isUCI;
1024     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1025     appData.hasOwnBookUCI[i] = hasBook;
1026     if(!nickName[0]) useNick = FALSE;
1027     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1028     if(addToList) {
1029         int len;
1030         char quote;
1031         q = firstChessProgramNames;
1032         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1033         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1034         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
1035                         quote, p, quote, appData.directory[i],
1036                         useNick ? " -fn \"" : "",
1037                         useNick ? nickName : "",
1038                         useNick ? "\"" : "",
1039                         v1 ? " -firstProtocolVersion 1" : "",
1040                         hasBook ? "" : " -fNoOwnBookUCI",
1041                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1042                         storeVariant ? " -variant " : "",
1043                         storeVariant ? VariantName(gameInfo.variant) : "");
1044         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
1045         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
1046         if(insert != q) insert[-1] = NULLCHAR;
1047         snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
1048         if(q)   free(q);
1049         FloatToFront(&appData.recentEngineList, buf);
1050         ASSIGN(currentEngine[i], buf);
1051     }
1052     ReplaceEngine(cps, i);
1053 }
1054
1055 void
1056 InitTimeControls ()
1057 {
1058     int matched, min, sec;
1059     /*
1060      * Parse timeControl resource
1061      */
1062     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1063                           appData.movesPerSession)) {
1064         char buf[MSG_SIZ];
1065         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1066         DisplayFatalError(buf, 0, 2);
1067     }
1068
1069     /*
1070      * Parse searchTime resource
1071      */
1072     if (*appData.searchTime != NULLCHAR) {
1073         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1074         if (matched == 1) {
1075             searchTime = min * 60;
1076         } else if (matched == 2) {
1077             searchTime = min * 60 + sec;
1078         } else {
1079             char buf[MSG_SIZ];
1080             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1081             DisplayFatalError(buf, 0, 2);
1082         }
1083     }
1084 }
1085
1086 void
1087 InitBackEnd1 ()
1088 {
1089
1090     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1091     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1092
1093     GetTimeMark(&programStartTime);
1094     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1095     appData.seedBase = random() + (random()<<15);
1096     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1097
1098     ClearProgramStats();
1099     programStats.ok_to_send = 1;
1100     programStats.seen_stat = 0;
1101
1102     /*
1103      * Initialize game list
1104      */
1105     ListNew(&gameList);
1106
1107
1108     /*
1109      * Internet chess server status
1110      */
1111     if (appData.icsActive) {
1112         appData.matchMode = FALSE;
1113         appData.matchGames = 0;
1114 #if ZIPPY
1115         appData.noChessProgram = !appData.zippyPlay;
1116 #else
1117         appData.zippyPlay = FALSE;
1118         appData.zippyTalk = FALSE;
1119         appData.noChessProgram = TRUE;
1120 #endif
1121         if (*appData.icsHelper != NULLCHAR) {
1122             appData.useTelnet = TRUE;
1123             appData.telnetProgram = appData.icsHelper;
1124         }
1125     } else {
1126         appData.zippyTalk = appData.zippyPlay = FALSE;
1127     }
1128
1129     /* [AS] Initialize pv info list [HGM] and game state */
1130     {
1131         int i, j;
1132
1133         for( i=0; i<=framePtr; i++ ) {
1134             pvInfoList[i].depth = -1;
1135             boards[i][EP_STATUS] = EP_NONE;
1136             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1137         }
1138     }
1139
1140     InitTimeControls();
1141
1142     /* [AS] Adjudication threshold */
1143     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1144
1145     InitEngine(&first, 0);
1146     InitEngine(&second, 1);
1147     CommonEngineInit();
1148
1149     pairing.which = "pairing"; // pairing engine
1150     pairing.pr = NoProc;
1151     pairing.isr = NULL;
1152     pairing.program = appData.pairingEngine;
1153     pairing.host = "localhost";
1154     pairing.dir = ".";
1155
1156     if (appData.icsActive) {
1157         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1158     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1159         appData.clockMode = FALSE;
1160         first.sendTime = second.sendTime = 0;
1161     }
1162
1163 #if ZIPPY
1164     /* Override some settings from environment variables, for backward
1165        compatibility.  Unfortunately it's not feasible to have the env
1166        vars just set defaults, at least in xboard.  Ugh.
1167     */
1168     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1169       ZippyInit();
1170     }
1171 #endif
1172
1173     if (!appData.icsActive) {
1174       char buf[MSG_SIZ];
1175       int len;
1176
1177       /* Check for variants that are supported only in ICS mode,
1178          or not at all.  Some that are accepted here nevertheless
1179          have bugs; see comments below.
1180       */
1181       VariantClass variant = StringToVariant(appData.variant);
1182       switch (variant) {
1183       case VariantBughouse:     /* need four players and two boards */
1184       case VariantKriegspiel:   /* need to hide pieces and move details */
1185         /* case VariantFischeRandom: (Fabien: moved below) */
1186         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1187         if( (len >= MSG_SIZ) && appData.debugMode )
1188           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1189
1190         DisplayFatalError(buf, 0, 2);
1191         return;
1192
1193       case VariantUnknown:
1194       case VariantLoadable:
1195       case Variant29:
1196       case Variant30:
1197       case Variant31:
1198       case Variant32:
1199       case Variant33:
1200       case Variant34:
1201       case Variant35:
1202       case Variant36:
1203       default:
1204         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1205         if( (len >= MSG_SIZ) && appData.debugMode )
1206           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1207
1208         DisplayFatalError(buf, 0, 2);
1209         return;
1210
1211       case VariantNormal:     /* definitely works! */
1212         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1213           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1214           return;
1215         }
1216       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1217       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1218       case VariantGothic:     /* [HGM] should work */
1219       case VariantCapablanca: /* [HGM] should work */
1220       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1221       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1222       case VariantChu:        /* [HGM] experimental */
1223       case VariantKnightmate: /* [HGM] should work */
1224       case VariantCylinder:   /* [HGM] untested */
1225       case VariantFalcon:     /* [HGM] untested */
1226       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1227                                  offboard interposition not understood */
1228       case VariantWildCastle: /* pieces not automatically shuffled */
1229       case VariantNoCastle:   /* pieces not automatically shuffled */
1230       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1231       case VariantLosers:     /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantSuicide:    /* should work except for win condition,
1234                                  and doesn't know captures are mandatory */
1235       case VariantGiveaway:   /* should work except for win condition,
1236                                  and doesn't know captures are mandatory */
1237       case VariantTwoKings:   /* should work */
1238       case VariantAtomic:     /* should work except for win condition */
1239       case Variant3Check:     /* should work except for win condition */
1240       case VariantShatranj:   /* should work except for all win conditions */
1241       case VariantMakruk:     /* should work except for draw countdown */
1242       case VariantASEAN :     /* should work except for draw countdown */
1243       case VariantBerolina:   /* might work if TestLegality is off */
1244       case VariantCapaRandom: /* should work */
1245       case VariantJanus:      /* should work */
1246       case VariantSuper:      /* experimental */
1247       case VariantGreat:      /* experimental, requires legality testing to be off */
1248       case VariantSChess:     /* S-Chess, should work */
1249       case VariantGrand:      /* should work */
1250       case VariantSpartan:    /* should work */
1251       case VariantLion:       /* should work */
1252       case VariantChuChess:   /* should work */
1253         break;
1254       }
1255     }
1256
1257 }
1258
1259 int
1260 NextIntegerFromString (char ** str, long * value)
1261 {
1262     int result = -1;
1263     char * s = *str;
1264
1265     while( *s == ' ' || *s == '\t' ) {
1266         s++;
1267     }
1268
1269     *value = 0;
1270
1271     if( *s >= '0' && *s <= '9' ) {
1272         while( *s >= '0' && *s <= '9' ) {
1273             *value = *value * 10 + (*s - '0');
1274             s++;
1275         }
1276
1277         result = 0;
1278     }
1279
1280     *str = s;
1281
1282     return result;
1283 }
1284
1285 int
1286 NextTimeControlFromString (char ** str, long * value)
1287 {
1288     long temp;
1289     int result = NextIntegerFromString( str, &temp );
1290
1291     if( result == 0 ) {
1292         *value = temp * 60; /* Minutes */
1293         if( **str == ':' ) {
1294             (*str)++;
1295             result = NextIntegerFromString( str, &temp );
1296             *value += temp; /* Seconds */
1297         }
1298     }
1299
1300     return result;
1301 }
1302
1303 int
1304 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1305 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1306     int result = -1, type = 0; long temp, temp2;
1307
1308     if(**str != ':') return -1; // old params remain in force!
1309     (*str)++;
1310     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1311     if( NextIntegerFromString( str, &temp ) ) return -1;
1312     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1313
1314     if(**str != '/') {
1315         /* time only: incremental or sudden-death time control */
1316         if(**str == '+') { /* increment follows; read it */
1317             (*str)++;
1318             if(**str == '!') type = *(*str)++; // Bronstein TC
1319             if(result = NextIntegerFromString( str, &temp2)) return -1;
1320             *inc = temp2 * 1000;
1321             if(**str == '.') { // read fraction of increment
1322                 char *start = ++(*str);
1323                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1324                 temp2 *= 1000;
1325                 while(start++ < *str) temp2 /= 10;
1326                 *inc += temp2;
1327             }
1328         } else *inc = 0;
1329         *moves = 0; *tc = temp * 1000; *incType = type;
1330         return 0;
1331     }
1332
1333     (*str)++; /* classical time control */
1334     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1335
1336     if(result == 0) {
1337         *moves = temp;
1338         *tc    = temp2 * 1000;
1339         *inc   = 0;
1340         *incType = type;
1341     }
1342     return result;
1343 }
1344
1345 int
1346 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1347 {   /* [HGM] get time to add from the multi-session time-control string */
1348     int incType, moves=1; /* kludge to force reading of first session */
1349     long time, increment;
1350     char *s = tcString;
1351
1352     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1353     do {
1354         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1355         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1356         if(movenr == -1) return time;    /* last move before new session     */
1357         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1358         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1359         if(!moves) return increment;     /* current session is incremental   */
1360         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1361     } while(movenr >= -1);               /* try again for next session       */
1362
1363     return 0; // no new time quota on this move
1364 }
1365
1366 int
1367 ParseTimeControl (char *tc, float ti, int mps)
1368 {
1369   long tc1;
1370   long tc2;
1371   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1372   int min, sec=0;
1373
1374   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1375   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1376       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1377   if(ti > 0) {
1378
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1383   } else {
1384     if(mps)
1385       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1386     else
1387       snprintf(buf, MSG_SIZ, ":%s", mytc);
1388   }
1389   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1390
1391   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1392     return FALSE;
1393   }
1394
1395   if( *tc == '/' ) {
1396     /* Parse second time control */
1397     tc++;
1398
1399     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1400       return FALSE;
1401     }
1402
1403     if( tc2 == 0 ) {
1404       return FALSE;
1405     }
1406
1407     timeControl_2 = tc2 * 1000;
1408   }
1409   else {
1410     timeControl_2 = 0;
1411   }
1412
1413   if( tc1 == 0 ) {
1414     return FALSE;
1415   }
1416
1417   timeControl = tc1 * 1000;
1418
1419   if (ti >= 0) {
1420     timeIncrement = ti * 1000;  /* convert to ms */
1421     movesPerSession = 0;
1422   } else {
1423     timeIncrement = 0;
1424     movesPerSession = mps;
1425   }
1426   return TRUE;
1427 }
1428
1429 void
1430 InitBackEnd2 ()
1431 {
1432     if (appData.debugMode) {
1433 #    ifdef __GIT_VERSION
1434       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1435 #    else
1436       fprintf(debugFP, "Version: %s\n", programVersion);
1437 #    endif
1438     }
1439     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1440
1441     set_cont_sequence(appData.wrapContSeq);
1442     if (appData.matchGames > 0) {
1443         appData.matchMode = TRUE;
1444     } else if (appData.matchMode) {
1445         appData.matchGames = 1;
1446     }
1447     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1448         appData.matchGames = appData.sameColorGames;
1449     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1450         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1451         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1452     }
1453     Reset(TRUE, FALSE);
1454     if (appData.noChessProgram || first.protocolVersion == 1) {
1455       InitBackEnd3();
1456     } else {
1457       /* kludge: allow timeout for initial "feature" commands */
1458       FreezeUI();
1459       DisplayMessage("", _("Starting chess program"));
1460       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1461     }
1462 }
1463
1464 int
1465 CalculateIndex (int index, int gameNr)
1466 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1467     int res;
1468     if(index > 0) return index; // fixed nmber
1469     if(index == 0) return 1;
1470     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1471     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1472     return res;
1473 }
1474
1475 int
1476 LoadGameOrPosition (int gameNr)
1477 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1478     if (*appData.loadGameFile != NULLCHAR) {
1479         if (!LoadGameFromFile(appData.loadGameFile,
1480                 CalculateIndex(appData.loadGameIndex, gameNr),
1481                               appData.loadGameFile, FALSE)) {
1482             DisplayFatalError(_("Bad game file"), 0, 1);
1483             return 0;
1484         }
1485     } else if (*appData.loadPositionFile != NULLCHAR) {
1486         if (!LoadPositionFromFile(appData.loadPositionFile,
1487                 CalculateIndex(appData.loadPositionIndex, gameNr),
1488                                   appData.loadPositionFile)) {
1489             DisplayFatalError(_("Bad position file"), 0, 1);
1490             return 0;
1491         }
1492     }
1493     return 1;
1494 }
1495
1496 void
1497 ReserveGame (int gameNr, char resChar)
1498 {
1499     FILE *tf = fopen(appData.tourneyFile, "r+");
1500     char *p, *q, c, buf[MSG_SIZ];
1501     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1502     safeStrCpy(buf, lastMsg, MSG_SIZ);
1503     DisplayMessage(_("Pick new game"), "");
1504     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1505     ParseArgsFromFile(tf);
1506     p = q = appData.results;
1507     if(appData.debugMode) {
1508       char *r = appData.participants;
1509       fprintf(debugFP, "results = '%s'\n", p);
1510       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1511       fprintf(debugFP, "\n");
1512     }
1513     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1514     nextGame = q - p;
1515     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1516     safeStrCpy(q, p, strlen(p) + 2);
1517     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1518     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1519     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1520         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1521         q[nextGame] = '*';
1522     }
1523     fseek(tf, -(strlen(p)+4), SEEK_END);
1524     c = fgetc(tf);
1525     if(c != '"') // depending on DOS or Unix line endings we can be one off
1526          fseek(tf, -(strlen(p)+2), SEEK_END);
1527     else fseek(tf, -(strlen(p)+3), SEEK_END);
1528     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1529     DisplayMessage(buf, "");
1530     free(p); appData.results = q;
1531     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1532        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1533       int round = appData.defaultMatchGames * appData.tourneyType;
1534       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1535          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1536         UnloadEngine(&first);  // next game belongs to other pairing;
1537         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1538     }
1539     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1540 }
1541
1542 void
1543 MatchEvent (int mode)
1544 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1545         int dummy;
1546         if(matchMode) { // already in match mode: switch it off
1547             abortMatch = TRUE;
1548             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1549             return;
1550         }
1551 //      if(gameMode != BeginningOfGame) {
1552 //          DisplayError(_("You can only start a match from the initial position."), 0);
1553 //          return;
1554 //      }
1555         abortMatch = FALSE;
1556         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1557         /* Set up machine vs. machine match */
1558         nextGame = 0;
1559         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1560         if(appData.tourneyFile[0]) {
1561             ReserveGame(-1, 0);
1562             if(nextGame > appData.matchGames) {
1563                 char buf[MSG_SIZ];
1564                 if(strchr(appData.results, '*') == NULL) {
1565                     FILE *f;
1566                     appData.tourneyCycles++;
1567                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1568                         fclose(f);
1569                         NextTourneyGame(-1, &dummy);
1570                         ReserveGame(-1, 0);
1571                         if(nextGame <= appData.matchGames) {
1572                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1573                             matchMode = mode;
1574                             ScheduleDelayedEvent(NextMatchGame, 10000);
1575                             return;
1576                         }
1577                     }
1578                 }
1579                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1580                 DisplayError(buf, 0);
1581                 appData.tourneyFile[0] = 0;
1582                 return;
1583             }
1584         } else
1585         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1586             DisplayFatalError(_("Can't have a match with no chess programs"),
1587                               0, 2);
1588             return;
1589         }
1590         matchMode = mode;
1591         matchGame = roundNr = 1;
1592         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1593         NextMatchGame();
1594 }
1595
1596 void
1597 InitBackEnd3 P((void))
1598 {
1599     GameMode initialMode;
1600     char buf[MSG_SIZ];
1601     int err, len;
1602
1603     ParseFeatures(appData.features[0], &first);
1604     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1605        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1606         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1607        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1608        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1609         char c, *q = first.variants, *p = strchr(q, ',');
1610         if(p) *p = NULLCHAR;
1611         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1612             int w, h, s;
1613             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1614                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1615             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1616             Reset(TRUE, FALSE);         // and re-initialize
1617         }
1618         if(p) *p = ',';
1619     }
1620
1621     InitChessProgram(&first, startedFromSetupPosition);
1622
1623     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1624         free(programVersion);
1625         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1626         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1627         FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1628     }
1629
1630     if (appData.icsActive) {
1631 #ifdef WIN32
1632         /* [DM] Make a console window if needed [HGM] merged ifs */
1633         ConsoleCreate();
1634 #endif
1635         err = establish();
1636         if (err != 0)
1637           {
1638             if (*appData.icsCommPort != NULLCHAR)
1639               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1640                              appData.icsCommPort);
1641             else
1642               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1643                         appData.icsHost, appData.icsPort);
1644
1645             if( (len >= MSG_SIZ) && appData.debugMode )
1646               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1647
1648             DisplayFatalError(buf, err, 1);
1649             return;
1650         }
1651         SetICSMode();
1652         telnetISR =
1653           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1654         fromUserISR =
1655           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1656         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1657             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1658     } else if (appData.noChessProgram) {
1659         SetNCPMode();
1660     } else {
1661         SetGNUMode();
1662     }
1663
1664     if (*appData.cmailGameName != NULLCHAR) {
1665         SetCmailMode();
1666         OpenLoopback(&cmailPR);
1667         cmailISR =
1668           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1669     }
1670
1671     ThawUI();
1672     DisplayMessage("", "");
1673     if (StrCaseCmp(appData.initialMode, "") == 0) {
1674       initialMode = BeginningOfGame;
1675       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1676         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1677         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1678         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1679         ModeHighlight();
1680       }
1681     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1682       initialMode = TwoMachinesPlay;
1683     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1684       initialMode = AnalyzeFile;
1685     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1686       initialMode = AnalyzeMode;
1687     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1688       initialMode = MachinePlaysWhite;
1689     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1690       initialMode = MachinePlaysBlack;
1691     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1692       initialMode = EditGame;
1693     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1694       initialMode = EditPosition;
1695     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1696       initialMode = Training;
1697     } else {
1698       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1699       if( (len >= MSG_SIZ) && appData.debugMode )
1700         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1701
1702       DisplayFatalError(buf, 0, 2);
1703       return;
1704     }
1705
1706     if (appData.matchMode) {
1707         if(appData.tourneyFile[0]) { // start tourney from command line
1708             FILE *f;
1709             if(f = fopen(appData.tourneyFile, "r")) {
1710                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1711                 fclose(f);
1712                 appData.clockMode = TRUE;
1713                 SetGNUMode();
1714             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1715         }
1716         MatchEvent(TRUE);
1717     } else if (*appData.cmailGameName != NULLCHAR) {
1718         /* Set up cmail mode */
1719         ReloadCmailMsgEvent(TRUE);
1720     } else {
1721         /* Set up other modes */
1722         if (initialMode == AnalyzeFile) {
1723           if (*appData.loadGameFile == NULLCHAR) {
1724             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1725             return;
1726           }
1727         }
1728         if (*appData.loadGameFile != NULLCHAR) {
1729             (void) LoadGameFromFile(appData.loadGameFile,
1730                                     appData.loadGameIndex,
1731                                     appData.loadGameFile, TRUE);
1732         } else if (*appData.loadPositionFile != NULLCHAR) {
1733             (void) LoadPositionFromFile(appData.loadPositionFile,
1734                                         appData.loadPositionIndex,
1735                                         appData.loadPositionFile);
1736             /* [HGM] try to make self-starting even after FEN load */
1737             /* to allow automatic setup of fairy variants with wtm */
1738             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1739                 gameMode = BeginningOfGame;
1740                 setboardSpoiledMachineBlack = 1;
1741             }
1742             /* [HGM] loadPos: make that every new game uses the setup */
1743             /* from file as long as we do not switch variant          */
1744             if(!blackPlaysFirst) {
1745                 startedFromPositionFile = TRUE;
1746                 CopyBoard(filePosition, boards[0]);
1747                 CopyBoard(initialPosition, boards[0]);
1748             }
1749         } else if(*appData.fen != NULLCHAR) {
1750             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1751                 startedFromPositionFile = TRUE;
1752                 Reset(TRUE, TRUE);
1753             }
1754         }
1755         if (initialMode == AnalyzeMode) {
1756           if (appData.noChessProgram) {
1757             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1758             return;
1759           }
1760           if (appData.icsActive) {
1761             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1762             return;
1763           }
1764           AnalyzeModeEvent();
1765         } else if (initialMode == AnalyzeFile) {
1766           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1767           ShowThinkingEvent();
1768           AnalyzeFileEvent();
1769           AnalysisPeriodicEvent(1);
1770         } else if (initialMode == MachinePlaysWhite) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           MachineWhiteEvent();
1782         } else if (initialMode == MachinePlaysBlack) {
1783           if (appData.noChessProgram) {
1784             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1785                               0, 2);
1786             return;
1787           }
1788           if (appData.icsActive) {
1789             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1790                               0, 2);
1791             return;
1792           }
1793           MachineBlackEvent();
1794         } else if (initialMode == TwoMachinesPlay) {
1795           if (appData.noChessProgram) {
1796             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1797                               0, 2);
1798             return;
1799           }
1800           if (appData.icsActive) {
1801             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1802                               0, 2);
1803             return;
1804           }
1805           TwoMachinesEvent();
1806         } else if (initialMode == EditGame) {
1807           EditGameEvent();
1808         } else if (initialMode == EditPosition) {
1809           EditPositionEvent();
1810         } else if (initialMode == Training) {
1811           if (*appData.loadGameFile == NULLCHAR) {
1812             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1813             return;
1814           }
1815           TrainingEvent();
1816         }
1817     }
1818 }
1819
1820 void
1821 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1822 {
1823     DisplayBook(current+1);
1824
1825     MoveHistorySet( movelist, first, last, current, pvInfoList );
1826
1827     EvalGraphSet( first, last, current, pvInfoList );
1828
1829     MakeEngineOutputTitle();
1830 }
1831
1832 /*
1833  * Establish will establish a contact to a remote host.port.
1834  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1835  *  used to talk to the host.
1836  * Returns 0 if okay, error code if not.
1837  */
1838 int
1839 establish ()
1840 {
1841     char buf[MSG_SIZ];
1842
1843     if (*appData.icsCommPort != NULLCHAR) {
1844         /* Talk to the host through a serial comm port */
1845         return OpenCommPort(appData.icsCommPort, &icsPR);
1846
1847     } else if (*appData.gateway != NULLCHAR) {
1848         if (*appData.remoteShell == NULLCHAR) {
1849             /* Use the rcmd protocol to run telnet program on a gateway host */
1850             snprintf(buf, sizeof(buf), "%s %s %s",
1851                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1852             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1853
1854         } else {
1855             /* Use the rsh program to run telnet program on a gateway host */
1856             if (*appData.remoteUser == NULLCHAR) {
1857                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1858                         appData.gateway, appData.telnetProgram,
1859                         appData.icsHost, appData.icsPort);
1860             } else {
1861                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1862                         appData.remoteShell, appData.gateway,
1863                         appData.remoteUser, appData.telnetProgram,
1864                         appData.icsHost, appData.icsPort);
1865             }
1866             return StartChildProcess(buf, "", &icsPR);
1867
1868         }
1869     } else if (appData.useTelnet) {
1870         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1871
1872     } else {
1873         /* TCP socket interface differs somewhat between
1874            Unix and NT; handle details in the front end.
1875            */
1876         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1877     }
1878 }
1879
1880 void
1881 EscapeExpand (char *p, char *q)
1882 {       // [HGM] initstring: routine to shape up string arguments
1883         while(*p++ = *q++) if(p[-1] == '\\')
1884             switch(*q++) {
1885                 case 'n': p[-1] = '\n'; break;
1886                 case 'r': p[-1] = '\r'; break;
1887                 case 't': p[-1] = '\t'; break;
1888                 case '\\': p[-1] = '\\'; break;
1889                 case 0: *p = 0; return;
1890                 default: p[-1] = q[-1]; break;
1891             }
1892 }
1893
1894 void
1895 show_bytes (FILE *fp, char *buf, int count)
1896 {
1897     while (count--) {
1898         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1899             fprintf(fp, "\\%03o", *buf & 0xff);
1900         } else {
1901             putc(*buf, fp);
1902         }
1903         buf++;
1904     }
1905     fflush(fp);
1906 }
1907
1908 /* Returns an errno value */
1909 int
1910 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1911 {
1912     char buf[8192], *p, *q, *buflim;
1913     int left, newcount, outcount;
1914
1915     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1916         *appData.gateway != NULLCHAR) {
1917         if (appData.debugMode) {
1918             fprintf(debugFP, ">ICS: ");
1919             show_bytes(debugFP, message, count);
1920             fprintf(debugFP, "\n");
1921         }
1922         return OutputToProcess(pr, message, count, outError);
1923     }
1924
1925     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1926     p = message;
1927     q = buf;
1928     left = count;
1929     newcount = 0;
1930     while (left) {
1931         if (q >= buflim) {
1932             if (appData.debugMode) {
1933                 fprintf(debugFP, ">ICS: ");
1934                 show_bytes(debugFP, buf, newcount);
1935                 fprintf(debugFP, "\n");
1936             }
1937             outcount = OutputToProcess(pr, buf, newcount, outError);
1938             if (outcount < newcount) return -1; /* to be sure */
1939             q = buf;
1940             newcount = 0;
1941         }
1942         if (*p == '\n') {
1943             *q++ = '\r';
1944             newcount++;
1945         } else if (((unsigned char) *p) == TN_IAC) {
1946             *q++ = (char) TN_IAC;
1947             newcount ++;
1948         }
1949         *q++ = *p++;
1950         newcount++;
1951         left--;
1952     }
1953     if (appData.debugMode) {
1954         fprintf(debugFP, ">ICS: ");
1955         show_bytes(debugFP, buf, newcount);
1956         fprintf(debugFP, "\n");
1957     }
1958     outcount = OutputToProcess(pr, buf, newcount, outError);
1959     if (outcount < newcount) return -1; /* to be sure */
1960     return count;
1961 }
1962
1963 void
1964 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1965 {
1966     int outError, outCount;
1967     static int gotEof = 0;
1968     static FILE *ini;
1969
1970     /* Pass data read from player on to ICS */
1971     if (count > 0) {
1972         gotEof = 0;
1973         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1974         if (outCount < count) {
1975             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1976         }
1977         if(have_sent_ICS_logon == 2) {
1978           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1979             fprintf(ini, "%s", message);
1980             have_sent_ICS_logon = 3;
1981           } else
1982             have_sent_ICS_logon = 1;
1983         } else if(have_sent_ICS_logon == 3) {
1984             fprintf(ini, "%s", message);
1985             fclose(ini);
1986           have_sent_ICS_logon = 1;
1987         }
1988     } else if (count < 0) {
1989         RemoveInputSource(isr);
1990         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1991     } else if (gotEof++ > 0) {
1992         RemoveInputSource(isr);
1993         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1994     }
1995 }
1996
1997 void
1998 KeepAlive ()
1999 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2000     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2001     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2002     SendToICS("date\n");
2003     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2004 }
2005
2006 /* added routine for printf style output to ics */
2007 void
2008 ics_printf (char *format, ...)
2009 {
2010     char buffer[MSG_SIZ];
2011     va_list args;
2012
2013     va_start(args, format);
2014     vsnprintf(buffer, sizeof(buffer), format, args);
2015     buffer[sizeof(buffer)-1] = '\0';
2016     SendToICS(buffer);
2017     va_end(args);
2018 }
2019
2020 void
2021 SendToICS (char *s)
2022 {
2023     int count, outCount, outError;
2024
2025     if (icsPR == NoProc) return;
2026
2027     count = strlen(s);
2028     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2029     if (outCount < count) {
2030         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2031     }
2032 }
2033
2034 /* This is used for sending logon scripts to the ICS. Sending
2035    without a delay causes problems when using timestamp on ICC
2036    (at least on my machine). */
2037 void
2038 SendToICSDelayed (char *s, long msdelay)
2039 {
2040     int count, outCount, outError;
2041
2042     if (icsPR == NoProc) return;
2043
2044     count = strlen(s);
2045     if (appData.debugMode) {
2046         fprintf(debugFP, ">ICS: ");
2047         show_bytes(debugFP, s, count);
2048         fprintf(debugFP, "\n");
2049     }
2050     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2051                                       msdelay);
2052     if (outCount < count) {
2053         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2054     }
2055 }
2056
2057
2058 /* Remove all highlighting escape sequences in s
2059    Also deletes any suffix starting with '('
2060    */
2061 char *
2062 StripHighlightAndTitle (char *s)
2063 {
2064     static char retbuf[MSG_SIZ];
2065     char *p = retbuf;
2066
2067     while (*s != NULLCHAR) {
2068         while (*s == '\033') {
2069             while (*s != NULLCHAR && !isalpha(*s)) s++;
2070             if (*s != NULLCHAR) s++;
2071         }
2072         while (*s != NULLCHAR && *s != '\033') {
2073             if (*s == '(' || *s == '[') {
2074                 *p = NULLCHAR;
2075                 return retbuf;
2076             }
2077             *p++ = *s++;
2078         }
2079     }
2080     *p = NULLCHAR;
2081     return retbuf;
2082 }
2083
2084 /* Remove all highlighting escape sequences in s */
2085 char *
2086 StripHighlight (char *s)
2087 {
2088     static char retbuf[MSG_SIZ];
2089     char *p = retbuf;
2090
2091     while (*s != NULLCHAR) {
2092         while (*s == '\033') {
2093             while (*s != NULLCHAR && !isalpha(*s)) s++;
2094             if (*s != NULLCHAR) s++;
2095         }
2096         while (*s != NULLCHAR && *s != '\033') {
2097             *p++ = *s++;
2098         }
2099     }
2100     *p = NULLCHAR;
2101     return retbuf;
2102 }
2103
2104 char engineVariant[MSG_SIZ];
2105 char *variantNames[] = VARIANT_NAMES;
2106 char *
2107 VariantName (VariantClass v)
2108 {
2109     if(v == VariantUnknown || *engineVariant) return engineVariant;
2110     return variantNames[v];
2111 }
2112
2113
2114 /* Identify a variant from the strings the chess servers use or the
2115    PGN Variant tag names we use. */
2116 VariantClass
2117 StringToVariant (char *e)
2118 {
2119     char *p;
2120     int wnum = -1;
2121     VariantClass v = VariantNormal;
2122     int i, found = FALSE;
2123     char buf[MSG_SIZ], c;
2124     int len;
2125
2126     if (!e) return v;
2127
2128     /* [HGM] skip over optional board-size prefixes */
2129     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2130         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2131         while( *e++ != '_');
2132     }
2133
2134     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2135         v = VariantNormal;
2136         found = TRUE;
2137     } else
2138     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2139       if (p = StrCaseStr(e, variantNames[i])) {
2140         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2141         v = (VariantClass) i;
2142         found = TRUE;
2143         break;
2144       }
2145     }
2146
2147     if (!found) {
2148       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2149           || StrCaseStr(e, "wild/fr")
2150           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2151         v = VariantFischeRandom;
2152       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2153                  (i = 1, p = StrCaseStr(e, "w"))) {
2154         p += i;
2155         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2156         if (isdigit(*p)) {
2157           wnum = atoi(p);
2158         } else {
2159           wnum = -1;
2160         }
2161         switch (wnum) {
2162         case 0: /* FICS only, actually */
2163         case 1:
2164           /* Castling legal even if K starts on d-file */
2165           v = VariantWildCastle;
2166           break;
2167         case 2:
2168         case 3:
2169         case 4:
2170           /* Castling illegal even if K & R happen to start in
2171              normal positions. */
2172           v = VariantNoCastle;
2173           break;
2174         case 5:
2175         case 7:
2176         case 8:
2177         case 10:
2178         case 11:
2179         case 12:
2180         case 13:
2181         case 14:
2182         case 15:
2183         case 18:
2184         case 19:
2185           /* Castling legal iff K & R start in normal positions */
2186           v = VariantNormal;
2187           break;
2188         case 6:
2189         case 20:
2190         case 21:
2191           /* Special wilds for position setup; unclear what to do here */
2192           v = VariantLoadable;
2193           break;
2194         case 9:
2195           /* Bizarre ICC game */
2196           v = VariantTwoKings;
2197           break;
2198         case 16:
2199           v = VariantKriegspiel;
2200           break;
2201         case 17:
2202           v = VariantLosers;
2203           break;
2204         case 22:
2205           v = VariantFischeRandom;
2206           break;
2207         case 23:
2208           v = VariantCrazyhouse;
2209           break;
2210         case 24:
2211           v = VariantBughouse;
2212           break;
2213         case 25:
2214           v = Variant3Check;
2215           break;
2216         case 26:
2217           /* Not quite the same as FICS suicide! */
2218           v = VariantGiveaway;
2219           break;
2220         case 27:
2221           v = VariantAtomic;
2222           break;
2223         case 28:
2224           v = VariantShatranj;
2225           break;
2226
2227         /* Temporary names for future ICC types.  The name *will* change in
2228            the next xboard/WinBoard release after ICC defines it. */
2229         case 29:
2230           v = Variant29;
2231           break;
2232         case 30:
2233           v = Variant30;
2234           break;
2235         case 31:
2236           v = Variant31;
2237           break;
2238         case 32:
2239           v = Variant32;
2240           break;
2241         case 33:
2242           v = Variant33;
2243           break;
2244         case 34:
2245           v = Variant34;
2246           break;
2247         case 35:
2248           v = Variant35;
2249           break;
2250         case 36:
2251           v = Variant36;
2252           break;
2253         case 37:
2254           v = VariantShogi;
2255           break;
2256         case 38:
2257           v = VariantXiangqi;
2258           break;
2259         case 39:
2260           v = VariantCourier;
2261           break;
2262         case 40:
2263           v = VariantGothic;
2264           break;
2265         case 41:
2266           v = VariantCapablanca;
2267           break;
2268         case 42:
2269           v = VariantKnightmate;
2270           break;
2271         case 43:
2272           v = VariantFairy;
2273           break;
2274         case 44:
2275           v = VariantCylinder;
2276           break;
2277         case 45:
2278           v = VariantFalcon;
2279           break;
2280         case 46:
2281           v = VariantCapaRandom;
2282           break;
2283         case 47:
2284           v = VariantBerolina;
2285           break;
2286         case 48:
2287           v = VariantJanus;
2288           break;
2289         case 49:
2290           v = VariantSuper;
2291           break;
2292         case 50:
2293           v = VariantGreat;
2294           break;
2295         case -1:
2296           /* Found "wild" or "w" in the string but no number;
2297              must assume it's normal chess. */
2298           v = VariantNormal;
2299           break;
2300         default:
2301           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2302           if( (len >= MSG_SIZ) && appData.debugMode )
2303             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2304
2305           DisplayError(buf, 0);
2306           v = VariantUnknown;
2307           break;
2308         }
2309       }
2310     }
2311     if (appData.debugMode) {
2312       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2313               e, wnum, VariantName(v));
2314     }
2315     return v;
2316 }
2317
2318 static int leftover_start = 0, leftover_len = 0;
2319 char star_match[STAR_MATCH_N][MSG_SIZ];
2320
2321 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2322    advance *index beyond it, and set leftover_start to the new value of
2323    *index; else return FALSE.  If pattern contains the character '*', it
2324    matches any sequence of characters not containing '\r', '\n', or the
2325    character following the '*' (if any), and the matched sequence(s) are
2326    copied into star_match.
2327    */
2328 int
2329 looking_at ( char *buf, int *index, char *pattern)
2330 {
2331     char *bufp = &buf[*index], *patternp = pattern;
2332     int star_count = 0;
2333     char *matchp = star_match[0];
2334
2335     for (;;) {
2336         if (*patternp == NULLCHAR) {
2337             *index = leftover_start = bufp - buf;
2338             *matchp = NULLCHAR;
2339             return TRUE;
2340         }
2341         if (*bufp == NULLCHAR) return FALSE;
2342         if (*patternp == '*') {
2343             if (*bufp == *(patternp + 1)) {
2344                 *matchp = NULLCHAR;
2345                 matchp = star_match[++star_count];
2346                 patternp += 2;
2347                 bufp++;
2348                 continue;
2349             } else if (*bufp == '\n' || *bufp == '\r') {
2350                 patternp++;
2351                 if (*patternp == NULLCHAR)
2352                   continue;
2353                 else
2354                   return FALSE;
2355             } else {
2356                 *matchp++ = *bufp++;
2357                 continue;
2358             }
2359         }
2360         if (*patternp != *bufp) return FALSE;
2361         patternp++;
2362         bufp++;
2363     }
2364 }
2365
2366 void
2367 SendToPlayer (char *data, int length)
2368 {
2369     int error, outCount;
2370     outCount = OutputToProcess(NoProc, data, length, &error);
2371     if (outCount < length) {
2372         DisplayFatalError(_("Error writing to display"), error, 1);
2373     }
2374 }
2375
2376 void
2377 PackHolding (char packed[], char *holding)
2378 {
2379     char *p = holding;
2380     char *q = packed;
2381     int runlength = 0;
2382     int curr = 9999;
2383     do {
2384         if (*p == curr) {
2385             runlength++;
2386         } else {
2387             switch (runlength) {
2388               case 0:
2389                 break;
2390               case 1:
2391                 *q++ = curr;
2392                 break;
2393               case 2:
2394                 *q++ = curr;
2395                 *q++ = curr;
2396                 break;
2397               default:
2398                 sprintf(q, "%d", runlength);
2399                 while (*q) q++;
2400                 *q++ = curr;
2401                 break;
2402             }
2403             runlength = 1;
2404             curr = *p;
2405         }
2406     } while (*p++);
2407     *q = NULLCHAR;
2408 }
2409
2410 /* Telnet protocol requests from the front end */
2411 void
2412 TelnetRequest (unsigned char ddww, unsigned char option)
2413 {
2414     unsigned char msg[3];
2415     int outCount, outError;
2416
2417     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2418
2419     if (appData.debugMode) {
2420         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2421         switch (ddww) {
2422           case TN_DO:
2423             ddwwStr = "DO";
2424             break;
2425           case TN_DONT:
2426             ddwwStr = "DONT";
2427             break;
2428           case TN_WILL:
2429             ddwwStr = "WILL";
2430             break;
2431           case TN_WONT:
2432             ddwwStr = "WONT";
2433             break;
2434           default:
2435             ddwwStr = buf1;
2436             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2437             break;
2438         }
2439         switch (option) {
2440           case TN_ECHO:
2441             optionStr = "ECHO";
2442             break;
2443           default:
2444             optionStr = buf2;
2445             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2446             break;
2447         }
2448         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2449     }
2450     msg[0] = TN_IAC;
2451     msg[1] = ddww;
2452     msg[2] = option;
2453     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2454     if (outCount < 3) {
2455         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2456     }
2457 }
2458
2459 void
2460 DoEcho ()
2461 {
2462     if (!appData.icsActive) return;
2463     TelnetRequest(TN_DO, TN_ECHO);
2464 }
2465
2466 void
2467 DontEcho ()
2468 {
2469     if (!appData.icsActive) return;
2470     TelnetRequest(TN_DONT, TN_ECHO);
2471 }
2472
2473 void
2474 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2475 {
2476     /* put the holdings sent to us by the server on the board holdings area */
2477     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2478     char p;
2479     ChessSquare piece;
2480
2481     if(gameInfo.holdingsWidth < 2)  return;
2482     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2483         return; // prevent overwriting by pre-board holdings
2484
2485     if( (int)lowestPiece >= BlackPawn ) {
2486         holdingsColumn = 0;
2487         countsColumn = 1;
2488         holdingsStartRow = handSize-1;
2489         direction = -1;
2490     } else {
2491         holdingsColumn = BOARD_WIDTH-1;
2492         countsColumn = BOARD_WIDTH-2;
2493         holdingsStartRow = 0;
2494         direction = 1;
2495     }
2496
2497     for(i=0; i<BOARD_RANKS-1; i++) { /* clear holdings */
2498         board[i][holdingsColumn] = EmptySquare;
2499         board[i][countsColumn]   = (ChessSquare) 0;
2500     }
2501     while( (p=*holdings++) != NULLCHAR ) {
2502         piece = CharToPiece( ToUpper(p) );
2503         if(piece == EmptySquare) continue;
2504         /*j = (int) piece - (int) WhitePawn;*/
2505         j = PieceToNumber(piece);
2506         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2507         if(j < 0) continue;               /* should not happen */
2508         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2509         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2510         board[holdingsStartRow+j*direction][countsColumn]++;
2511     }
2512 }
2513
2514
2515 void
2516 VariantSwitch (Board board, VariantClass newVariant)
2517 {
2518    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2519    static Board oldBoard;
2520
2521    startedFromPositionFile = FALSE;
2522    if(gameInfo.variant == newVariant) return;
2523
2524    /* [HGM] This routine is called each time an assignment is made to
2525     * gameInfo.variant during a game, to make sure the board sizes
2526     * are set to match the new variant. If that means adding or deleting
2527     * holdings, we shift the playing board accordingly
2528     * This kludge is needed because in ICS observe mode, we get boards
2529     * of an ongoing game without knowing the variant, and learn about the
2530     * latter only later. This can be because of the move list we requested,
2531     * in which case the game history is refilled from the beginning anyway,
2532     * but also when receiving holdings of a crazyhouse game. In the latter
2533     * case we want to add those holdings to the already received position.
2534     */
2535
2536
2537    if (appData.debugMode) {
2538      fprintf(debugFP, "Switch board from %s to %s\n",
2539              VariantName(gameInfo.variant), VariantName(newVariant));
2540      setbuf(debugFP, NULL);
2541    }
2542    shuffleOpenings = 0;       /* [HGM] shuffle */
2543    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2544    switch(newVariant)
2545      {
2546      case VariantShogi:
2547        newWidth = 9;  newHeight = 9;
2548        gameInfo.holdingsSize = 7;
2549      case VariantBughouse:
2550      case VariantCrazyhouse:
2551        newHoldingsWidth = 2; break;
2552      case VariantGreat:
2553        newWidth = 10;
2554      case VariantSuper:
2555        newHoldingsWidth = 2;
2556        gameInfo.holdingsSize = 8;
2557        break;
2558      case VariantGothic:
2559      case VariantCapablanca:
2560      case VariantCapaRandom:
2561        newWidth = 10;
2562      default:
2563        newHoldingsWidth = gameInfo.holdingsSize = 0;
2564      };
2565
2566    if(newWidth  != gameInfo.boardWidth  ||
2567       newHeight != gameInfo.boardHeight ||
2568       newHoldingsWidth != gameInfo.holdingsWidth ) {
2569
2570      /* shift position to new playing area, if needed */
2571      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2572        for(i=0; i<BOARD_HEIGHT; i++)
2573          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2574            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575              board[i][j];
2576        for(i=0; i<newHeight; i++) {
2577          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2578          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2579        }
2580      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2581        for(i=0; i<BOARD_HEIGHT; i++)
2582          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2583            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2584              board[i][j];
2585      }
2586      board[HOLDINGS_SET] = 0;
2587      gameInfo.boardWidth  = newWidth;
2588      gameInfo.boardHeight = newHeight;
2589      gameInfo.holdingsWidth = newHoldingsWidth;
2590      gameInfo.variant = newVariant;
2591      InitDrawingSizes(-2, 0);
2592    } else gameInfo.variant = newVariant;
2593    CopyBoard(oldBoard, board);   // remember correctly formatted board
2594      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2595    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2596 }
2597
2598 static int loggedOn = FALSE;
2599
2600 /*-- Game start info cache: --*/
2601 int gs_gamenum;
2602 char gs_kind[MSG_SIZ];
2603 static char player1Name[128] = "";
2604 static char player2Name[128] = "";
2605 static char cont_seq[] = "\n\\   ";
2606 static int player1Rating = -1;
2607 static int player2Rating = -1;
2608 /*----------------------------*/
2609
2610 ColorClass curColor = ColorNormal;
2611 int suppressKibitz = 0;
2612
2613 // [HGM] seekgraph
2614 Boolean soughtPending = FALSE;
2615 Boolean seekGraphUp;
2616 #define MAX_SEEK_ADS 200
2617 #define SQUARE 0x80
2618 char *seekAdList[MAX_SEEK_ADS];
2619 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2620 float tcList[MAX_SEEK_ADS];
2621 char colorList[MAX_SEEK_ADS];
2622 int nrOfSeekAds = 0;
2623 int minRating = 1010, maxRating = 2800;
2624 int hMargin = 10, vMargin = 20, h, w;
2625 extern int squareSize, lineGap;
2626
2627 void
2628 PlotSeekAd (int i)
2629 {
2630         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2631         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2632         if(r < minRating+100 && r >=0 ) r = minRating+100;
2633         if(r > maxRating) r = maxRating;
2634         if(tc < 1.f) tc = 1.f;
2635         if(tc > 95.f) tc = 95.f;
2636         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2637         y = ((double)r - minRating)/(maxRating - minRating)
2638             * (h-vMargin-squareSize/8-1) + vMargin;
2639         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2640         if(strstr(seekAdList[i], " u ")) color = 1;
2641         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2642            !strstr(seekAdList[i], "bullet") &&
2643            !strstr(seekAdList[i], "blitz") &&
2644            !strstr(seekAdList[i], "standard") ) color = 2;
2645         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2646         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2647 }
2648
2649 void
2650 PlotSingleSeekAd (int i)
2651 {
2652         PlotSeekAd(i);
2653 }
2654
2655 void
2656 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2657 {
2658         char buf[MSG_SIZ], *ext = "";
2659         VariantClass v = StringToVariant(type);
2660         if(strstr(type, "wild")) {
2661             ext = type + 4; // append wild number
2662             if(v == VariantFischeRandom) type = "chess960"; else
2663             if(v == VariantLoadable) type = "setup"; else
2664             type = VariantName(v);
2665         }
2666         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2667         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2668             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2669             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2670             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2671             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2672             seekNrList[nrOfSeekAds] = nr;
2673             zList[nrOfSeekAds] = 0;
2674             seekAdList[nrOfSeekAds++] = StrSave(buf);
2675             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2676         }
2677 }
2678
2679 void
2680 EraseSeekDot (int i)
2681 {
2682     int x = xList[i], y = yList[i], d=squareSize/4, k;
2683     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2684     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2685     // now replot every dot that overlapped
2686     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2687         int xx = xList[k], yy = yList[k];
2688         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2689             DrawSeekDot(xx, yy, colorList[k]);
2690     }
2691 }
2692
2693 void
2694 RemoveSeekAd (int nr)
2695 {
2696         int i;
2697         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2698             EraseSeekDot(i);
2699             if(seekAdList[i]) free(seekAdList[i]);
2700             seekAdList[i] = seekAdList[--nrOfSeekAds];
2701             seekNrList[i] = seekNrList[nrOfSeekAds];
2702             ratingList[i] = ratingList[nrOfSeekAds];
2703             colorList[i]  = colorList[nrOfSeekAds];
2704             tcList[i] = tcList[nrOfSeekAds];
2705             xList[i]  = xList[nrOfSeekAds];
2706             yList[i]  = yList[nrOfSeekAds];
2707             zList[i]  = zList[nrOfSeekAds];
2708             seekAdList[nrOfSeekAds] = NULL;
2709             break;
2710         }
2711 }
2712
2713 Boolean
2714 MatchSoughtLine (char *line)
2715 {
2716     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2717     int nr, base, inc, u=0; char dummy;
2718
2719     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2720        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2721        (u=1) &&
2722        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2723         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2724         // match: compact and save the line
2725         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2726         return TRUE;
2727     }
2728     return FALSE;
2729 }
2730
2731 int
2732 DrawSeekGraph ()
2733 {
2734     int i;
2735     if(!seekGraphUp) return FALSE;
2736     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2737     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2738
2739     DrawSeekBackground(0, 0, w, h);
2740     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2741     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2742     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2743         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2744         yy = h-1-yy;
2745         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2746         if(i%500 == 0) {
2747             char buf[MSG_SIZ];
2748             snprintf(buf, MSG_SIZ, "%d", i);
2749             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2750         }
2751     }
2752     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2753     for(i=1; i<100; i+=(i<10?1:5)) {
2754         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2755         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2756         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2757             char buf[MSG_SIZ];
2758             snprintf(buf, MSG_SIZ, "%d", i);
2759             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2760         }
2761     }
2762     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2763     return TRUE;
2764 }
2765
2766 int
2767 SeekGraphClick (ClickType click, int x, int y, int moving)
2768 {
2769     static int lastDown = 0, displayed = 0, lastSecond;
2770     if(y < 0) return FALSE;
2771     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2772         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2773         if(!seekGraphUp) return FALSE;
2774         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2775         DrawPosition(TRUE, NULL);
2776         return TRUE;
2777     }
2778     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2779         if(click == Release || moving) return FALSE;
2780         nrOfSeekAds = 0;
2781         soughtPending = TRUE;
2782         SendToICS(ics_prefix);
2783         SendToICS("sought\n"); // should this be "sought all"?
2784     } else { // issue challenge based on clicked ad
2785         int dist = 10000; int i, closest = 0, second = 0;
2786         for(i=0; i<nrOfSeekAds; i++) {
2787             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2788             if(d < dist) { dist = d; closest = i; }
2789             second += (d - zList[i] < 120); // count in-range ads
2790             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2791         }
2792         if(dist < 120) {
2793             char buf[MSG_SIZ];
2794             second = (second > 1);
2795             if(displayed != closest || second != lastSecond) {
2796                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2797                 lastSecond = second; displayed = closest;
2798             }
2799             if(click == Press) {
2800                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2801                 lastDown = closest;
2802                 return TRUE;
2803             } // on press 'hit', only show info
2804             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2805             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2806             SendToICS(ics_prefix);
2807             SendToICS(buf);
2808             return TRUE; // let incoming board of started game pop down the graph
2809         } else if(click == Release) { // release 'miss' is ignored
2810             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2811             if(moving == 2) { // right up-click
2812                 nrOfSeekAds = 0; // refresh graph
2813                 soughtPending = TRUE;
2814                 SendToICS(ics_prefix);
2815                 SendToICS("sought\n"); // should this be "sought all"?
2816             }
2817             return TRUE;
2818         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2819         // press miss or release hit 'pop down' seek graph
2820         seekGraphUp = FALSE;
2821         DrawPosition(TRUE, NULL);
2822     }
2823     return TRUE;
2824 }
2825
2826 void
2827 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2828 {
2829 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2830 #define STARTED_NONE 0
2831 #define STARTED_MOVES 1
2832 #define STARTED_BOARD 2
2833 #define STARTED_OBSERVE 3
2834 #define STARTED_HOLDINGS 4
2835 #define STARTED_CHATTER 5
2836 #define STARTED_COMMENT 6
2837 #define STARTED_MOVES_NOHIDE 7
2838
2839     static int started = STARTED_NONE;
2840     static char parse[20000];
2841     static int parse_pos = 0;
2842     static char buf[BUF_SIZE + 1];
2843     static int firstTime = TRUE, intfSet = FALSE;
2844     static ColorClass prevColor = ColorNormal;
2845     static int savingComment = FALSE;
2846     static int cmatch = 0; // continuation sequence match
2847     char *bp;
2848     char str[MSG_SIZ];
2849     int i, oldi;
2850     int buf_len;
2851     int next_out;
2852     int tkind;
2853     int backup;    /* [DM] For zippy color lines */
2854     char *p;
2855     char talker[MSG_SIZ]; // [HGM] chat
2856     int channel, collective=0;
2857
2858     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2859
2860     if (appData.debugMode) {
2861       if (!error) {
2862         fprintf(debugFP, "<ICS: ");
2863         show_bytes(debugFP, data, count);
2864         fprintf(debugFP, "\n");
2865       }
2866     }
2867
2868     if (appData.debugMode) { int f = forwardMostMove;
2869         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2870                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2871                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2872     }
2873     if (count > 0) {
2874         /* If last read ended with a partial line that we couldn't parse,
2875            prepend it to the new read and try again. */
2876         if (leftover_len > 0) {
2877             for (i=0; i<leftover_len; i++)
2878               buf[i] = buf[leftover_start + i];
2879         }
2880
2881     /* copy new characters into the buffer */
2882     bp = buf + leftover_len;
2883     buf_len=leftover_len;
2884     for (i=0; i<count; i++)
2885     {
2886         // ignore these
2887         if (data[i] == '\r')
2888             continue;
2889
2890         // join lines split by ICS?
2891         if (!appData.noJoin)
2892         {
2893             /*
2894                 Joining just consists of finding matches against the
2895                 continuation sequence, and discarding that sequence
2896                 if found instead of copying it.  So, until a match
2897                 fails, there's nothing to do since it might be the
2898                 complete sequence, and thus, something we don't want
2899                 copied.
2900             */
2901             if (data[i] == cont_seq[cmatch])
2902             {
2903                 cmatch++;
2904                 if (cmatch == strlen(cont_seq))
2905                 {
2906                     cmatch = 0; // complete match.  just reset the counter
2907
2908                     /*
2909                         it's possible for the ICS to not include the space
2910                         at the end of the last word, making our [correct]
2911                         join operation fuse two separate words.  the server
2912                         does this when the space occurs at the width setting.
2913                     */
2914                     if (!buf_len || buf[buf_len-1] != ' ')
2915                     {
2916                         *bp++ = ' ';
2917                         buf_len++;
2918                     }
2919                 }
2920                 continue;
2921             }
2922             else if (cmatch)
2923             {
2924                 /*
2925                     match failed, so we have to copy what matched before
2926                     falling through and copying this character.  In reality,
2927                     this will only ever be just the newline character, but
2928                     it doesn't hurt to be precise.
2929                 */
2930                 strncpy(bp, cont_seq, cmatch);
2931                 bp += cmatch;
2932                 buf_len += cmatch;
2933                 cmatch = 0;
2934             }
2935         }
2936
2937         // copy this char
2938         *bp++ = data[i];
2939         buf_len++;
2940     }
2941
2942         buf[buf_len] = NULLCHAR;
2943 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2944         next_out = 0;
2945         leftover_start = 0;
2946
2947         i = 0;
2948         while (i < buf_len) {
2949             /* Deal with part of the TELNET option negotiation
2950                protocol.  We refuse to do anything beyond the
2951                defaults, except that we allow the WILL ECHO option,
2952                which ICS uses to turn off password echoing when we are
2953                directly connected to it.  We reject this option
2954                if localLineEditing mode is on (always on in xboard)
2955                and we are talking to port 23, which might be a real
2956                telnet server that will try to keep WILL ECHO on permanently.
2957              */
2958             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2959                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2960                 unsigned char option;
2961                 oldi = i;
2962                 switch ((unsigned char) buf[++i]) {
2963                   case TN_WILL:
2964                     if (appData.debugMode)
2965                       fprintf(debugFP, "\n<WILL ");
2966                     switch (option = (unsigned char) buf[++i]) {
2967                       case TN_ECHO:
2968                         if (appData.debugMode)
2969                           fprintf(debugFP, "ECHO ");
2970                         /* Reply only if this is a change, according
2971                            to the protocol rules. */
2972                         if (remoteEchoOption) break;
2973                         if (appData.localLineEditing &&
2974                             atoi(appData.icsPort) == TN_PORT) {
2975                             TelnetRequest(TN_DONT, TN_ECHO);
2976                         } else {
2977                             EchoOff();
2978                             TelnetRequest(TN_DO, TN_ECHO);
2979                             remoteEchoOption = TRUE;
2980                         }
2981                         break;
2982                       default:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "%d ", option);
2985                         /* Whatever this is, we don't want it. */
2986                         TelnetRequest(TN_DONT, option);
2987                         break;
2988                     }
2989                     break;
2990                   case TN_WONT:
2991                     if (appData.debugMode)
2992                       fprintf(debugFP, "\n<WONT ");
2993                     switch (option = (unsigned char) buf[++i]) {
2994                       case TN_ECHO:
2995                         if (appData.debugMode)
2996                           fprintf(debugFP, "ECHO ");
2997                         /* Reply only if this is a change, according
2998                            to the protocol rules. */
2999                         if (!remoteEchoOption) break;
3000                         EchoOn();
3001                         TelnetRequest(TN_DONT, TN_ECHO);
3002                         remoteEchoOption = FALSE;
3003                         break;
3004                       default:
3005                         if (appData.debugMode)
3006                           fprintf(debugFP, "%d ", (unsigned char) option);
3007                         /* Whatever this is, it must already be turned
3008                            off, because we never agree to turn on
3009                            anything non-default, so according to the
3010                            protocol rules, we don't reply. */
3011                         break;
3012                     }
3013                     break;
3014                   case TN_DO:
3015                     if (appData.debugMode)
3016                       fprintf(debugFP, "\n<DO ");
3017                     switch (option = (unsigned char) buf[++i]) {
3018                       default:
3019                         /* Whatever this is, we refuse to do it. */
3020                         if (appData.debugMode)
3021                           fprintf(debugFP, "%d ", option);
3022                         TelnetRequest(TN_WONT, option);
3023                         break;
3024                     }
3025                     break;
3026                   case TN_DONT:
3027                     if (appData.debugMode)
3028                       fprintf(debugFP, "\n<DONT ");
3029                     switch (option = (unsigned char) buf[++i]) {
3030                       default:
3031                         if (appData.debugMode)
3032                           fprintf(debugFP, "%d ", option);
3033                         /* Whatever this is, we are already not doing
3034                            it, because we never agree to do anything
3035                            non-default, so according to the protocol
3036                            rules, we don't reply. */
3037                         break;
3038                     }
3039                     break;
3040                   case TN_IAC:
3041                     if (appData.debugMode)
3042                       fprintf(debugFP, "\n<IAC ");
3043                     /* Doubled IAC; pass it through */
3044                     i--;
3045                     break;
3046                   default:
3047                     if (appData.debugMode)
3048                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3049                     /* Drop all other telnet commands on the floor */
3050                     break;
3051                 }
3052                 if (oldi > next_out)
3053                   SendToPlayer(&buf[next_out], oldi - next_out);
3054                 if (++i > next_out)
3055                   next_out = i;
3056                 continue;
3057             }
3058
3059             /* OK, this at least will *usually* work */
3060             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3061                 loggedOn = TRUE;
3062             }
3063
3064             if (loggedOn && !intfSet) {
3065                 if (ics_type == ICS_ICC) {
3066                   snprintf(str, MSG_SIZ,
3067                           "/set-quietly interface %s\n/set-quietly style 12\n",
3068                           programVersion);
3069                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3071                 } else if (ics_type == ICS_CHESSNET) {
3072                   snprintf(str, MSG_SIZ, "/style 12\n");
3073                 } else {
3074                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3075                   strcat(str, programVersion);
3076                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3077                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3078                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3079 #ifdef WIN32
3080                   strcat(str, "$iset nohighlight 1\n");
3081 #endif
3082                   strcat(str, "$iset lock 1\n$style 12\n");
3083                 }
3084                 SendToICS(str);
3085                 NotifyFrontendLogin();
3086                 intfSet = TRUE;
3087             }
3088
3089             if (started == STARTED_COMMENT) {
3090                 /* Accumulate characters in comment */
3091                 parse[parse_pos++] = buf[i];
3092                 if (buf[i] == '\n') {
3093                     parse[parse_pos] = NULLCHAR;
3094                     if(chattingPartner>=0) {
3095                         char mess[MSG_SIZ];
3096                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3097                         OutputChatMessage(chattingPartner, mess);
3098                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3099                             int p;
3100                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3101                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3102                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3103                                 OutputChatMessage(p, mess);
3104                                 break;
3105                             }
3106                         }
3107                         chattingPartner = -1;
3108                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3109                         collective = 0;
3110                     } else
3111                     if(!suppressKibitz) // [HGM] kibitz
3112                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3113                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3114                         int nrDigit = 0, nrAlph = 0, j;
3115                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3116                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3117                         parse[parse_pos] = NULLCHAR;
3118                         // try to be smart: if it does not look like search info, it should go to
3119                         // ICS interaction window after all, not to engine-output window.
3120                         for(j=0; j<parse_pos; j++) { // count letters and digits
3121                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3122                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3123                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3124                         }
3125                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3126                             int depth=0; float score;
3127                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3128                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3129                                 pvInfoList[forwardMostMove-1].depth = depth;
3130                                 pvInfoList[forwardMostMove-1].score = 100*score;
3131                             }
3132                             OutputKibitz(suppressKibitz, parse);
3133                         } else {
3134                             char tmp[MSG_SIZ];
3135                             if(gameMode == IcsObserving) // restore original ICS messages
3136                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3137                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3138                             else
3139                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3140                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3141                             SendToPlayer(tmp, strlen(tmp));
3142                         }
3143                         next_out = i+1; // [HGM] suppress printing in ICS window
3144                     }
3145                     started = STARTED_NONE;
3146                 } else {
3147                     /* Don't match patterns against characters in comment */
3148                     i++;
3149                     continue;
3150                 }
3151             }
3152             if (started == STARTED_CHATTER) {
3153                 if (buf[i] != '\n') {
3154                     /* Don't match patterns against characters in chatter */
3155                     i++;
3156                     continue;
3157                 }
3158                 started = STARTED_NONE;
3159                 if(suppressKibitz) next_out = i+1;
3160             }
3161
3162             /* Kludge to deal with rcmd protocol */
3163             if (firstTime && looking_at(buf, &i, "\001*")) {
3164                 DisplayFatalError(&buf[1], 0, 1);
3165                 continue;
3166             } else {
3167                 firstTime = FALSE;
3168             }
3169
3170             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3171                 ics_type = ICS_ICC;
3172                 ics_prefix = "/";
3173                 if (appData.debugMode)
3174                   fprintf(debugFP, "ics_type %d\n", ics_type);
3175                 continue;
3176             }
3177             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3178                 ics_type = ICS_FICS;
3179                 ics_prefix = "$";
3180                 if (appData.debugMode)
3181                   fprintf(debugFP, "ics_type %d\n", ics_type);
3182                 continue;
3183             }
3184             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3185                 ics_type = ICS_CHESSNET;
3186                 ics_prefix = "/";
3187                 if (appData.debugMode)
3188                   fprintf(debugFP, "ics_type %d\n", ics_type);
3189                 continue;
3190             }
3191
3192             if (!loggedOn &&
3193                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3194                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3195                  looking_at(buf, &i, "will be \"*\""))) {
3196               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3197               continue;
3198             }
3199
3200             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3201               char buf[MSG_SIZ];
3202               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3203               DisplayIcsInteractionTitle(buf);
3204               have_set_title = TRUE;
3205             }
3206
3207             /* skip finger notes */
3208             if (started == STARTED_NONE &&
3209                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3210                  (buf[i] == '1' && buf[i+1] == '0')) &&
3211                 buf[i+2] == ':' && buf[i+3] == ' ') {
3212               started = STARTED_CHATTER;
3213               i += 3;
3214               continue;
3215             }
3216
3217             oldi = i;
3218             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3219             if(appData.seekGraph) {
3220                 if(soughtPending && MatchSoughtLine(buf+i)) {
3221                     i = strstr(buf+i, "rated") - buf;
3222                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3223                     next_out = leftover_start = i;
3224                     started = STARTED_CHATTER;
3225                     suppressKibitz = TRUE;
3226                     continue;
3227                 }
3228                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3229                         && looking_at(buf, &i, "* ads displayed")) {
3230                     soughtPending = FALSE;
3231                     seekGraphUp = TRUE;
3232                     DrawSeekGraph();
3233                     continue;
3234                 }
3235                 if(appData.autoRefresh) {
3236                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3237                         int s = (ics_type == ICS_ICC); // ICC format differs
3238                         if(seekGraphUp)
3239                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3240                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3241                         looking_at(buf, &i, "*% "); // eat prompt
3242                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3243                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3244                         next_out = i; // suppress
3245                         continue;
3246                     }
3247                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3248                         char *p = star_match[0];
3249                         while(*p) {
3250                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3251                             while(*p && *p++ != ' '); // next
3252                         }
3253                         looking_at(buf, &i, "*% "); // eat prompt
3254                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3255                         next_out = i;
3256                         continue;
3257                     }
3258                 }
3259             }
3260
3261             /* skip formula vars */
3262             if (started == STARTED_NONE &&
3263                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3264               started = STARTED_CHATTER;
3265               i += 3;
3266               continue;
3267             }
3268
3269             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3270             if (appData.autoKibitz && started == STARTED_NONE &&
3271                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3272                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3273                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3274                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3275                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3276                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3277                         suppressKibitz = TRUE;
3278                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3279                         next_out = i;
3280                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3281                                 && (gameMode == IcsPlayingWhite)) ||
3282                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3283                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3284                             started = STARTED_CHATTER; // own kibitz we simply discard
3285                         else {
3286                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3287                             parse_pos = 0; parse[0] = NULLCHAR;
3288                             savingComment = TRUE;
3289                             suppressKibitz = gameMode != IcsObserving ? 2 :
3290                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3291                         }
3292                         continue;
3293                 } else
3294                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3295                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3296                          && atoi(star_match[0])) {
3297                     // suppress the acknowledgements of our own autoKibitz
3298                     char *p;
3299                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3300                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3301                     SendToPlayer(star_match[0], strlen(star_match[0]));
3302                     if(looking_at(buf, &i, "*% ")) // eat prompt
3303                         suppressKibitz = FALSE;
3304                     next_out = i;
3305                     continue;
3306                 }
3307             } // [HGM] kibitz: end of patch
3308
3309             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3310
3311             // [HGM] chat: intercept tells by users for which we have an open chat window
3312             channel = -1;
3313             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3314                                            looking_at(buf, &i, "* whispers:") ||
3315                                            looking_at(buf, &i, "* kibitzes:") ||
3316                                            looking_at(buf, &i, "* shouts:") ||
3317                                            looking_at(buf, &i, "* c-shouts:") ||
3318                                            looking_at(buf, &i, "--> * ") ||
3319                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3320                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3321                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3322                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3323                 int p;
3324                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3325                 chattingPartner = -1; collective = 0;
3326
3327                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3331                     talker[0] = '['; strcat(talker, "] ");
3332                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3333                     chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3337                 for(p=0; p<MAX_CHAT; p++) {
3338                     collective = 1;
3339                     if(!strcmp("kibitzes", chatPartner[p])) {
3340                         talker[0] = '['; strcat(talker, "] ");
3341                         chattingPartner = p; break;
3342                     }
3343                 } else
3344                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3345                 for(p=0; p<MAX_CHAT; p++) {
3346                     collective = 1;
3347                     if(!strcmp("whispers", chatPartner[p])) {
3348                         talker[0] = '['; strcat(talker, "] ");
3349                         chattingPartner = p; break;
3350                     }
3351                 } else
3352                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3353                   if(buf[i-8] == '-' && buf[i-3] == 't')
3354                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3355                     collective = 1;
3356                     if(!strcmp("c-shouts", chatPartner[p])) {
3357                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3358                         chattingPartner = p; break;
3359                     }
3360                   }
3361                   if(chattingPartner < 0)
3362                   for(p=0; p<MAX_CHAT; p++) {
3363                     collective = 1;
3364                     if(!strcmp("shouts", chatPartner[p])) {
3365                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3366                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3367                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3368                         chattingPartner = p; break;
3369                     }
3370                   }
3371                 }
3372                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3373                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3374                     talker[0] = 0;
3375                     Colorize(ColorTell, FALSE);
3376                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3377                     collective |= 2;
3378                     chattingPartner = p; break;
3379                 }
3380                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3381                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3382                     started = STARTED_COMMENT;
3383                     parse_pos = 0; parse[0] = NULLCHAR;
3384                     savingComment = 3 + chattingPartner; // counts as TRUE
3385                     if(collective == 3) i = oldi; else {
3386                         suppressKibitz = TRUE;
3387                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3388                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3389                         continue;
3390                     }
3391                 }
3392             } // [HGM] chat: end of patch
3393
3394           backup = i;
3395             if (appData.zippyTalk || appData.zippyPlay) {
3396                 /* [DM] Backup address for color zippy lines */
3397 #if ZIPPY
3398                if (loggedOn == TRUE)
3399                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3400                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3401                        ;
3402 #endif
3403             } // [DM] 'else { ' deleted
3404                 if (
3405                     /* Regular tells and says */
3406                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3407                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3408                     looking_at(buf, &i, "* says: ") ||
3409                     /* Don't color "message" or "messages" output */
3410                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3411                     looking_at(buf, &i, "*. * at *:*: ") ||
3412                     looking_at(buf, &i, "--* (*:*): ") ||
3413                     /* Message notifications (same color as tells) */
3414                     looking_at(buf, &i, "* has left a message ") ||
3415                     looking_at(buf, &i, "* just sent you a message:\n") ||
3416                     /* Whispers and kibitzes */
3417                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3418                     looking_at(buf, &i, "* kibitzes: ") ||
3419                     /* Channel tells */
3420                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3421
3422                   if (tkind == 1 && strchr(star_match[0], ':')) {
3423                       /* Avoid "tells you:" spoofs in channels */
3424                      tkind = 3;
3425                   }
3426                   if (star_match[0][0] == NULLCHAR ||
3427                       strchr(star_match[0], ' ') ||
3428                       (tkind == 3 && strchr(star_match[1], ' '))) {
3429                     /* Reject bogus matches */
3430                     i = oldi;
3431                   } else {
3432                     if (appData.colorize) {
3433                       if (oldi > next_out) {
3434                         SendToPlayer(&buf[next_out], oldi - next_out);
3435                         next_out = oldi;
3436                       }
3437                       switch (tkind) {
3438                       case 1:
3439                         Colorize(ColorTell, FALSE);
3440                         curColor = ColorTell;
3441                         break;
3442                       case 2:
3443                         Colorize(ColorKibitz, FALSE);
3444                         curColor = ColorKibitz;
3445                         break;
3446                       case 3:
3447                         p = strrchr(star_match[1], '(');
3448                         if (p == NULL) {
3449                           p = star_match[1];
3450                         } else {
3451                           p++;
3452                         }
3453                         if (atoi(p) == 1) {
3454                           Colorize(ColorChannel1, FALSE);
3455                           curColor = ColorChannel1;
3456                         } else {
3457                           Colorize(ColorChannel, FALSE);
3458                           curColor = ColorChannel;
3459                         }
3460                         break;
3461                       case 5:
3462                         curColor = ColorNormal;
3463                         break;
3464                       }
3465                     }
3466                     if (started == STARTED_NONE && appData.autoComment &&
3467                         (gameMode == IcsObserving ||
3468                          gameMode == IcsPlayingWhite ||
3469                          gameMode == IcsPlayingBlack)) {
3470                       parse_pos = i - oldi;
3471                       memcpy(parse, &buf[oldi], parse_pos);
3472                       parse[parse_pos] = NULLCHAR;
3473                       started = STARTED_COMMENT;
3474                       savingComment = TRUE;
3475                     } else if(collective != 3) {
3476                       started = STARTED_CHATTER;
3477                       savingComment = FALSE;
3478                     }
3479                     loggedOn = TRUE;
3480                     continue;
3481                   }
3482                 }
3483
3484                 if (looking_at(buf, &i, "* s-shouts: ") ||
3485                     looking_at(buf, &i, "* c-shouts: ")) {
3486                     if (appData.colorize) {
3487                         if (oldi > next_out) {
3488                             SendToPlayer(&buf[next_out], oldi - next_out);
3489                             next_out = oldi;
3490                         }
3491                         Colorize(ColorSShout, FALSE);
3492                         curColor = ColorSShout;
3493                     }
3494                     loggedOn = TRUE;
3495                     started = STARTED_CHATTER;
3496                     continue;
3497                 }
3498
3499                 if (looking_at(buf, &i, "--->")) {
3500                     loggedOn = TRUE;
3501                     continue;
3502                 }
3503
3504                 if (looking_at(buf, &i, "* shouts: ") ||
3505                     looking_at(buf, &i, "--> ")) {
3506                     if (appData.colorize) {
3507                         if (oldi > next_out) {
3508                             SendToPlayer(&buf[next_out], oldi - next_out);
3509                             next_out = oldi;
3510                         }
3511                         Colorize(ColorShout, FALSE);
3512                         curColor = ColorShout;
3513                     }
3514                     loggedOn = TRUE;
3515                     started = STARTED_CHATTER;
3516                     continue;
3517                 }
3518
3519                 if (looking_at( buf, &i, "Challenge:")) {
3520                     if (appData.colorize) {
3521                         if (oldi > next_out) {
3522                             SendToPlayer(&buf[next_out], oldi - next_out);
3523                             next_out = oldi;
3524                         }
3525                         Colorize(ColorChallenge, FALSE);
3526                         curColor = ColorChallenge;
3527                     }
3528                     loggedOn = TRUE;
3529                     continue;
3530                 }
3531
3532                 if (looking_at(buf, &i, "* offers you") ||
3533                     looking_at(buf, &i, "* offers to be") ||
3534                     looking_at(buf, &i, "* would like to") ||
3535                     looking_at(buf, &i, "* requests to") ||
3536                     looking_at(buf, &i, "Your opponent offers") ||
3537                     looking_at(buf, &i, "Your opponent requests")) {
3538
3539                     if (appData.colorize) {
3540                         if (oldi > next_out) {
3541                             SendToPlayer(&buf[next_out], oldi - next_out);
3542                             next_out = oldi;
3543                         }
3544                         Colorize(ColorRequest, FALSE);
3545                         curColor = ColorRequest;
3546                     }
3547                     continue;
3548                 }
3549
3550                 if (looking_at(buf, &i, "* (*) seeking")) {
3551                     if (appData.colorize) {
3552                         if (oldi > next_out) {
3553                             SendToPlayer(&buf[next_out], oldi - next_out);
3554                             next_out = oldi;
3555                         }
3556                         Colorize(ColorSeek, FALSE);
3557                         curColor = ColorSeek;
3558                     }
3559                     continue;
3560             }
3561
3562           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3563
3564             if (looking_at(buf, &i, "\\   ")) {
3565                 if (prevColor != ColorNormal) {
3566                     if (oldi > next_out) {
3567                         SendToPlayer(&buf[next_out], oldi - next_out);
3568                         next_out = oldi;
3569                     }
3570                     Colorize(prevColor, TRUE);
3571                     curColor = prevColor;
3572                 }
3573                 if (savingComment) {
3574                     parse_pos = i - oldi;
3575                     memcpy(parse, &buf[oldi], parse_pos);
3576                     parse[parse_pos] = NULLCHAR;
3577                     started = STARTED_COMMENT;
3578                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3579                         chattingPartner = savingComment - 3; // kludge to remember the box
3580                 } else {
3581                     started = STARTED_CHATTER;
3582                 }
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "Black Strength :") ||
3587                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3588                 looking_at(buf, &i, "<10>") ||
3589                 looking_at(buf, &i, "#@#")) {
3590                 /* Wrong board style */
3591                 loggedOn = TRUE;
3592                 SendToICS(ics_prefix);
3593                 SendToICS("set style 12\n");
3594                 SendToICS(ics_prefix);
3595                 SendToICS("refresh\n");
3596                 continue;
3597             }
3598
3599             if (looking_at(buf, &i, "login:")) {
3600               if (!have_sent_ICS_logon) {
3601                 if(ICSInitScript())
3602                   have_sent_ICS_logon = 1;
3603                 else // no init script was found
3604                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3605               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3606                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3607               }
3608                 continue;
3609             }
3610
3611             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3612                 (looking_at(buf, &i, "\n<12> ") ||
3613                  looking_at(buf, &i, "<12> "))) {
3614                 loggedOn = TRUE;
3615                 if (oldi > next_out) {
3616                     SendToPlayer(&buf[next_out], oldi - next_out);
3617                 }
3618                 next_out = i;
3619                 started = STARTED_BOARD;
3620                 parse_pos = 0;
3621                 continue;
3622             }
3623
3624             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3625                 looking_at(buf, &i, "<b1> ")) {
3626                 if (oldi > next_out) {
3627                     SendToPlayer(&buf[next_out], oldi - next_out);
3628                 }
3629                 next_out = i;
3630                 started = STARTED_HOLDINGS;
3631                 parse_pos = 0;
3632                 continue;
3633             }
3634
3635             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3636                 loggedOn = TRUE;
3637                 /* Header for a move list -- first line */
3638
3639                 switch (ics_getting_history) {
3640                   case H_FALSE:
3641                     switch (gameMode) {
3642                       case IcsIdle:
3643                       case BeginningOfGame:
3644                         /* User typed "moves" or "oldmoves" while we
3645                            were idle.  Pretend we asked for these
3646                            moves and soak them up so user can step
3647                            through them and/or save them.
3648                            */
3649                         Reset(FALSE, TRUE);
3650                         gameMode = IcsObserving;
3651                         ModeHighlight();
3652                         ics_gamenum = -1;
3653                         ics_getting_history = H_GOT_UNREQ_HEADER;
3654                         break;
3655                       case EditGame: /*?*/
3656                       case EditPosition: /*?*/
3657                         /* Should above feature work in these modes too? */
3658                         /* For now it doesn't */
3659                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3660                         break;
3661                       default:
3662                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3663                         break;
3664                     }
3665                     break;
3666                   case H_REQUESTED:
3667                     /* Is this the right one? */
3668                     if (gameInfo.white && gameInfo.black &&
3669                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3670                         strcmp(gameInfo.black, star_match[2]) == 0) {
3671                         /* All is well */
3672                         ics_getting_history = H_GOT_REQ_HEADER;
3673                     }
3674                     break;
3675                   case H_GOT_REQ_HEADER:
3676                   case H_GOT_UNREQ_HEADER:
3677                   case H_GOT_UNWANTED_HEADER:
3678                   case H_GETTING_MOVES:
3679                     /* Should not happen */
3680                     DisplayError(_("Error gathering move list: two headers"), 0);
3681                     ics_getting_history = H_FALSE;
3682                     break;
3683                 }
3684
3685                 /* Save player ratings into gameInfo if needed */
3686                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3687                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3688                     (gameInfo.whiteRating == -1 ||
3689                      gameInfo.blackRating == -1)) {
3690
3691                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3692                     gameInfo.blackRating = string_to_rating(star_match[3]);
3693                     if (appData.debugMode)
3694                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3695                               gameInfo.whiteRating, gameInfo.blackRating);
3696                 }
3697                 continue;
3698             }
3699
3700             if (looking_at(buf, &i,
3701               "* * match, initial time: * minute*, increment: * second")) {
3702                 /* Header for a move list -- second line */
3703                 /* Initial board will follow if this is a wild game */
3704                 if (gameInfo.event != NULL) free(gameInfo.event);
3705                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3706                 gameInfo.event = StrSave(str);
3707                 /* [HGM] we switched variant. Translate boards if needed. */
3708                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3709                 continue;
3710             }
3711
3712             if (looking_at(buf, &i, "Move  ")) {
3713                 /* Beginning of a move list */
3714                 switch (ics_getting_history) {
3715                   case H_FALSE:
3716                     /* Normally should not happen */
3717                     /* Maybe user hit reset while we were parsing */
3718                     break;
3719                   case H_REQUESTED:
3720                     /* Happens if we are ignoring a move list that is not
3721                      * the one we just requested.  Common if the user
3722                      * tries to observe two games without turning off
3723                      * getMoveList */
3724                     break;
3725                   case H_GETTING_MOVES:
3726                     /* Should not happen */
3727                     DisplayError(_("Error gathering move list: nested"), 0);
3728                     ics_getting_history = H_FALSE;
3729                     break;
3730                   case H_GOT_REQ_HEADER:
3731                     ics_getting_history = H_GETTING_MOVES;
3732                     started = STARTED_MOVES;
3733                     parse_pos = 0;
3734                     if (oldi > next_out) {
3735                         SendToPlayer(&buf[next_out], oldi - next_out);
3736                     }
3737                     break;
3738                   case H_GOT_UNREQ_HEADER:
3739                     ics_getting_history = H_GETTING_MOVES;
3740                     started = STARTED_MOVES_NOHIDE;
3741                     parse_pos = 0;
3742                     break;
3743                   case H_GOT_UNWANTED_HEADER:
3744                     ics_getting_history = H_FALSE;
3745                     break;
3746                 }
3747                 continue;
3748             }
3749
3750             if (looking_at(buf, &i, "% ") ||
3751                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3752                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3753                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3754                     soughtPending = FALSE;
3755                     seekGraphUp = TRUE;
3756                     DrawSeekGraph();
3757                 }
3758                 if(suppressKibitz) next_out = i;
3759                 savingComment = FALSE;
3760                 suppressKibitz = 0;
3761                 switch (started) {
3762                   case STARTED_MOVES:
3763                   case STARTED_MOVES_NOHIDE:
3764                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3765                     parse[parse_pos + i - oldi] = NULLCHAR;
3766                     ParseGameHistory(parse);
3767 #if ZIPPY
3768                     if (appData.zippyPlay && first.initDone) {
3769                         FeedMovesToProgram(&first, forwardMostMove);
3770                         if (gameMode == IcsPlayingWhite) {
3771                             if (WhiteOnMove(forwardMostMove)) {
3772                                 if (first.sendTime) {
3773                                   if (first.useColors) {
3774                                     SendToProgram("black\n", &first);
3775                                   }
3776                                   SendTimeRemaining(&first, TRUE);
3777                                 }
3778                                 if (first.useColors) {
3779                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3780                                 }
3781                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3782                                 first.maybeThinking = TRUE;
3783                             } else {
3784                                 if (first.usePlayother) {
3785                                   if (first.sendTime) {
3786                                     SendTimeRemaining(&first, TRUE);
3787                                   }
3788                                   SendToProgram("playother\n", &first);
3789                                   firstMove = FALSE;
3790                                 } else {
3791                                   firstMove = TRUE;
3792                                 }
3793                             }
3794                         } else if (gameMode == IcsPlayingBlack) {
3795                             if (!WhiteOnMove(forwardMostMove)) {
3796                                 if (first.sendTime) {
3797                                   if (first.useColors) {
3798                                     SendToProgram("white\n", &first);
3799                                   }
3800                                   SendTimeRemaining(&first, FALSE);
3801                                 }
3802                                 if (first.useColors) {
3803                                   SendToProgram("black\n", &first);
3804                                 }
3805                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3806                                 first.maybeThinking = TRUE;
3807                             } else {
3808                                 if (first.usePlayother) {
3809                                   if (first.sendTime) {
3810                                     SendTimeRemaining(&first, FALSE);
3811                                   }
3812                                   SendToProgram("playother\n", &first);
3813                                   firstMove = FALSE;
3814                                 } else {
3815                                   firstMove = TRUE;
3816                                 }
3817                             }
3818                         }
3819                     }
3820 #endif
3821                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3822                         /* Moves came from oldmoves or moves command
3823                            while we weren't doing anything else.
3824                            */
3825                         currentMove = forwardMostMove;
3826                         ClearHighlights();/*!!could figure this out*/
3827                         flipView = appData.flipView;
3828                         DrawPosition(TRUE, boards[currentMove]);
3829                         DisplayBothClocks();
3830                         snprintf(str, MSG_SIZ, "%s %s %s",
3831                                 gameInfo.white, _("vs."),  gameInfo.black);
3832                         DisplayTitle(str);
3833                         gameMode = IcsIdle;
3834                     } else {
3835                         /* Moves were history of an active game */
3836                         if (gameInfo.resultDetails != NULL) {
3837                             free(gameInfo.resultDetails);
3838                             gameInfo.resultDetails = NULL;
3839                         }
3840                     }
3841                     HistorySet(parseList, backwardMostMove,
3842                                forwardMostMove, currentMove-1);
3843                     DisplayMove(currentMove - 1);
3844                     if (started == STARTED_MOVES) next_out = i;
3845                     started = STARTED_NONE;
3846                     ics_getting_history = H_FALSE;
3847                     break;
3848
3849                   case STARTED_OBSERVE:
3850                     started = STARTED_NONE;
3851                     SendToICS(ics_prefix);
3852                     SendToICS("refresh\n");
3853                     break;
3854
3855                   default:
3856                     break;
3857                 }
3858                 if(bookHit) { // [HGM] book: simulate book reply
3859                     static char bookMove[MSG_SIZ]; // a bit generous?
3860
3861                     programStats.nodes = programStats.depth = programStats.time =
3862                     programStats.score = programStats.got_only_move = 0;
3863                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3864
3865                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3866                     strcat(bookMove, bookHit);
3867                     HandleMachineMove(bookMove, &first);
3868                 }
3869                 continue;
3870             }
3871
3872             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3873                  started == STARTED_HOLDINGS ||
3874                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3875                 /* Accumulate characters in move list or board */
3876                 parse[parse_pos++] = buf[i];
3877             }
3878
3879             /* Start of game messages.  Mostly we detect start of game
3880                when the first board image arrives.  On some versions
3881                of the ICS, though, we need to do a "refresh" after starting
3882                to observe in order to get the current board right away. */
3883             if (looking_at(buf, &i, "Adding game * to observation list")) {
3884                 started = STARTED_OBSERVE;
3885                 continue;
3886             }
3887
3888             /* Handle auto-observe */
3889             if (appData.autoObserve &&
3890                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3891                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3892                 char *player;
3893                 /* Choose the player that was highlighted, if any. */
3894                 if (star_match[0][0] == '\033' ||
3895                     star_match[1][0] != '\033') {
3896                     player = star_match[0];
3897                 } else {
3898                     player = star_match[2];
3899                 }
3900                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3901                         ics_prefix, StripHighlightAndTitle(player));
3902                 SendToICS(str);
3903
3904                 /* Save ratings from notify string */
3905                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3906                 player1Rating = string_to_rating(star_match[1]);
3907                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3908                 player2Rating = string_to_rating(star_match[3]);
3909
3910                 if (appData.debugMode)
3911                   fprintf(debugFP,
3912                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3913                           player1Name, player1Rating,
3914                           player2Name, player2Rating);
3915
3916                 continue;
3917             }
3918
3919             /* Deal with automatic examine mode after a game,
3920                and with IcsObserving -> IcsExamining transition */
3921             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3922                 looking_at(buf, &i, "has made you an examiner of game *")) {
3923
3924                 int gamenum = atoi(star_match[0]);
3925                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3926                     gamenum == ics_gamenum) {
3927                     /* We were already playing or observing this game;
3928                        no need to refetch history */
3929                     gameMode = IcsExamining;
3930                     if (pausing) {
3931                         pauseExamForwardMostMove = forwardMostMove;
3932                     } else if (currentMove < forwardMostMove) {
3933                         ForwardInner(forwardMostMove);
3934                     }
3935                 } else {
3936                     /* I don't think this case really can happen */
3937                     SendToICS(ics_prefix);
3938                     SendToICS("refresh\n");
3939                 }
3940                 continue;
3941             }
3942
3943             /* Error messages */
3944 //          if (ics_user_moved) {
3945             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3946                 if (looking_at(buf, &i, "Illegal move") ||
3947                     looking_at(buf, &i, "Not a legal move") ||
3948                     looking_at(buf, &i, "Your king is in check") ||
3949                     looking_at(buf, &i, "It isn't your turn") ||
3950                     looking_at(buf, &i, "It is not your move")) {
3951                     /* Illegal move */
3952                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3953                         currentMove = forwardMostMove-1;
3954                         DisplayMove(currentMove - 1); /* before DMError */
3955                         DrawPosition(FALSE, boards[currentMove]);
3956                         SwitchClocks(forwardMostMove-1); // [HGM] race
3957                         DisplayBothClocks();
3958                     }
3959                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3960                     ics_user_moved = 0;
3961                     continue;
3962                 }
3963             }
3964
3965             if (looking_at(buf, &i, "still have time") ||
3966                 looking_at(buf, &i, "not out of time") ||
3967                 looking_at(buf, &i, "either player is out of time") ||
3968                 looking_at(buf, &i, "has timeseal; checking")) {
3969                 /* We must have called his flag a little too soon */
3970                 whiteFlag = blackFlag = FALSE;
3971                 continue;
3972             }
3973
3974             if (looking_at(buf, &i, "added * seconds to") ||
3975                 looking_at(buf, &i, "seconds were added to")) {
3976                 /* Update the clocks */
3977                 SendToICS(ics_prefix);
3978                 SendToICS("refresh\n");
3979                 continue;
3980             }
3981
3982             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3983                 ics_clock_paused = TRUE;
3984                 StopClocks();
3985                 continue;
3986             }
3987
3988             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3989                 ics_clock_paused = FALSE;
3990                 StartClocks();
3991                 continue;
3992             }
3993
3994             /* Grab player ratings from the Creating: message.
3995                Note we have to check for the special case when
3996                the ICS inserts things like [white] or [black]. */
3997             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3998                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3999                 /* star_matches:
4000                    0    player 1 name (not necessarily white)
4001                    1    player 1 rating
4002                    2    empty, white, or black (IGNORED)
4003                    3    player 2 name (not necessarily black)
4004                    4    player 2 rating
4005
4006                    The names/ratings are sorted out when the game
4007                    actually starts (below).
4008                 */
4009                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4010                 player1Rating = string_to_rating(star_match[1]);
4011                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4012                 player2Rating = string_to_rating(star_match[4]);
4013
4014                 if (appData.debugMode)
4015                   fprintf(debugFP,
4016                           "Ratings from 'Creating:' %s %d, %s %d\n",
4017                           player1Name, player1Rating,
4018                           player2Name, player2Rating);
4019
4020                 continue;
4021             }
4022
4023             /* Improved generic start/end-of-game messages */
4024             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4025                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4026                 /* If tkind == 0: */
4027                 /* star_match[0] is the game number */
4028                 /*           [1] is the white player's name */
4029                 /*           [2] is the black player's name */
4030                 /* For end-of-game: */
4031                 /*           [3] is the reason for the game end */
4032                 /*           [4] is a PGN end game-token, preceded by " " */
4033                 /* For start-of-game: */
4034                 /*           [3] begins with "Creating" or "Continuing" */
4035                 /*           [4] is " *" or empty (don't care). */
4036                 int gamenum = atoi(star_match[0]);
4037                 char *whitename, *blackname, *why, *endtoken;
4038                 ChessMove endtype = EndOfFile;
4039
4040                 if (tkind == 0) {
4041                   whitename = star_match[1];
4042                   blackname = star_match[2];
4043                   why = star_match[3];
4044                   endtoken = star_match[4];
4045                 } else {
4046                   whitename = star_match[1];
4047                   blackname = star_match[3];
4048                   why = star_match[5];
4049                   endtoken = star_match[6];
4050                 }
4051
4052                 /* Game start messages */
4053                 if (strncmp(why, "Creating ", 9) == 0 ||
4054                     strncmp(why, "Continuing ", 11) == 0) {
4055                     gs_gamenum = gamenum;
4056                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4057                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4058                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4059 #if ZIPPY
4060                     if (appData.zippyPlay) {
4061                         ZippyGameStart(whitename, blackname);
4062                     }
4063 #endif /*ZIPPY*/
4064                     partnerBoardValid = FALSE; // [HGM] bughouse
4065                     continue;
4066                 }
4067
4068                 /* Game end messages */
4069                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4070                     ics_gamenum != gamenum) {
4071                     continue;
4072                 }
4073                 while (endtoken[0] == ' ') endtoken++;
4074                 switch (endtoken[0]) {
4075                   case '*':
4076                   default:
4077                     endtype = GameUnfinished;
4078                     break;
4079                   case '0':
4080                     endtype = BlackWins;
4081                     break;
4082                   case '1':
4083                     if (endtoken[1] == '/')
4084                       endtype = GameIsDrawn;
4085                     else
4086                       endtype = WhiteWins;
4087                     break;
4088                 }
4089                 GameEnds(endtype, why, GE_ICS);
4090 #if ZIPPY
4091                 if (appData.zippyPlay && first.initDone) {
4092                     ZippyGameEnd(endtype, why);
4093                     if (first.pr == NoProc) {
4094                       /* Start the next process early so that we'll
4095                          be ready for the next challenge */
4096                       StartChessProgram(&first);
4097                     }
4098                     /* Send "new" early, in case this command takes
4099                        a long time to finish, so that we'll be ready
4100                        for the next challenge. */
4101                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4102                     Reset(TRUE, TRUE);
4103                 }
4104 #endif /*ZIPPY*/
4105                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4106                 continue;
4107             }
4108
4109             if (looking_at(buf, &i, "Removing game * from observation") ||
4110                 looking_at(buf, &i, "no longer observing game *") ||
4111                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4112                 if (gameMode == IcsObserving &&
4113                     atoi(star_match[0]) == ics_gamenum)
4114                   {
4115                       /* icsEngineAnalyze */
4116                       if (appData.icsEngineAnalyze) {
4117                             ExitAnalyzeMode();
4118                             ModeHighlight();
4119                       }
4120                       StopClocks();
4121                       gameMode = IcsIdle;
4122                       ics_gamenum = -1;
4123                       ics_user_moved = FALSE;
4124                   }
4125                 continue;
4126             }
4127
4128             if (looking_at(buf, &i, "no longer examining game *")) {
4129                 if (gameMode == IcsExamining &&
4130                     atoi(star_match[0]) == ics_gamenum)
4131                   {
4132                       gameMode = IcsIdle;
4133                       ics_gamenum = -1;
4134                       ics_user_moved = FALSE;
4135                   }
4136                 continue;
4137             }
4138
4139             /* Advance leftover_start past any newlines we find,
4140                so only partial lines can get reparsed */
4141             if (looking_at(buf, &i, "\n")) {
4142                 prevColor = curColor;
4143                 if (curColor != ColorNormal) {
4144                     if (oldi > next_out) {
4145                         SendToPlayer(&buf[next_out], oldi - next_out);
4146                         next_out = oldi;
4147                     }
4148                     Colorize(ColorNormal, FALSE);
4149                     curColor = ColorNormal;
4150                 }
4151                 if (started == STARTED_BOARD) {
4152                     started = STARTED_NONE;
4153                     parse[parse_pos] = NULLCHAR;
4154                     ParseBoard12(parse);
4155                     ics_user_moved = 0;
4156
4157                     /* Send premove here */
4158                     if (appData.premove) {
4159                       char str[MSG_SIZ];
4160                       if (currentMove == 0 &&
4161                           gameMode == IcsPlayingWhite &&
4162                           appData.premoveWhite) {
4163                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4164                         if (appData.debugMode)
4165                           fprintf(debugFP, "Sending premove:\n");
4166                         SendToICS(str);
4167                       } else if (currentMove == 1 &&
4168                                  gameMode == IcsPlayingBlack &&
4169                                  appData.premoveBlack) {
4170                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4171                         if (appData.debugMode)
4172                           fprintf(debugFP, "Sending premove:\n");
4173                         SendToICS(str);
4174                       } else if (gotPremove) {
4175                         int oldFMM = forwardMostMove;
4176                         gotPremove = 0;
4177                         ClearPremoveHighlights();
4178                         if (appData.debugMode)
4179                           fprintf(debugFP, "Sending premove:\n");
4180                           UserMoveEvent(premoveFromX, premoveFromY,
4181                                         premoveToX, premoveToY,
4182                                         premovePromoChar);
4183                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4184                           if(moveList[oldFMM-1][1] != '@')
4185                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4186                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4187                           else // (drop)
4188                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4189                         }
4190                       }
4191                     }
4192
4193                     /* Usually suppress following prompt */
4194                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4195                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4196                         if (looking_at(buf, &i, "*% ")) {
4197                             savingComment = FALSE;
4198                             suppressKibitz = 0;
4199                         }
4200                     }
4201                     next_out = i;
4202                 } else if (started == STARTED_HOLDINGS) {
4203                     int gamenum;
4204                     char new_piece[MSG_SIZ];
4205                     started = STARTED_NONE;
4206                     parse[parse_pos] = NULLCHAR;
4207                     if (appData.debugMode)
4208                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4209                                                         parse, currentMove);
4210                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4211                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4212                         new_piece[0] = NULLCHAR;
4213                         sscanf(parse, "game %d white [%s black [%s <- %s",
4214                                &gamenum, white_holding, black_holding,
4215                                new_piece);
4216                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4217                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4218                         if (gameInfo.variant == VariantNormal) {
4219                           /* [HGM] We seem to switch variant during a game!
4220                            * Presumably no holdings were displayed, so we have
4221                            * to move the position two files to the right to
4222                            * create room for them!
4223                            */
4224                           VariantClass newVariant;
4225                           switch(gameInfo.boardWidth) { // base guess on board width
4226                                 case 9:  newVariant = VariantShogi; break;
4227                                 case 10: newVariant = VariantGreat; break;
4228                                 default: newVariant = VariantCrazyhouse;
4229                                      if(strchr(white_holding, 'E') || strchr(black_holding, 'E') || 
4230                                         strchr(white_holding, 'H') || strchr(black_holding, 'H')   )
4231                                          newVariant = VariantSChess;
4232                           }
4233                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4234                           /* Get a move list just to see the header, which
4235                              will tell us whether this is really bug or zh */
4236                           if (ics_getting_history == H_FALSE) {
4237                             ics_getting_history = H_REQUESTED;
4238                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4239                             SendToICS(str);
4240                           }
4241                         }
4242                         /* [HGM] copy holdings to board holdings area */
4243                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4244                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4245                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4246 #if ZIPPY
4247                         if (appData.zippyPlay && first.initDone) {
4248                             ZippyHoldings(white_holding, black_holding,
4249                                           new_piece);
4250                         }
4251 #endif /*ZIPPY*/
4252                         if (tinyLayout || smallLayout) {
4253                             char wh[16], bh[16];
4254                             PackHolding(wh, white_holding);
4255                             PackHolding(bh, black_holding);
4256                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4257                                     gameInfo.white, gameInfo.black);
4258                         } else {
4259                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4260                                     gameInfo.white, white_holding, _("vs."),
4261                                     gameInfo.black, black_holding);
4262                         }
4263                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4264                         DrawPosition(FALSE, boards[currentMove]);
4265                         DisplayTitle(str);
4266                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4267                         sscanf(parse, "game %d white [%s black [%s <- %s",
4268                                &gamenum, white_holding, black_holding,
4269                                new_piece);
4270                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4271                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4272                         /* [HGM] copy holdings to partner-board holdings area */
4273                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4274                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4275                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4276                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4277                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4278                       }
4279                     }
4280                     /* Suppress following prompt */
4281                     if (looking_at(buf, &i, "*% ")) {
4282                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4283                         savingComment = FALSE;
4284                         suppressKibitz = 0;
4285                     }
4286                     next_out = i;
4287                 }
4288                 continue;
4289             }
4290
4291             i++;                /* skip unparsed character and loop back */
4292         }
4293
4294         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4295 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4296 //          SendToPlayer(&buf[next_out], i - next_out);
4297             started != STARTED_HOLDINGS && leftover_start > next_out) {
4298             SendToPlayer(&buf[next_out], leftover_start - next_out);
4299             next_out = i;
4300         }
4301
4302         leftover_len = buf_len - leftover_start;
4303         /* if buffer ends with something we couldn't parse,
4304            reparse it after appending the next read */
4305
4306     } else if (count == 0) {
4307         RemoveInputSource(isr);
4308         DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4309     } else {
4310         DisplayFatalError(_("Error reading from ICS"), error, 1);
4311     }
4312 }
4313
4314
4315 /* Board style 12 looks like this:
4316
4317    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4318
4319  * The "<12> " is stripped before it gets to this routine.  The two
4320  * trailing 0's (flip state and clock ticking) are later addition, and
4321  * some chess servers may not have them, or may have only the first.
4322  * Additional trailing fields may be added in the future.
4323  */
4324
4325 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4326
4327 #define RELATION_OBSERVING_PLAYED    0
4328 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4329 #define RELATION_PLAYING_MYMOVE      1
4330 #define RELATION_PLAYING_NOTMYMOVE  -1
4331 #define RELATION_EXAMINING           2
4332 #define RELATION_ISOLATED_BOARD     -3
4333 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4334
4335 void
4336 ParseBoard12 (char *string)
4337 {
4338 #if ZIPPY
4339     int i, takeback;
4340     char *bookHit = NULL; // [HGM] book
4341 #endif
4342     GameMode newGameMode;
4343     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4344     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4345     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4346     char to_play, board_chars[200];
4347     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4348     char black[32], white[32];
4349     Board board;
4350     int prevMove = currentMove;
4351     int ticking = 2;
4352     ChessMove moveType;
4353     int fromX, fromY, toX, toY;
4354     char promoChar;
4355     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4356     Boolean weird = FALSE, reqFlag = FALSE;
4357
4358     fromX = fromY = toX = toY = -1;
4359
4360     newGame = FALSE;
4361
4362     if (appData.debugMode)
4363       fprintf(debugFP, "Parsing board: %s\n", string);
4364
4365     move_str[0] = NULLCHAR;
4366     elapsed_time[0] = NULLCHAR;
4367     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4368         int  i = 0, j;
4369         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4370             if(string[i] == ' ') { ranks++; files = 0; }
4371             else files++;
4372             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4373             i++;
4374         }
4375         for(j = 0; j <i; j++) board_chars[j] = string[j];
4376         board_chars[i] = '\0';
4377         string += i + 1;
4378     }
4379     n = sscanf(string, PATTERN, &to_play, &double_push,
4380                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4381                &gamenum, white, black, &relation, &basetime, &increment,
4382                &white_stren, &black_stren, &white_time, &black_time,
4383                &moveNum, str, elapsed_time, move_str, &ics_flip,
4384                &ticking);
4385
4386     if (n < 21) {
4387         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4388         DisplayError(str, 0);
4389         return;
4390     }
4391
4392     /* Convert the move number to internal form */
4393     moveNum = (moveNum - 1) * 2;
4394     if (to_play == 'B') moveNum++;
4395     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4396       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4397                         0, 1);
4398       return;
4399     }
4400
4401     switch (relation) {
4402       case RELATION_OBSERVING_PLAYED:
4403       case RELATION_OBSERVING_STATIC:
4404         if (gamenum == -1) {
4405             /* Old ICC buglet */
4406             relation = RELATION_OBSERVING_STATIC;
4407         }
4408         newGameMode = IcsObserving;
4409         break;
4410       case RELATION_PLAYING_MYMOVE:
4411       case RELATION_PLAYING_NOTMYMOVE:
4412         newGameMode =
4413           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4414             IcsPlayingWhite : IcsPlayingBlack;
4415         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4416         break;
4417       case RELATION_EXAMINING:
4418         newGameMode = IcsExamining;
4419         break;
4420       case RELATION_ISOLATED_BOARD:
4421       default:
4422         /* Just display this board.  If user was doing something else,
4423            we will forget about it until the next board comes. */
4424         newGameMode = IcsIdle;
4425         break;
4426       case RELATION_STARTING_POSITION:
4427         newGameMode = gameMode;
4428         break;
4429     }
4430
4431     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4432         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4433          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4434       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4435       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4436       static int lastBgGame = -1;
4437       char *toSqr;
4438       for (k = 0; k < ranks; k++) {
4439         for (j = 0; j < files; j++)
4440           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4441         if(gameInfo.holdingsWidth > 1) {
4442              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4443              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4444         }
4445       }
4446       CopyBoard(partnerBoard, board);
4447       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4448         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4449         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4450       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4451       if(toSqr = strchr(str, '-')) {
4452         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4453         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4454       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4455       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4456       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4457       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4458       if(twoBoards) {
4459           DisplayWhiteClock(white_time*fac, to_play == 'W');
4460           DisplayBlackClock(black_time*fac, to_play != 'W');
4461           activePartner = to_play;
4462           if(gamenum != lastBgGame) {
4463               char buf[MSG_SIZ];
4464               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4465               DisplayTitle(buf);
4466           }
4467           lastBgGame = gamenum;
4468           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4469                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4470       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4471                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4472       if(!twoBoards) DisplayMessage(partnerStatus, "");
4473         partnerBoardValid = TRUE;
4474       return;
4475     }
4476
4477     if(appData.dualBoard && appData.bgObserve) {
4478         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4479             SendToICS(ics_prefix), SendToICS("pobserve\n");
4480         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4481             char buf[MSG_SIZ];
4482             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4483             SendToICS(buf);
4484         }
4485     }
4486
4487     /* Modify behavior for initial board display on move listing
4488        of wild games.
4489        */
4490     switch (ics_getting_history) {
4491       case H_FALSE:
4492       case H_REQUESTED:
4493         break;
4494       case H_GOT_REQ_HEADER:
4495       case H_GOT_UNREQ_HEADER:
4496         /* This is the initial position of the current game */
4497         gamenum = ics_gamenum;
4498         moveNum = 0;            /* old ICS bug workaround */
4499         if (to_play == 'B') {
4500           startedFromSetupPosition = TRUE;
4501           blackPlaysFirst = TRUE;
4502           moveNum = 1;
4503           if (forwardMostMove == 0) forwardMostMove = 1;
4504           if (backwardMostMove == 0) backwardMostMove = 1;
4505           if (currentMove == 0) currentMove = 1;
4506         }
4507         newGameMode = gameMode;
4508         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4509         break;
4510       case H_GOT_UNWANTED_HEADER:
4511         /* This is an initial board that we don't want */
4512         return;
4513       case H_GETTING_MOVES:
4514         /* Should not happen */
4515         DisplayError(_("Error gathering move list: extra board"), 0);
4516         ics_getting_history = H_FALSE;
4517         return;
4518     }
4519
4520    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4521                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4522                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4523      /* [HGM] We seem to have switched variant unexpectedly
4524       * Try to guess new variant from board size
4525       */
4526           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4527           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4528           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4529           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4530           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4531           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4532           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4533           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4534           /* Get a move list just to see the header, which
4535              will tell us whether this is really bug or zh */
4536           if (ics_getting_history == H_FALSE) {
4537             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4538             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4539             SendToICS(str);
4540           }
4541     }
4542
4543     /* Take action if this is the first board of a new game, or of a
4544        different game than is currently being displayed.  */
4545     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4546         relation == RELATION_ISOLATED_BOARD) {
4547
4548         /* Forget the old game and get the history (if any) of the new one */
4549         if (gameMode != BeginningOfGame) {
4550           Reset(TRUE, TRUE);
4551         }
4552         newGame = TRUE;
4553         if (appData.autoRaiseBoard) BoardToTop();
4554         prevMove = -3;
4555         if (gamenum == -1) {
4556             newGameMode = IcsIdle;
4557         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4558                    appData.getMoveList && !reqFlag) {
4559             /* Need to get game history */
4560             ics_getting_history = H_REQUESTED;
4561             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4562             SendToICS(str);
4563         }
4564
4565         /* Initially flip the board to have black on the bottom if playing
4566            black or if the ICS flip flag is set, but let the user change
4567            it with the Flip View button. */
4568         flipView = appData.autoFlipView ?
4569           (newGameMode == IcsPlayingBlack) || ics_flip :
4570           appData.flipView;
4571
4572         /* Done with values from previous mode; copy in new ones */
4573         gameMode = newGameMode;
4574         ModeHighlight();
4575         ics_gamenum = gamenum;
4576         if (gamenum == gs_gamenum) {
4577             int klen = strlen(gs_kind);
4578             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4579             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4580             gameInfo.event = StrSave(str);
4581         } else {
4582             gameInfo.event = StrSave("ICS game");
4583         }
4584         gameInfo.site = StrSave(appData.icsHost);
4585         gameInfo.date = PGNDate();
4586         gameInfo.round = StrSave("-");
4587         gameInfo.white = StrSave(white);
4588         gameInfo.black = StrSave(black);
4589         timeControl = basetime * 60 * 1000;
4590         timeControl_2 = 0;
4591         timeIncrement = increment * 1000;
4592         movesPerSession = 0;
4593         gameInfo.timeControl = TimeControlTagValue();
4594         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4595   if (appData.debugMode) {
4596     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4597     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4598     setbuf(debugFP, NULL);
4599   }
4600
4601         gameInfo.outOfBook = NULL;
4602
4603         /* Do we have the ratings? */
4604         if (strcmp(player1Name, white) == 0 &&
4605             strcmp(player2Name, black) == 0) {
4606             if (appData.debugMode)
4607               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4608                       player1Rating, player2Rating);
4609             gameInfo.whiteRating = player1Rating;
4610             gameInfo.blackRating = player2Rating;
4611         } else if (strcmp(player2Name, white) == 0 &&
4612                    strcmp(player1Name, black) == 0) {
4613             if (appData.debugMode)
4614               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4615                       player2Rating, player1Rating);
4616             gameInfo.whiteRating = player2Rating;
4617             gameInfo.blackRating = player1Rating;
4618         }
4619         player1Name[0] = player2Name[0] = NULLCHAR;
4620
4621         /* Silence shouts if requested */
4622         if (appData.quietPlay &&
4623             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4624             SendToICS(ics_prefix);
4625             SendToICS("set shout 0\n");
4626         }
4627     }
4628
4629     /* Deal with midgame name changes */
4630     if (!newGame) {
4631         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4632             if (gameInfo.white) free(gameInfo.white);
4633             gameInfo.white = StrSave(white);
4634         }
4635         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4636             if (gameInfo.black) free(gameInfo.black);
4637             gameInfo.black = StrSave(black);
4638         }
4639     }
4640
4641     /* Throw away game result if anything actually changes in examine mode */
4642     if (gameMode == IcsExamining && !newGame) {
4643         gameInfo.result = GameUnfinished;
4644         if (gameInfo.resultDetails != NULL) {
4645             free(gameInfo.resultDetails);
4646             gameInfo.resultDetails = NULL;
4647         }
4648     }
4649
4650     /* In pausing && IcsExamining mode, we ignore boards coming
4651        in if they are in a different variation than we are. */
4652     if (pauseExamInvalid) return;
4653     if (pausing && gameMode == IcsExamining) {
4654         if (moveNum <= pauseExamForwardMostMove) {
4655             pauseExamInvalid = TRUE;
4656             forwardMostMove = pauseExamForwardMostMove;
4657             return;
4658         }
4659     }
4660
4661   if (appData.debugMode) {
4662     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4663   }
4664     /* Parse the board */
4665     for (k = 0; k < ranks; k++) {
4666       for (j = 0; j < files; j++)
4667         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4668       if(gameInfo.holdingsWidth > 1) {
4669            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4670            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4671       }
4672     }
4673     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4674       board[5][BOARD_RGHT+1] = WhiteAngel;
4675       board[6][BOARD_RGHT+1] = WhiteMarshall;
4676       board[1][0] = BlackMarshall;
4677       board[2][0] = BlackAngel;
4678       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4679     }
4680     CopyBoard(boards[moveNum], board);
4681     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4682     if (moveNum == 0) {
4683         startedFromSetupPosition =
4684           !CompareBoards(board, initialPosition);
4685         if(startedFromSetupPosition)
4686             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4687     }
4688
4689     /* [HGM] Set castling rights. Take the outermost Rooks,
4690        to make it also work for FRC opening positions. Note that board12
4691        is really defective for later FRC positions, as it has no way to
4692        indicate which Rook can castle if they are on the same side of King.
4693        For the initial position we grant rights to the outermost Rooks,
4694        and remember thos rights, and we then copy them on positions
4695        later in an FRC game. This means WB might not recognize castlings with
4696        Rooks that have moved back to their original position as illegal,
4697        but in ICS mode that is not its job anyway.
4698     */
4699     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4700     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4701
4702         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4703             if(board[0][i] == WhiteRook) j = i;
4704         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4705         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4706             if(board[0][i] == WhiteRook) j = i;
4707         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4708         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4709             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4710         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4711         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4712             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4713         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4714
4715         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4716         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4717         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4718             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4719         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4720             if(board[BOARD_HEIGHT-1][k] == bKing)
4721                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4722         if(gameInfo.variant == VariantTwoKings) {
4723             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4724             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4725             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4726         }
4727     } else { int r;
4728         r = boards[moveNum][CASTLING][0] = initialRights[0];
4729         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4730         r = boards[moveNum][CASTLING][1] = initialRights[1];
4731         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4732         r = boards[moveNum][CASTLING][3] = initialRights[3];
4733         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4734         r = boards[moveNum][CASTLING][4] = initialRights[4];
4735         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4736         /* wildcastle kludge: always assume King has rights */
4737         r = boards[moveNum][CASTLING][2] = initialRights[2];
4738         r = boards[moveNum][CASTLING][5] = initialRights[5];
4739     }
4740     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4741     boards[moveNum][EP_STATUS] = EP_NONE;
4742     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4743     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4744     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4745
4746
4747     if (ics_getting_history == H_GOT_REQ_HEADER ||
4748         ics_getting_history == H_GOT_UNREQ_HEADER) {
4749         /* This was an initial position from a move list, not
4750            the current position */
4751         return;
4752     }
4753
4754     /* Update currentMove and known move number limits */
4755     newMove = newGame || moveNum > forwardMostMove;
4756
4757     if (newGame) {
4758         forwardMostMove = backwardMostMove = currentMove = moveNum;
4759         if (gameMode == IcsExamining && moveNum == 0) {
4760           /* Workaround for ICS limitation: we are not told the wild
4761              type when starting to examine a game.  But if we ask for
4762              the move list, the move list header will tell us */
4763             ics_getting_history = H_REQUESTED;
4764             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4765             SendToICS(str);
4766         }
4767     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4768                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4769 #if ZIPPY
4770         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4771         /* [HGM] applied this also to an engine that is silently watching        */
4772         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4773             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4774             gameInfo.variant == currentlyInitializedVariant) {
4775           takeback = forwardMostMove - moveNum;
4776           for (i = 0; i < takeback; i++) {
4777             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4778             SendToProgram("undo\n", &first);
4779           }
4780         }
4781 #endif
4782
4783         forwardMostMove = moveNum;
4784         if (!pausing || currentMove > forwardMostMove)
4785           currentMove = forwardMostMove;
4786     } else {
4787         /* New part of history that is not contiguous with old part */
4788         if (pausing && gameMode == IcsExamining) {
4789             pauseExamInvalid = TRUE;
4790             forwardMostMove = pauseExamForwardMostMove;
4791             return;
4792         }
4793         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4794 #if ZIPPY
4795             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4796                 // [HGM] when we will receive the move list we now request, it will be
4797                 // fed to the engine from the first move on. So if the engine is not
4798                 // in the initial position now, bring it there.
4799                 InitChessProgram(&first, 0);
4800             }
4801 #endif
4802             ics_getting_history = H_REQUESTED;
4803             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4804             SendToICS(str);
4805         }
4806         forwardMostMove = backwardMostMove = currentMove = moveNum;
4807     }
4808
4809     /* Update the clocks */
4810     if (strchr(elapsed_time, '.')) {
4811       /* Time is in ms */
4812       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4813       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4814     } else {
4815       /* Time is in seconds */
4816       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4817       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4818     }
4819
4820
4821 #if ZIPPY
4822     if (appData.zippyPlay && newGame &&
4823         gameMode != IcsObserving && gameMode != IcsIdle &&
4824         gameMode != IcsExamining)
4825       ZippyFirstBoard(moveNum, basetime, increment);
4826 #endif
4827
4828     /* Put the move on the move list, first converting
4829        to canonical algebraic form. */
4830     if (moveNum > 0) {
4831   if (appData.debugMode) {
4832     int f = forwardMostMove;
4833     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4834             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4835             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4836     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4837     fprintf(debugFP, "moveNum = %d\n", moveNum);
4838     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4839     setbuf(debugFP, NULL);
4840   }
4841         if (moveNum <= backwardMostMove) {
4842             /* We don't know what the board looked like before
4843                this move.  Punt. */
4844           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4845             strcat(parseList[moveNum - 1], " ");
4846             strcat(parseList[moveNum - 1], elapsed_time);
4847             moveList[moveNum - 1][0] = NULLCHAR;
4848         } else if (strcmp(move_str, "none") == 0) {
4849             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4850             /* Again, we don't know what the board looked like;
4851                this is really the start of the game. */
4852             parseList[moveNum - 1][0] = NULLCHAR;
4853             moveList[moveNum - 1][0] = NULLCHAR;
4854             backwardMostMove = moveNum;
4855             startedFromSetupPosition = TRUE;
4856             fromX = fromY = toX = toY = -1;
4857         } else {
4858           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4859           //                 So we parse the long-algebraic move string in stead of the SAN move
4860           int valid; char buf[MSG_SIZ], *prom;
4861
4862           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4863                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4864           // str looks something like "Q/a1-a2"; kill the slash
4865           if(str[1] == '/')
4866             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4867           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4868           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4869                 strcat(buf, prom); // long move lacks promo specification!
4870           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4871                 if(appData.debugMode)
4872                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4873                 safeStrCpy(move_str, buf, MSG_SIZ);
4874           }
4875           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4876                                 &fromX, &fromY, &toX, &toY, &promoChar)
4877                || ParseOneMove(buf, moveNum - 1, &moveType,
4878                                 &fromX, &fromY, &toX, &toY, &promoChar);
4879           // end of long SAN patch
4880           if (valid) {
4881             (void) CoordsToAlgebraic(boards[moveNum - 1],
4882                                      PosFlags(moveNum - 1),
4883                                      fromY, fromX, toY, toX, promoChar,
4884                                      parseList[moveNum-1]);
4885             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4886               case MT_NONE:
4887               case MT_STALEMATE:
4888               default:
4889                 break;
4890               case MT_CHECK:
4891                 if(!IS_SHOGI(gameInfo.variant))
4892                     strcat(parseList[moveNum - 1], "+");
4893                 break;
4894               case MT_CHECKMATE:
4895               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4896                 strcat(parseList[moveNum - 1], "#");
4897                 break;
4898             }
4899             strcat(parseList[moveNum - 1], " ");
4900             strcat(parseList[moveNum - 1], elapsed_time);
4901             /* currentMoveString is set as a side-effect of ParseOneMove */
4902             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4903             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4904             strcat(moveList[moveNum - 1], "\n");
4905
4906             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4907                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4908               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4909                 ChessSquare old, new = boards[moveNum][k][j];
4910                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4911                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4912                   if(old == new) continue;
4913                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4914                   else if(new == WhiteWazir || new == BlackWazir) {
4915                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4916                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4917                       else boards[moveNum][k][j] = old; // preserve type of Gold
4918                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4919                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4920               }
4921           } else {
4922             /* Move from ICS was illegal!?  Punt. */
4923             if (appData.debugMode) {
4924               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4925               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4926             }
4927             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4928             strcat(parseList[moveNum - 1], " ");
4929             strcat(parseList[moveNum - 1], elapsed_time);
4930             moveList[moveNum - 1][0] = NULLCHAR;
4931             fromX = fromY = toX = toY = -1;
4932           }
4933         }
4934   if (appData.debugMode) {
4935     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4936     setbuf(debugFP, NULL);
4937   }
4938
4939 #if ZIPPY
4940         /* Send move to chess program (BEFORE animating it). */
4941         if (appData.zippyPlay && !newGame && newMove &&
4942            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4943
4944             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4945                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4946                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4947                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4948                             move_str);
4949                     DisplayError(str, 0);
4950                 } else {
4951                     if (first.sendTime) {
4952                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4953                     }
4954                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4955                     if (firstMove && !bookHit) {
4956                         firstMove = FALSE;
4957                         if (first.useColors) {
4958                           SendToProgram(gameMode == IcsPlayingWhite ?
4959                                         "white\ngo\n" :
4960                                         "black\ngo\n", &first);
4961                         } else {
4962                           SendToProgram("go\n", &first);
4963                         }
4964                         first.maybeThinking = TRUE;
4965                     }
4966                 }
4967             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4968               if (moveList[moveNum - 1][0] == NULLCHAR) {
4969                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4970                 DisplayError(str, 0);
4971               } else {
4972                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4973                 SendMoveToProgram(moveNum - 1, &first);
4974               }
4975             }
4976         }
4977 #endif
4978     }
4979
4980     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4981         /* If move comes from a remote source, animate it.  If it
4982            isn't remote, it will have already been animated. */
4983         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4984             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4985         }
4986         if (!pausing && appData.highlightLastMove) {
4987             SetHighlights(fromX, fromY, toX, toY);
4988         }
4989     }
4990
4991     /* Start the clocks */
4992     whiteFlag = blackFlag = FALSE;
4993     appData.clockMode = !(basetime == 0 && increment == 0);
4994     if (ticking == 0) {
4995       ics_clock_paused = TRUE;
4996       StopClocks();
4997     } else if (ticking == 1) {
4998       ics_clock_paused = FALSE;
4999     }
5000     if (gameMode == IcsIdle ||
5001         relation == RELATION_OBSERVING_STATIC ||
5002         relation == RELATION_EXAMINING ||
5003         ics_clock_paused)
5004       DisplayBothClocks();
5005     else
5006       StartClocks();
5007
5008     /* Display opponents and material strengths */
5009     if (gameInfo.variant != VariantBughouse &&
5010         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5011         if (tinyLayout || smallLayout) {
5012             if(gameInfo.variant == VariantNormal)
5013               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5014                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5015                     basetime, increment);
5016             else
5017               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5018                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5019                     basetime, increment, (int) gameInfo.variant);
5020         } else {
5021             if(gameInfo.variant == VariantNormal)
5022               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5023                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5024                     basetime, increment);
5025             else
5026               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5027                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5028                     basetime, increment, VariantName(gameInfo.variant));
5029         }
5030         DisplayTitle(str);
5031   if (appData.debugMode) {
5032     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5033   }
5034     }
5035
5036
5037     /* Display the board */
5038     if (!pausing && !appData.noGUI) {
5039
5040       if (appData.premove)
5041           if (!gotPremove ||
5042              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5043              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5044               ClearPremoveHighlights();
5045
5046       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5047         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5048       DrawPosition(j, boards[currentMove]);
5049
5050       DisplayMove(moveNum - 1);
5051       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5052             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5053               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5054         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5055       }
5056     }
5057
5058     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5059 #if ZIPPY
5060     if(bookHit) { // [HGM] book: simulate book reply
5061         static char bookMove[MSG_SIZ]; // a bit generous?
5062
5063         programStats.nodes = programStats.depth = programStats.time =
5064         programStats.score = programStats.got_only_move = 0;
5065         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5066
5067         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5068         strcat(bookMove, bookHit);
5069         HandleMachineMove(bookMove, &first);
5070     }
5071 #endif
5072 }
5073
5074 void
5075 GetMoveListEvent ()
5076 {
5077     char buf[MSG_SIZ];
5078     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5079         ics_getting_history = H_REQUESTED;
5080         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5081         SendToICS(buf);
5082     }
5083 }
5084
5085 void
5086 SendToBoth (char *msg)
5087 {   // to make it easy to keep two engines in step in dual analysis
5088     SendToProgram(msg, &first);
5089     if(second.analyzing) SendToProgram(msg, &second);
5090 }
5091
5092 void
5093 AnalysisPeriodicEvent (int force)
5094 {
5095     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5096          && !force) || !appData.periodicUpdates)
5097       return;
5098
5099     /* Send . command to Crafty to collect stats */
5100     SendToBoth(".\n");
5101
5102     /* Don't send another until we get a response (this makes
5103        us stop sending to old Crafty's which don't understand
5104        the "." command (sending illegal cmds resets node count & time,
5105        which looks bad)) */
5106     programStats.ok_to_send = 0;
5107 }
5108
5109 void
5110 ics_update_width (int new_width)
5111 {
5112         ics_printf("set width %d\n", new_width);
5113 }
5114
5115 void
5116 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5117 {
5118     char buf[MSG_SIZ];
5119
5120     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5121         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5122             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5123             SendToProgram(buf, cps);
5124             return;
5125         }
5126         // null move in variant where engine does not understand it (for analysis purposes)
5127         SendBoard(cps, moveNum + 1); // send position after move in stead.
5128         return;
5129     }
5130     if (cps->useUsermove) {
5131       SendToProgram("usermove ", cps);
5132     }
5133     if (cps->useSAN) {
5134       char *space;
5135       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5136         int len = space - parseList[moveNum];
5137         memcpy(buf, parseList[moveNum], len);
5138         buf[len++] = '\n';
5139         buf[len] = NULLCHAR;
5140       } else {
5141         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5142       }
5143       SendToProgram(buf, cps);
5144     } else {
5145       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5146         AlphaRank(moveList[moveNum], 4);
5147         SendToProgram(moveList[moveNum], cps);
5148         AlphaRank(moveList[moveNum], 4); // and back
5149       } else
5150       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5151        * the engine. It would be nice to have a better way to identify castle
5152        * moves here. */
5153       if(appData.fischerCastling && cps->useOOCastle) {
5154         int fromX = moveList[moveNum][0] - AAA;
5155         int fromY = moveList[moveNum][1] - ONE;
5156         int toX = moveList[moveNum][2] - AAA;
5157         int toY = moveList[moveNum][3] - ONE;
5158         if((boards[moveNum][fromY][fromX] == WhiteKing
5159             && boards[moveNum][toY][toX] == WhiteRook)
5160            || (boards[moveNum][fromY][fromX] == BlackKing
5161                && boards[moveNum][toY][toX] == BlackRook)) {
5162           if(toX > fromX) SendToProgram("O-O\n", cps);
5163           else SendToProgram("O-O-O\n", cps);
5164         }
5165         else SendToProgram(moveList[moveNum], cps);
5166       } else
5167       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5168         char *m = moveList[moveNum];
5169         static char c[2];
5170         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5171         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5172           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5173                                                m[2], m[3] - '0',
5174                                                m[5], m[6] - '0',
5175                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5176         else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5177           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5178           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5179                                                m[7], m[8] - '0',
5180                                                m[7], m[8] - '0',
5181                                                m[5], m[6] - '0',
5182                                                m[5], m[6] - '0',
5183                                                m[2], m[3] - '0', c);
5184         } else
5185           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5186                                                m[5], m[6] - '0',
5187                                                m[5], m[6] - '0',
5188                                                m[2], m[3] - '0', c);
5189           SendToProgram(buf, cps);
5190       } else
5191       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5192         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5193           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5194           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5195                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5196         } else
5197           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5198                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5199         SendToProgram(buf, cps);
5200       }
5201       else SendToProgram(moveList[moveNum], cps);
5202       /* End of additions by Tord */
5203     }
5204
5205     /* [HGM] setting up the opening has brought engine in force mode! */
5206     /*       Send 'go' if we are in a mode where machine should play. */
5207     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5208         (gameMode == TwoMachinesPlay   ||
5209 #if ZIPPY
5210          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5211 #endif
5212          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5213         SendToProgram("go\n", cps);
5214   if (appData.debugMode) {
5215     fprintf(debugFP, "(extra)\n");
5216   }
5217     }
5218     setboardSpoiledMachineBlack = 0;
5219 }
5220
5221 void
5222 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5223 {
5224     char user_move[MSG_SIZ];
5225     char suffix[4];
5226
5227     if(gameInfo.variant == VariantSChess && promoChar) {
5228         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5229         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5230     } else suffix[0] = NULLCHAR;
5231
5232     switch (moveType) {
5233       default:
5234         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5235                 (int)moveType, fromX, fromY, toX, toY);
5236         DisplayError(user_move + strlen("say "), 0);
5237         break;
5238       case WhiteKingSideCastle:
5239       case BlackKingSideCastle:
5240       case WhiteQueenSideCastleWild:
5241       case BlackQueenSideCastleWild:
5242       /* PUSH Fabien */
5243       case WhiteHSideCastleFR:
5244       case BlackHSideCastleFR:
5245       /* POP Fabien */
5246         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5247         break;
5248       case WhiteQueenSideCastle:
5249       case BlackQueenSideCastle:
5250       case WhiteKingSideCastleWild:
5251       case BlackKingSideCastleWild:
5252       /* PUSH Fabien */
5253       case WhiteASideCastleFR:
5254       case BlackASideCastleFR:
5255       /* POP Fabien */
5256         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5257         break;
5258       case WhiteNonPromotion:
5259       case BlackNonPromotion:
5260         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5261         break;
5262       case WhitePromotion:
5263       case BlackPromotion:
5264         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5265            gameInfo.variant == VariantMakruk)
5266           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5267                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5268                 PieceToChar(WhiteFerz));
5269         else if(gameInfo.variant == VariantGreat)
5270           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5271                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5272                 PieceToChar(WhiteMan));
5273         else
5274           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5275                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5276                 promoChar);
5277         break;
5278       case WhiteDrop:
5279       case BlackDrop:
5280       drop:
5281         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5282                  ToUpper(PieceToChar((ChessSquare) fromX)),
5283                  AAA + toX, ONE + toY);
5284         break;
5285       case IllegalMove:  /* could be a variant we don't quite understand */
5286         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5287       case NormalMove:
5288       case WhiteCapturesEnPassant:
5289       case BlackCapturesEnPassant:
5290         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5291                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5292         break;
5293     }
5294     SendToICS(user_move);
5295     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5296         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5297 }
5298
5299 void
5300 UploadGameEvent ()
5301 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5302     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5303     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5304     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5305       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5306       return;
5307     }
5308     if(gameMode != IcsExamining) { // is this ever not the case?
5309         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5310
5311         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5312           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5313         } else { // on FICS we must first go to general examine mode
5314           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5315         }
5316         if(gameInfo.variant != VariantNormal) {
5317             // try figure out wild number, as xboard names are not always valid on ICS
5318             for(i=1; i<=36; i++) {
5319               snprintf(buf, MSG_SIZ, "wild/%d", i);
5320                 if(StringToVariant(buf) == gameInfo.variant) break;
5321             }
5322             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5323             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5324             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5325         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5326         SendToICS(ics_prefix);
5327         SendToICS(buf);
5328         if(startedFromSetupPosition || backwardMostMove != 0) {
5329           fen = PositionToFEN(backwardMostMove, NULL, 1);
5330           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5331             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5332             SendToICS(buf);
5333           } else { // FICS: everything has to set by separate bsetup commands
5334             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5335             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5336             SendToICS(buf);
5337             if(!WhiteOnMove(backwardMostMove)) {
5338                 SendToICS("bsetup tomove black\n");
5339             }
5340             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5341             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5342             SendToICS(buf);
5343             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5344             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5345             SendToICS(buf);
5346             i = boards[backwardMostMove][EP_STATUS];
5347             if(i >= 0) { // set e.p.
5348               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5349                 SendToICS(buf);
5350             }
5351             bsetup++;
5352           }
5353         }
5354       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5355             SendToICS("bsetup done\n"); // switch to normal examining.
5356     }
5357     for(i = backwardMostMove; i<last; i++) {
5358         char buf[20];
5359         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5360         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5361             int len = strlen(moveList[i]);
5362             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5363             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5364         }
5365         SendToICS(buf);
5366     }
5367     SendToICS(ics_prefix);
5368     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5369 }
5370
5371 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5372 int legNr = 1;
5373
5374 void
5375 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5376 {
5377     if (rf == DROP_RANK) {
5378       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5379       sprintf(move, "%c@%c%c\n",
5380                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5381     } else {
5382         if (promoChar == 'x' || promoChar == NULLCHAR) {
5383           sprintf(move, "%c%c%c%c\n",
5384                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5385           if(killX >= 0 && killY >= 0) {
5386             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5387             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5388           }
5389         } else {
5390             sprintf(move, "%c%c%c%c%c\n",
5391                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5392           if(killX >= 0 && killY >= 0) {
5393             sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5394             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5395           }
5396         }
5397     }
5398 }
5399
5400 void
5401 ProcessICSInitScript (FILE *f)
5402 {
5403     char buf[MSG_SIZ];
5404
5405     while (fgets(buf, MSG_SIZ, f)) {
5406         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5407     }
5408
5409     fclose(f);
5410 }
5411
5412
5413 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5414 int dragging;
5415 static ClickType lastClickType;
5416
5417 int
5418 PieceInString (char *s, ChessSquare piece)
5419 {
5420   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5421   while((p = strchr(s, ID))) {
5422     if(!suffix || p[1] == suffix) return TRUE;
5423     s = p;
5424   }
5425   return FALSE;
5426 }
5427
5428 int
5429 Partner (ChessSquare *p)
5430 { // change piece into promotion partner if one shogi-promotes to the other
5431   ChessSquare partner = promoPartner[*p];
5432   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5433   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5434   *p = partner;
5435   return 1;
5436 }
5437
5438 void
5439 Sweep (int step)
5440 {
5441     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5442     static int toggleFlag;
5443     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5444     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5445     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5446     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5447     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5448     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5449     do {
5450         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5451         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5452         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5453         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5454         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5455         if(!step) step = -1;
5456     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5457             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5458             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5459             promoSweep == pawn ||
5460             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5461             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5462     if(toX >= 0) {
5463         int victim = boards[currentMove][toY][toX];
5464         boards[currentMove][toY][toX] = promoSweep;
5465         DrawPosition(FALSE, boards[currentMove]);
5466         boards[currentMove][toY][toX] = victim;
5467     } else
5468     ChangeDragPiece(promoSweep);
5469 }
5470
5471 int
5472 PromoScroll (int x, int y)
5473 {
5474   int step = 0;
5475
5476   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5477   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5478   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5479   if(!step) return FALSE;
5480   lastX = x; lastY = y;
5481   if((promoSweep < BlackPawn) == flipView) step = -step;
5482   if(step > 0) selectFlag = 1;
5483   if(!selectFlag) Sweep(step);
5484   return FALSE;
5485 }
5486
5487 void
5488 NextPiece (int step)
5489 {
5490     ChessSquare piece = boards[currentMove][toY][toX];
5491     do {
5492         pieceSweep -= step;
5493         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5494         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5495         if(!step) step = -1;
5496     } while(PieceToChar(pieceSweep) == '.');
5497     boards[currentMove][toY][toX] = pieceSweep;
5498     DrawPosition(FALSE, boards[currentMove]);
5499     boards[currentMove][toY][toX] = piece;
5500 }
5501 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5502 void
5503 AlphaRank (char *move, int n)
5504 {
5505 //    char *p = move, c; int x, y;
5506
5507     if (appData.debugMode) {
5508         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5509     }
5510
5511     if(move[1]=='*' &&
5512        move[2]>='0' && move[2]<='9' &&
5513        move[3]>='a' && move[3]<='x'    ) {
5514         move[1] = '@';
5515         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5516         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5517     } else
5518     if(move[0]>='0' && move[0]<='9' &&
5519        move[1]>='a' && move[1]<='x' &&
5520        move[2]>='0' && move[2]<='9' &&
5521        move[3]>='a' && move[3]<='x'    ) {
5522         /* input move, Shogi -> normal */
5523         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5524         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5525         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5526         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5527     } else
5528     if(move[1]=='@' &&
5529        move[3]>='0' && move[3]<='9' &&
5530        move[2]>='a' && move[2]<='x'    ) {
5531         move[1] = '*';
5532         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5533         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5534     } else
5535     if(
5536        move[0]>='a' && move[0]<='x' &&
5537        move[3]>='0' && move[3]<='9' &&
5538        move[2]>='a' && move[2]<='x'    ) {
5539          /* output move, normal -> Shogi */
5540         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5541         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5542         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5543         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5544         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5545     }
5546     if (appData.debugMode) {
5547         fprintf(debugFP, "   out = '%s'\n", move);
5548     }
5549 }
5550
5551 char yy_textstr[8000];
5552
5553 /* Parser for moves from gnuchess, ICS, or user typein box */
5554 Boolean
5555 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5556 {
5557     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5558
5559     switch (*moveType) {
5560       case WhitePromotion:
5561       case BlackPromotion:
5562       case WhiteNonPromotion:
5563       case BlackNonPromotion:
5564       case NormalMove:
5565       case FirstLeg:
5566       case WhiteCapturesEnPassant:
5567       case BlackCapturesEnPassant:
5568       case WhiteKingSideCastle:
5569       case WhiteQueenSideCastle:
5570       case BlackKingSideCastle:
5571       case BlackQueenSideCastle:
5572       case WhiteKingSideCastleWild:
5573       case WhiteQueenSideCastleWild:
5574       case BlackKingSideCastleWild:
5575       case BlackQueenSideCastleWild:
5576       /* Code added by Tord: */
5577       case WhiteHSideCastleFR:
5578       case WhiteASideCastleFR:
5579       case BlackHSideCastleFR:
5580       case BlackASideCastleFR:
5581       /* End of code added by Tord */
5582       case IllegalMove:         /* bug or odd chess variant */
5583         if(currentMoveString[1] == '@') { // illegal drop
5584           *fromX = WhiteOnMove(moveNum) ?
5585             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5586             (int) CharToPiece(ToLower(currentMoveString[0]));
5587           goto drop;
5588         }
5589         *fromX = currentMoveString[0] - AAA;
5590         *fromY = currentMoveString[1] - ONE;
5591         *toX = currentMoveString[2] - AAA;
5592         *toY = currentMoveString[3] - ONE;
5593         *promoChar = currentMoveString[4];
5594         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5595         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5596             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5597     if (appData.debugMode) {
5598         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5599     }
5600             *fromX = *fromY = *toX = *toY = 0;
5601             return FALSE;
5602         }
5603         if (appData.testLegality) {
5604           return (*moveType != IllegalMove);
5605         } else {
5606           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5607                          // [HGM] lion: if this is a double move we are less critical
5608                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5609         }
5610
5611       case WhiteDrop:
5612       case BlackDrop:
5613         *fromX = *moveType == WhiteDrop ?
5614           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5615           (int) CharToPiece(ToLower(currentMoveString[0]));
5616       drop:
5617         *fromY = DROP_RANK;
5618         *toX = currentMoveString[2] - AAA;
5619         *toY = currentMoveString[3] - ONE;
5620         *promoChar = NULLCHAR;
5621         return TRUE;
5622
5623       case AmbiguousMove:
5624       case ImpossibleMove:
5625       case EndOfFile:
5626       case ElapsedTime:
5627       case Comment:
5628       case PGNTag:
5629       case NAG:
5630       case WhiteWins:
5631       case BlackWins:
5632       case GameIsDrawn:
5633       default:
5634     if (appData.debugMode) {
5635         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5636     }
5637         /* bug? */
5638         *fromX = *fromY = *toX = *toY = 0;
5639         *promoChar = NULLCHAR;
5640         return FALSE;
5641     }
5642 }
5643
5644 Boolean pushed = FALSE;
5645 char *lastParseAttempt;
5646
5647 void
5648 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5649 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5650   int fromX, fromY, toX, toY; char promoChar;
5651   ChessMove moveType;
5652   Boolean valid;
5653   int nr = 0;
5654
5655   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5656   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5657     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5658     pushed = TRUE;
5659   }
5660   endPV = forwardMostMove;
5661   do {
5662     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5663     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5664     lastParseAttempt = pv;
5665     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5666     if(!valid && nr == 0 &&
5667        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5668         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5669         // Hande case where played move is different from leading PV move
5670         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5671         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5672         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5673         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5674           endPV += 2; // if position different, keep this
5675           moveList[endPV-1][0] = fromX + AAA;
5676           moveList[endPV-1][1] = fromY + ONE;
5677           moveList[endPV-1][2] = toX + AAA;
5678           moveList[endPV-1][3] = toY + ONE;
5679           parseList[endPV-1][0] = NULLCHAR;
5680           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5681         }
5682       }
5683     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5684     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5685     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5686     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5687         valid++; // allow comments in PV
5688         continue;
5689     }
5690     nr++;
5691     if(endPV+1 > framePtr) break; // no space, truncate
5692     if(!valid) break;
5693     endPV++;
5694     CopyBoard(boards[endPV], boards[endPV-1]);
5695     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5696     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5697     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5698     CoordsToAlgebraic(boards[endPV - 1],
5699                              PosFlags(endPV - 1),
5700                              fromY, fromX, toY, toX, promoChar,
5701                              parseList[endPV - 1]);
5702   } while(valid);
5703   if(atEnd == 2) return; // used hidden, for PV conversion
5704   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5705   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5706   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5707                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5708   DrawPosition(TRUE, boards[currentMove]);
5709 }
5710
5711 int
5712 MultiPV (ChessProgramState *cps, int kind)
5713 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5714         int i;
5715         for(i=0; i<cps->nrOptions; i++) {
5716             char *s = cps->option[i].name;
5717             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5718             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5719                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5720         }
5721         return -1;
5722 }
5723
5724 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5725 static int multi, pv_margin;
5726 static ChessProgramState *activeCps;
5727
5728 Boolean
5729 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5730 {
5731         int startPV, lineStart, origIndex = index;
5732         char *p, buf2[MSG_SIZ];
5733         ChessProgramState *cps = (pane ? &second : &first);
5734
5735         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5736         lastX = x; lastY = y;
5737         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5738         lineStart = startPV = index;
5739         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5740         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5741         index = startPV;
5742         do{ while(buf[index] && buf[index] != '\n') index++;
5743         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5744         buf[index] = 0;
5745         if(lineStart == 0 && gameMode == AnalyzeMode) {
5746             int n = 0;
5747             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5748             if(n == 0) { // click not on "fewer" or "more"
5749                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5750                     pv_margin = cps->option[multi].value;
5751                     activeCps = cps; // non-null signals margin adjustment
5752                 }
5753             } else if((multi = MultiPV(cps, 1)) >= 0) {
5754                 n += cps->option[multi].value; if(n < 1) n = 1;
5755                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5756                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5757                 cps->option[multi].value = n;
5758                 *start = *end = 0;
5759                 return FALSE;
5760             }
5761         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5762                 ExcludeClick(origIndex - lineStart);
5763                 return FALSE;
5764         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5765                 Collapse(origIndex - lineStart);
5766                 return FALSE;
5767         }
5768         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5769         *start = startPV; *end = index-1;
5770         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5771         return TRUE;
5772 }
5773
5774 char *
5775 PvToSAN (char *pv)
5776 {
5777         static char buf[10*MSG_SIZ];
5778         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5779         *buf = NULLCHAR;
5780         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5781         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5782         for(i = forwardMostMove; i<endPV; i++){
5783             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5784             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5785             k += strlen(buf+k);
5786         }
5787         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5788         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5789         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5790         endPV = savedEnd;
5791         return buf;
5792 }
5793
5794 Boolean
5795 LoadPV (int x, int y)
5796 { // called on right mouse click to load PV
5797   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5798   lastX = x; lastY = y;
5799   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5800   extendGame = FALSE;
5801   return TRUE;
5802 }
5803
5804 void
5805 UnLoadPV ()
5806 {
5807   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5808   if(activeCps) {
5809     if(pv_margin != activeCps->option[multi].value) {
5810       char buf[MSG_SIZ];
5811       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5812       SendToProgram(buf, activeCps);
5813       activeCps->option[multi].value = pv_margin;
5814     }
5815     activeCps = NULL;
5816     return;
5817   }
5818   if(endPV < 0) return;
5819   if(appData.autoCopyPV) CopyFENToClipboard();
5820   endPV = -1;
5821   if(extendGame && currentMove > forwardMostMove) {
5822         Boolean saveAnimate = appData.animate;
5823         if(pushed) {
5824             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5825                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5826             } else storedGames--; // abandon shelved tail of original game
5827         }
5828         pushed = FALSE;
5829         forwardMostMove = currentMove;
5830         currentMove = oldFMM;
5831         appData.animate = FALSE;
5832         ToNrEvent(forwardMostMove);
5833         appData.animate = saveAnimate;
5834   }
5835   currentMove = forwardMostMove;
5836   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5837   ClearPremoveHighlights();
5838   DrawPosition(TRUE, boards[currentMove]);
5839 }
5840
5841 void
5842 MovePV (int x, int y, int h)
5843 { // step through PV based on mouse coordinates (called on mouse move)
5844   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5845
5846   if(activeCps) { // adjusting engine's multi-pv margin
5847     if(x > lastX) pv_margin++; else
5848     if(x < lastX) pv_margin -= (pv_margin > 0);
5849     if(x != lastX) {
5850       char buf[MSG_SIZ];
5851       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5852       DisplayMessage(buf, "");
5853     }
5854     lastX = x;
5855     return;
5856   }
5857   // we must somehow check if right button is still down (might be released off board!)
5858   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5859   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5860   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5861   if(!step) return;
5862   lastX = x; lastY = y;
5863
5864   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5865   if(endPV < 0) return;
5866   if(y < margin) step = 1; else
5867   if(y > h - margin) step = -1;
5868   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5869   currentMove += step;
5870   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5871   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5872                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5873   DrawPosition(FALSE, boards[currentMove]);
5874 }
5875
5876
5877 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5878 // All positions will have equal probability, but the current method will not provide a unique
5879 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5880 #define DARK 1
5881 #define LITE 2
5882 #define ANY 3
5883
5884 int squaresLeft[4];
5885 int piecesLeft[(int)BlackPawn];
5886 int seed, nrOfShuffles;
5887
5888 void
5889 GetPositionNumber ()
5890 {       // sets global variable seed
5891         int i;
5892
5893         seed = appData.defaultFrcPosition;
5894         if(seed < 0) { // randomize based on time for negative FRC position numbers
5895                 for(i=0; i<50; i++) seed += random();
5896                 seed = random() ^ random() >> 8 ^ random() << 8;
5897                 if(seed<0) seed = -seed;
5898         }
5899 }
5900
5901 int
5902 put (Board board, int pieceType, int rank, int n, int shade)
5903 // put the piece on the (n-1)-th empty squares of the given shade
5904 {
5905         int i;
5906
5907         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5908                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5909                         board[rank][i] = (ChessSquare) pieceType;
5910                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5911                         squaresLeft[ANY]--;
5912                         piecesLeft[pieceType]--;
5913                         return i;
5914                 }
5915         }
5916         return -1;
5917 }
5918
5919
5920 void
5921 AddOnePiece (Board board, int pieceType, int rank, int shade)
5922 // calculate where the next piece goes, (any empty square), and put it there
5923 {
5924         int i;
5925
5926         i = seed % squaresLeft[shade];
5927         nrOfShuffles *= squaresLeft[shade];
5928         seed /= squaresLeft[shade];
5929         put(board, pieceType, rank, i, shade);
5930 }
5931
5932 void
5933 AddTwoPieces (Board board, int pieceType, int rank)
5934 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5935 {
5936         int i, n=squaresLeft[ANY], j=n-1, k;
5937
5938         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5939         i = seed % k;  // pick one
5940         nrOfShuffles *= k;
5941         seed /= k;
5942         while(i >= j) i -= j--;
5943         j = n - 1 - j; i += j;
5944         put(board, pieceType, rank, j, ANY);
5945         put(board, pieceType, rank, i, ANY);
5946 }
5947
5948 void
5949 SetUpShuffle (Board board, int number)
5950 {
5951         int i, p, first=1;
5952
5953         GetPositionNumber(); nrOfShuffles = 1;
5954
5955         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5956         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5957         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5958
5959         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5960
5961         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5962             p = (int) board[0][i];
5963             if(p < (int) BlackPawn) piecesLeft[p] ++;
5964             board[0][i] = EmptySquare;
5965         }
5966
5967         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5968             // shuffles restricted to allow normal castling put KRR first
5969             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5970                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5971             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5972                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5973             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5974                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5975             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5976                 put(board, WhiteRook, 0, 0, ANY);
5977             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5978         }
5979
5980         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5981             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5982             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5983                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5984                 while(piecesLeft[p] >= 2) {
5985                     AddOnePiece(board, p, 0, LITE);
5986                     AddOnePiece(board, p, 0, DARK);
5987                 }
5988                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5989             }
5990
5991         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5992             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5993             // but we leave King and Rooks for last, to possibly obey FRC restriction
5994             if(p == (int)WhiteRook) continue;
5995             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5996             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5997         }
5998
5999         // now everything is placed, except perhaps King (Unicorn) and Rooks
6000
6001         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6002             // Last King gets castling rights
6003             while(piecesLeft[(int)WhiteUnicorn]) {
6004                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6005                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6006             }
6007
6008             while(piecesLeft[(int)WhiteKing]) {
6009                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6010                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6011             }
6012
6013
6014         } else {
6015             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6016             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6017         }
6018
6019         // Only Rooks can be left; simply place them all
6020         while(piecesLeft[(int)WhiteRook]) {
6021                 i = put(board, WhiteRook, 0, 0, ANY);
6022                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6023                         if(first) {
6024                                 first=0;
6025                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6026                         }
6027                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6028                 }
6029         }
6030         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6031             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6032         }
6033
6034         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6035 }
6036
6037 int
6038 ptclen (const char *s, char *escapes)
6039 {
6040     int n = 0;
6041     if(!*escapes) return strlen(s);
6042     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6043     return n;
6044 }
6045
6046 int
6047 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6048 /* [HGM] moved here from winboard.c because of its general usefulness */
6049 /*       Basically a safe strcpy that uses the last character as King */
6050 {
6051     int result = FALSE; int NrPieces;
6052     unsigned char partner[EmptySquare];
6053
6054     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6055                     && NrPieces >= 12 && !(NrPieces&1)) {
6056         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6057
6058         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6059         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6060             char *p, c=0;
6061             if(map[j] == '/') offs = WhitePBishop - i, j++;
6062             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6063             table[i+offs] = map[j++];
6064             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6065             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6066             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6067         }
6068         table[(int) WhiteKing]  = map[j++];
6069         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6070             char *p, c=0;
6071             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6072             i = WHITE_TO_BLACK ii;
6073             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6074             table[i+offs] = map[j++];
6075             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6076             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6077             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6078         }
6079         table[(int) BlackKing]  = map[j++];
6080
6081
6082         if(*escapes) { // set up promotion pairing
6083             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6084             // pieceToChar entirely filled, so we can look up specified partners
6085             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6086                 int c = table[i];
6087                 if(c == '^' || c == '-') { // has specified partner
6088                     int p;
6089                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6090                     if(c == '^') table[i] = '+';
6091                     if(p < EmptySquare) {
6092                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6093                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6094                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6095                     }
6096                 } else if(c == '*') {
6097                     table[i] = partner[i];
6098                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6099                 }
6100             }
6101         }
6102
6103         result = TRUE;
6104     }
6105
6106     return result;
6107 }
6108
6109 int
6110 SetCharTable (unsigned char *table, const char * map)
6111 {
6112     return SetCharTableEsc(table, map, "");
6113 }
6114
6115 void
6116 Prelude (Board board)
6117 {       // [HGM] superchess: random selection of exo-pieces
6118         int i, j, k; ChessSquare p;
6119         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6120
6121         GetPositionNumber(); // use FRC position number
6122
6123         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6124             SetCharTable(pieceToChar, appData.pieceToCharTable);
6125             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6126                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6127         }
6128
6129         j = seed%4;                 seed /= 4;
6130         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6131         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6132         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6133         j = seed%3 + (seed%3 >= j); seed /= 3;
6134         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6135         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6136         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6137         j = seed%3;                 seed /= 3;
6138         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6139         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6140         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6141         j = seed%2 + (seed%2 >= j); seed /= 2;
6142         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6143         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6144         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6145         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6146         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6147         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6148         put(board, exoPieces[0],    0, 0, ANY);
6149         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6150 }
6151
6152 void
6153 InitPosition (int redraw)
6154 {
6155     ChessSquare (* pieces)[BOARD_FILES];
6156     int i, j, pawnRow=1, pieceRows=1, overrule,
6157     oldx = gameInfo.boardWidth,
6158     oldy = gameInfo.boardHeight,
6159     oldh = gameInfo.holdingsWidth;
6160     static int oldv;
6161
6162     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6163
6164     /* [AS] Initialize pv info list [HGM] and game status */
6165     {
6166         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6167             pvInfoList[i].depth = 0;
6168             boards[i][EP_STATUS] = EP_NONE;
6169             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6170         }
6171
6172         initialRulePlies = 0; /* 50-move counter start */
6173
6174         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6175         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6176     }
6177
6178
6179     /* [HGM] logic here is completely changed. In stead of full positions */
6180     /* the initialized data only consist of the two backranks. The switch */
6181     /* selects which one we will use, which is than copied to the Board   */
6182     /* initialPosition, which for the rest is initialized by Pawns and    */
6183     /* empty squares. This initial position is then copied to boards[0],  */
6184     /* possibly after shuffling, so that it remains available.            */
6185
6186     gameInfo.holdingsWidth = 0; /* default board sizes */
6187     gameInfo.boardWidth    = 8;
6188     gameInfo.boardHeight   = 8;
6189     gameInfo.holdingsSize  = 0;
6190     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6191     for(i=0; i<BOARD_FILES-6; i++)
6192       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6193     initialPosition[EP_STATUS] = EP_NONE;
6194     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6195     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6196     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6197          SetCharTable(pieceNickName, appData.pieceNickNames);
6198     else SetCharTable(pieceNickName, "............");
6199     pieces = FIDEArray;
6200
6201     switch (gameInfo.variant) {
6202     case VariantFischeRandom:
6203       shuffleOpenings = TRUE;
6204       appData.fischerCastling = TRUE;
6205     default:
6206       break;
6207     case VariantShatranj:
6208       pieces = ShatranjArray;
6209       nrCastlingRights = 0;
6210       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6211       break;
6212     case VariantMakruk:
6213       pieces = makrukArray;
6214       nrCastlingRights = 0;
6215       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6216       break;
6217     case VariantASEAN:
6218       pieces = aseanArray;
6219       nrCastlingRights = 0;
6220       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6221       break;
6222     case VariantTwoKings:
6223       pieces = twoKingsArray;
6224       break;
6225     case VariantGrand:
6226       pieces = GrandArray;
6227       nrCastlingRights = 0;
6228       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6229       gameInfo.boardWidth = 10;
6230       gameInfo.boardHeight = 10;
6231       gameInfo.holdingsSize = 7;
6232       break;
6233     case VariantCapaRandom:
6234       shuffleOpenings = TRUE;
6235       appData.fischerCastling = TRUE;
6236     case VariantCapablanca:
6237       pieces = CapablancaArray;
6238       gameInfo.boardWidth = 10;
6239       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6240       break;
6241     case VariantGothic:
6242       pieces = GothicArray;
6243       gameInfo.boardWidth = 10;
6244       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6245       break;
6246     case VariantSChess:
6247       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6248       gameInfo.holdingsSize = 7;
6249       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6250       break;
6251     case VariantJanus:
6252       pieces = JanusArray;
6253       gameInfo.boardWidth = 10;
6254       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6255       nrCastlingRights = 6;
6256         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6257         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6258         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6259         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6260         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6261         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6262       break;
6263     case VariantFalcon:
6264       pieces = FalconArray;
6265       gameInfo.boardWidth = 10;
6266       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6267       break;
6268     case VariantXiangqi:
6269       pieces = XiangqiArray;
6270       gameInfo.boardWidth  = 9;
6271       gameInfo.boardHeight = 10;
6272       nrCastlingRights = 0;
6273       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6274       break;
6275     case VariantShogi:
6276       pieces = ShogiArray;
6277       gameInfo.boardWidth  = 9;
6278       gameInfo.boardHeight = 9;
6279       gameInfo.holdingsSize = 7;
6280       nrCastlingRights = 0;
6281       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6282       break;
6283     case VariantChu:
6284       pieces = ChuArray; pieceRows = 3;
6285       gameInfo.boardWidth  = 12;
6286       gameInfo.boardHeight = 12;
6287       nrCastlingRights = 0;
6288 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6289   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6290       SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6291                                    "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6292       break;
6293     case VariantCourier:
6294       pieces = CourierArray;
6295       gameInfo.boardWidth  = 12;
6296       nrCastlingRights = 0;
6297       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6298       break;
6299     case VariantKnightmate:
6300       pieces = KnightmateArray;
6301       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6302       break;
6303     case VariantSpartan:
6304       pieces = SpartanArray;
6305       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6306       break;
6307     case VariantLion:
6308       pieces = lionArray;
6309       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6310       break;
6311     case VariantChuChess:
6312       pieces = ChuChessArray;
6313       gameInfo.boardWidth = 10;
6314       gameInfo.boardHeight = 10;
6315       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6316       break;
6317     case VariantFairy:
6318       pieces = fairyArray;
6319       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6320       break;
6321     case VariantGreat:
6322       pieces = GreatArray;
6323       gameInfo.boardWidth = 10;
6324       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6325       gameInfo.holdingsSize = 8;
6326       break;
6327     case VariantSuper:
6328       pieces = FIDEArray;
6329       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6330       gameInfo.holdingsSize = 8;
6331       startedFromSetupPosition = TRUE;
6332       break;
6333     case VariantCrazyhouse:
6334     case VariantBughouse:
6335       pieces = FIDEArray;
6336       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6337       gameInfo.holdingsSize = 5;
6338       break;
6339     case VariantWildCastle:
6340       pieces = FIDEArray;
6341       /* !!?shuffle with kings guaranteed to be on d or e file */
6342       shuffleOpenings = 1;
6343       break;
6344     case VariantNoCastle:
6345       pieces = FIDEArray;
6346       nrCastlingRights = 0;
6347       /* !!?unconstrained back-rank shuffle */
6348       shuffleOpenings = 1;
6349       break;
6350     }
6351
6352     overrule = 0;
6353     if(appData.NrFiles >= 0) {
6354         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6355         gameInfo.boardWidth = appData.NrFiles;
6356     }
6357     if(appData.NrRanks >= 0) {
6358         gameInfo.boardHeight = appData.NrRanks;
6359     }
6360     if(appData.holdingsSize >= 0) {
6361         i = appData.holdingsSize;
6362         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6363         gameInfo.holdingsSize = i;
6364     }
6365     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6366     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6367         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6368
6369     handSize = BOARD_HEIGHT;
6370     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6371     if(pawnRow < 1) pawnRow = 1;
6372     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6373        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6374     if(gameInfo.variant == VariantChu) pawnRow = 3;
6375
6376     /* User pieceToChar list overrules defaults */
6377     if(appData.pieceToCharTable != NULL)
6378         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6379
6380     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6381
6382         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6383             s = (ChessSquare) 0; /* account holding counts in guard band */
6384         for( i=0; i<BOARD_HEIGHT; i++ )
6385             initialPosition[i][j] = s;
6386
6387         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6388         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6389         initialPosition[pawnRow][j] = WhitePawn;
6390         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6391         if(gameInfo.variant == VariantXiangqi) {
6392             if(j&1) {
6393                 initialPosition[pawnRow][j] =
6394                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6395                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6396                    initialPosition[2][j] = WhiteCannon;
6397                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6398                 }
6399             }
6400         }
6401         if(gameInfo.variant == VariantChu) {
6402              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6403                initialPosition[pawnRow+1][j] = WhiteCobra,
6404                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6405              for(i=1; i<pieceRows; i++) {
6406                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6407                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6408              }
6409         }
6410         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6411             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6412                initialPosition[0][j] = WhiteRook;
6413                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6414             }
6415         }
6416         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6417     }
6418     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6419     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6420
6421             j=BOARD_LEFT+1;
6422             initialPosition[1][j] = WhiteBishop;
6423             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6424             j=BOARD_RGHT-2;
6425             initialPosition[1][j] = WhiteRook;
6426             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6427     }
6428
6429     if( nrCastlingRights == -1) {
6430         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6431         /*       This sets default castling rights from none to normal corners   */
6432         /* Variants with other castling rights must set them themselves above    */
6433         nrCastlingRights = 6;
6434
6435         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6436         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6437         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6438         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6439         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6440         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6441      }
6442
6443      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6444      if(gameInfo.variant == VariantGreat) { // promotion commoners
6445         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6446         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6447         initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6448         initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6449      }
6450      if( gameInfo.variant == VariantSChess ) {
6451       initialPosition[1][0] = BlackMarshall;
6452       initialPosition[2][0] = BlackAngel;
6453       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6454       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6455       initialPosition[1][1] = initialPosition[2][1] =
6456       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6457      }
6458      initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6459   if (appData.debugMode) {
6460     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6461   }
6462     if(shuffleOpenings) {
6463         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6464         startedFromSetupPosition = TRUE;
6465     }
6466     if(startedFromPositionFile) {
6467       /* [HGM] loadPos: use PositionFile for every new game */
6468       CopyBoard(initialPosition, filePosition);
6469       for(i=0; i<nrCastlingRights; i++)
6470           initialRights[i] = filePosition[CASTLING][i];
6471       startedFromSetupPosition = TRUE;
6472     }
6473     if(*appData.men) LoadPieceDesc(appData.men);
6474
6475     CopyBoard(boards[0], initialPosition);
6476
6477     if(oldx != gameInfo.boardWidth ||
6478        oldy != gameInfo.boardHeight ||
6479        oldv != gameInfo.variant ||
6480        oldh != gameInfo.holdingsWidth
6481                                          )
6482             InitDrawingSizes(-2 ,0);
6483
6484     oldv = gameInfo.variant;
6485     if (redraw)
6486       DrawPosition(TRUE, boards[currentMove]);
6487 }
6488
6489 void
6490 SendBoard (ChessProgramState *cps, int moveNum)
6491 {
6492     char message[MSG_SIZ];
6493
6494     if (cps->useSetboard) {
6495       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6496       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6497       SendToProgram(message, cps);
6498       free(fen);
6499
6500     } else {
6501       ChessSquare *bp;
6502       int i, j, left=0, right=BOARD_WIDTH;
6503       /* Kludge to set black to move, avoiding the troublesome and now
6504        * deprecated "black" command.
6505        */
6506       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6507         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6508
6509       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6510
6511       SendToProgram("edit\n", cps);
6512       SendToProgram("#\n", cps);
6513       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6514         bp = &boards[moveNum][i][left];
6515         for (j = left; j < right; j++, bp++) {
6516           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6517           if ((int) *bp < (int) BlackPawn) {
6518             if(j == BOARD_RGHT+1)
6519                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6520             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6521             if(message[0] == '+' || message[0] == '~') {
6522               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6523                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6524                         AAA + j, ONE + i - '0');
6525             }
6526             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6527                 message[1] = BOARD_RGHT   - 1 - j + '1';
6528                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6529             }
6530             SendToProgram(message, cps);
6531           }
6532         }
6533       }
6534
6535       SendToProgram("c\n", cps);
6536       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6537         bp = &boards[moveNum][i][left];
6538         for (j = left; j < right; j++, bp++) {
6539           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6540           if (((int) *bp != (int) EmptySquare)
6541               && ((int) *bp >= (int) BlackPawn)) {
6542             if(j == BOARD_LEFT-2)
6543                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6544             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6545                     AAA + j, ONE + i - '0');
6546             if(message[0] == '+' || message[0] == '~') {
6547               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6548                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6549                         AAA + j, ONE + i - '0');
6550             }
6551             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6552                 message[1] = BOARD_RGHT   - 1 - j + '1';
6553                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6554             }
6555             SendToProgram(message, cps);
6556           }
6557         }
6558       }
6559
6560       SendToProgram(".\n", cps);
6561     }
6562     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6563 }
6564
6565 char exclusionHeader[MSG_SIZ];
6566 int exCnt, excludePtr;
6567 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6568 static Exclusion excluTab[200];
6569 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6570
6571 static void
6572 WriteMap (int s)
6573 {
6574     int j;
6575     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6576     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6577 }
6578
6579 static void
6580 ClearMap ()
6581 {
6582     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6583     excludePtr = 24; exCnt = 0;
6584     WriteMap(0);
6585 }
6586
6587 static void
6588 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6589 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6590     char buf[2*MOVE_LEN], *p;
6591     Exclusion *e = excluTab;
6592     int i;
6593     for(i=0; i<exCnt; i++)
6594         if(e[i].ff == fromX && e[i].fr == fromY &&
6595            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6596     if(i == exCnt) { // was not in exclude list; add it
6597         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6598         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6599             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6600             return; // abort
6601         }
6602         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6603         excludePtr++; e[i].mark = excludePtr++;
6604         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6605         exCnt++;
6606     }
6607     exclusionHeader[e[i].mark] = state;
6608 }
6609
6610 static int
6611 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6612 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6613     char buf[MSG_SIZ];
6614     int j, k;
6615     ChessMove moveType;
6616     if((signed char)promoChar == -1) { // kludge to indicate best move
6617         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6618             return 1; // if unparsable, abort
6619     }
6620     // update exclusion map (resolving toggle by consulting existing state)
6621     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6622     j = k%8; k >>= 3;
6623     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6624     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6625          excludeMap[k] |=   1<<j;
6626     else excludeMap[k] &= ~(1<<j);
6627     // update header
6628     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6629     // inform engine
6630     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6631     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6632     SendToBoth(buf);
6633     return (state == '+');
6634 }
6635
6636 static void
6637 ExcludeClick (int index)
6638 {
6639     int i, j;
6640     Exclusion *e = excluTab;
6641     if(index < 25) { // none, best or tail clicked
6642         if(index < 13) { // none: include all
6643             WriteMap(0); // clear map
6644             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6645             SendToBoth("include all\n"); // and inform engine
6646         } else if(index > 18) { // tail
6647             if(exclusionHeader[19] == '-') { // tail was excluded
6648                 SendToBoth("include all\n");
6649                 WriteMap(0); // clear map completely
6650                 // now re-exclude selected moves
6651                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6652                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6653             } else { // tail was included or in mixed state
6654                 SendToBoth("exclude all\n");
6655                 WriteMap(0xFF); // fill map completely
6656                 // now re-include selected moves
6657                 j = 0; // count them
6658                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6659                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6660                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6661             }
6662         } else { // best
6663             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6664         }
6665     } else {
6666         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6667             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6668             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6669             break;
6670         }
6671     }
6672 }
6673
6674 ChessSquare
6675 DefaultPromoChoice (int white)
6676 {
6677     ChessSquare result;
6678     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6679        gameInfo.variant == VariantMakruk)
6680         result = WhiteFerz; // no choice
6681     else if(gameInfo.variant == VariantASEAN)
6682         result = WhiteRook; // no choice
6683     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6684         result= WhiteKing; // in Suicide Q is the last thing we want
6685     else if(gameInfo.variant == VariantSpartan)
6686         result = white ? WhiteQueen : WhiteAngel;
6687     else result = WhiteQueen;
6688     if(!white) result = WHITE_TO_BLACK result;
6689     return result;
6690 }
6691
6692 static int autoQueen; // [HGM] oneclick
6693
6694 int
6695 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6696 {
6697     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6698     /* [HGM] add Shogi promotions */
6699     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6700     ChessSquare piece, partner;
6701     ChessMove moveType;
6702     Boolean premove;
6703
6704     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6705     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6706
6707     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6708       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6709         return FALSE;
6710
6711     if(legal[toY][toX] == 4) return FALSE;
6712
6713     piece = boards[currentMove][fromY][fromX];
6714     if(gameInfo.variant == VariantChu) {
6715         promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3;
6716         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6717         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6718     } else if(gameInfo.variant == VariantShogi) {
6719         promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8);
6720         highestPromotingPiece = (int)WhiteAlfil;
6721         if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
6722     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6723         promotionZoneSize = 3;
6724     }
6725
6726     // Treat Lance as Pawn when it is not representing Amazon or Lance
6727     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6728         if(piece == WhiteLance) piece = WhitePawn; else
6729         if(piece == BlackLance) piece = BlackPawn;
6730     }
6731
6732     // next weed out all moves that do not touch the promotion zone at all
6733     if((int)piece >= BlackPawn) {
6734         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6735              return FALSE;
6736         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6737         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6738     } else {
6739         if(  toY < BOARD_HEIGHT - deadRanks - promotionZoneSize &&
6740            fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE;
6741         if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess)
6742              return FALSE;
6743     }
6744
6745     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6746
6747     // weed out mandatory Shogi promotions
6748     if(gameInfo.variant == VariantShogi) {
6749         if(piece >= BlackPawn) {
6750             if(toY == 0 && piece == BlackPawn ||
6751                toY == 0 && piece == BlackQueen ||
6752                toY <= 1 && piece == BlackKnight) {
6753                 *promoChoice = '+';
6754                 return FALSE;
6755             }
6756         } else {
6757             if(toY == BOARD_HEIGHT-deadRanks-1 && piece == WhitePawn ||
6758                toY == BOARD_HEIGHT-deadRanks-1 && piece == WhiteQueen ||
6759                toY >= BOARD_HEIGHT-deadRanks-2 && piece == WhiteKnight) {
6760                 *promoChoice = '+';
6761                 return FALSE;
6762             }
6763         }
6764     }
6765
6766     // weed out obviously illegal Pawn moves
6767     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6768         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6769         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6770         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6771         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6772         // note we are not allowed to test for valid (non-)capture, due to premove
6773     }
6774
6775     // we either have a choice what to promote to, or (in Shogi) whether to promote
6776     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6777        gameInfo.variant == VariantMakruk) {
6778         ChessSquare p=BlackFerz;  // no choice
6779         while(p < EmptySquare) {  //but make sure we use piece that exists
6780             *promoChoice = PieceToChar(p++);
6781             if(*promoChoice != '.') break;
6782         }
6783         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6784     }
6785     // no sense asking what we must promote to if it is going to explode...
6786     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6787         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6788         return FALSE;
6789     }
6790     // give caller the default choice even if we will not make it
6791     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6792     partner = piece; // pieces can promote if the pieceToCharTable says so
6793     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6794     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6795     if(        sweepSelect && gameInfo.variant != VariantGreat
6796                            && gameInfo.variant != VariantGrand
6797                            && gameInfo.variant != VariantSuper) return FALSE;
6798     if(autoQueen) return FALSE; // predetermined
6799
6800     // suppress promotion popup on illegal moves that are not premoves
6801     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6802               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6803     if(appData.testLegality && !premove) {
6804         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6805                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6806         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6807         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6808             return FALSE;
6809     }
6810
6811     return TRUE;
6812 }
6813
6814 int
6815 InPalace (int row, int column)
6816 {   /* [HGM] for Xiangqi */
6817     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6818          column < (BOARD_WIDTH + 4)/2 &&
6819          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6820     return FALSE;
6821 }
6822
6823 int
6824 PieceForSquare (int x, int y)
6825 {
6826   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6827      return -1;
6828   else
6829      return boards[currentMove][y][x];
6830 }
6831
6832 int
6833 OKToStartUserMove (int x, int y)
6834 {
6835     ChessSquare from_piece;
6836     int white_piece;
6837
6838     if (matchMode) return FALSE;
6839     if (gameMode == EditPosition) return TRUE;
6840
6841     if (x >= 0 && y >= 0)
6842       from_piece = boards[currentMove][y][x];
6843     else
6844       from_piece = EmptySquare;
6845
6846     if (from_piece == EmptySquare) return FALSE;
6847
6848     white_piece = (int)from_piece >= (int)WhitePawn &&
6849       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6850
6851     switch (gameMode) {
6852       case AnalyzeFile:
6853       case TwoMachinesPlay:
6854       case EndOfGame:
6855         return FALSE;
6856
6857       case IcsObserving:
6858       case IcsIdle:
6859         return FALSE;
6860
6861       case MachinePlaysWhite:
6862       case IcsPlayingBlack:
6863         if (appData.zippyPlay) return FALSE;
6864         if (white_piece) {
6865             DisplayMoveError(_("You are playing Black"));
6866             return FALSE;
6867         }
6868         break;
6869
6870       case MachinePlaysBlack:
6871       case IcsPlayingWhite:
6872         if (appData.zippyPlay) return FALSE;
6873         if (!white_piece) {
6874             DisplayMoveError(_("You are playing White"));
6875             return FALSE;
6876         }
6877         break;
6878
6879       case PlayFromGameFile:
6880             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6881       case EditGame:
6882       case AnalyzeMode:
6883         if (!white_piece && WhiteOnMove(currentMove)) {
6884             DisplayMoveError(_("It is White's turn"));
6885             return FALSE;
6886         }
6887         if (white_piece && !WhiteOnMove(currentMove)) {
6888             DisplayMoveError(_("It is Black's turn"));
6889             return FALSE;
6890         }
6891         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6892             /* Editing correspondence game history */
6893             /* Could disallow this or prompt for confirmation */
6894             cmailOldMove = -1;
6895         }
6896         break;
6897
6898       case BeginningOfGame:
6899         if (appData.icsActive) return FALSE;
6900         if (!appData.noChessProgram) {
6901             if (!white_piece) {
6902                 DisplayMoveError(_("You are playing White"));
6903                 return FALSE;
6904             }
6905         }
6906         break;
6907
6908       case Training:
6909         if (!white_piece && WhiteOnMove(currentMove)) {
6910             DisplayMoveError(_("It is White's turn"));
6911             return FALSE;
6912         }
6913         if (white_piece && !WhiteOnMove(currentMove)) {
6914             DisplayMoveError(_("It is Black's turn"));
6915             return FALSE;
6916         }
6917         break;
6918
6919       default:
6920       case IcsExamining:
6921         break;
6922     }
6923     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6924         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6925         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6926         && gameMode != AnalyzeFile && gameMode != Training) {
6927         DisplayMoveError(_("Displayed position is not current"));
6928         return FALSE;
6929     }
6930     return TRUE;
6931 }
6932
6933 Boolean
6934 OnlyMove (int *x, int *y, Boolean captures)
6935 {
6936     DisambiguateClosure cl;
6937     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6938     switch(gameMode) {
6939       case MachinePlaysBlack:
6940       case IcsPlayingWhite:
6941       case BeginningOfGame:
6942         if(!WhiteOnMove(currentMove)) return FALSE;
6943         break;
6944       case MachinePlaysWhite:
6945       case IcsPlayingBlack:
6946         if(WhiteOnMove(currentMove)) return FALSE;
6947         break;
6948       case EditGame:
6949         break;
6950       default:
6951         return FALSE;
6952     }
6953     cl.pieceIn = EmptySquare;
6954     cl.rfIn = *y;
6955     cl.ffIn = *x;
6956     cl.rtIn = -1;
6957     cl.ftIn = -1;
6958     cl.promoCharIn = NULLCHAR;
6959     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6960     if( cl.kind == NormalMove ||
6961         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6962         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6963         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6964       fromX = cl.ff;
6965       fromY = cl.rf;
6966       *x = cl.ft;
6967       *y = cl.rt;
6968       return TRUE;
6969     }
6970     if(cl.kind != ImpossibleMove) return FALSE;
6971     cl.pieceIn = EmptySquare;
6972     cl.rfIn = -1;
6973     cl.ffIn = -1;
6974     cl.rtIn = *y;
6975     cl.ftIn = *x;
6976     cl.promoCharIn = NULLCHAR;
6977     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6978     if( cl.kind == NormalMove ||
6979         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6980         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6981         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6982       fromX = cl.ff;
6983       fromY = cl.rf;
6984       *x = cl.ft;
6985       *y = cl.rt;
6986       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6987       return TRUE;
6988     }
6989     return FALSE;
6990 }
6991
6992 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6993 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6994 int lastLoadGameUseList = FALSE;
6995 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6996 ChessMove lastLoadGameStart = EndOfFile;
6997 int doubleClick;
6998 Boolean addToBookFlag;
6999 static Board rightsBoard, nullBoard;
7000
7001 void
7002 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7003 {
7004     ChessMove moveType;
7005     ChessSquare pup;
7006     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7007
7008     /* Check if the user is playing in turn.  This is complicated because we
7009        let the user "pick up" a piece before it is his turn.  So the piece he
7010        tried to pick up may have been captured by the time he puts it down!
7011        Therefore we use the color the user is supposed to be playing in this
7012        test, not the color of the piece that is currently on the starting
7013        square---except in EditGame mode, where the user is playing both
7014        sides; fortunately there the capture race can't happen.  (It can
7015        now happen in IcsExamining mode, but that's just too bad.  The user
7016        will get a somewhat confusing message in that case.)
7017        */
7018
7019     switch (gameMode) {
7020       case AnalyzeFile:
7021       case TwoMachinesPlay:
7022       case EndOfGame:
7023       case IcsObserving:
7024       case IcsIdle:
7025         /* We switched into a game mode where moves are not accepted,
7026            perhaps while the mouse button was down. */
7027         return;
7028
7029       case MachinePlaysWhite:
7030         /* User is moving for Black */
7031         if (WhiteOnMove(currentMove)) {
7032             DisplayMoveError(_("It is White's turn"));
7033             return;
7034         }
7035         break;
7036
7037       case MachinePlaysBlack:
7038         /* User is moving for White */
7039         if (!WhiteOnMove(currentMove)) {
7040             DisplayMoveError(_("It is Black's turn"));
7041             return;
7042         }
7043         break;
7044
7045       case PlayFromGameFile:
7046             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7047       case EditGame:
7048       case IcsExamining:
7049       case BeginningOfGame:
7050       case AnalyzeMode:
7051       case Training:
7052         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7053         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7054             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7055             /* User is moving for Black */
7056             if (WhiteOnMove(currentMove)) {
7057                 DisplayMoveError(_("It is White's turn"));
7058                 return;
7059             }
7060         } else {
7061             /* User is moving for White */
7062             if (!WhiteOnMove(currentMove)) {
7063                 DisplayMoveError(_("It is Black's turn"));
7064                 return;
7065             }
7066         }
7067         break;
7068
7069       case IcsPlayingBlack:
7070         /* User is moving for Black */
7071         if (WhiteOnMove(currentMove)) {
7072             if (!appData.premove) {
7073                 DisplayMoveError(_("It is White's turn"));
7074             } else if (toX >= 0 && toY >= 0) {
7075                 premoveToX = toX;
7076                 premoveToY = toY;
7077                 premoveFromX = fromX;
7078                 premoveFromY = fromY;
7079                 premovePromoChar = promoChar;
7080                 gotPremove = 1;
7081                 if (appData.debugMode)
7082                     fprintf(debugFP, "Got premove: fromX %d,"
7083                             "fromY %d, toX %d, toY %d\n",
7084                             fromX, fromY, toX, toY);
7085             }
7086             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7087             return;
7088         }
7089         break;
7090
7091       case IcsPlayingWhite:
7092         /* User is moving for White */
7093         if (!WhiteOnMove(currentMove)) {
7094             if (!appData.premove) {
7095                 DisplayMoveError(_("It is Black's turn"));
7096             } else if (toX >= 0 && toY >= 0) {
7097                 premoveToX = toX;
7098                 premoveToY = toY;
7099                 premoveFromX = fromX;
7100                 premoveFromY = fromY;
7101                 premovePromoChar = promoChar;
7102                 gotPremove = 1;
7103                 if (appData.debugMode)
7104                     fprintf(debugFP, "Got premove: fromX %d,"
7105                             "fromY %d, toX %d, toY %d\n",
7106                             fromX, fromY, toX, toY);
7107             }
7108             DrawPosition(TRUE, boards[currentMove]);
7109             return;
7110         }
7111         break;
7112
7113       default:
7114         break;
7115
7116       case EditPosition:
7117         /* EditPosition, empty square, or different color piece;
7118            click-click move is possible */
7119         if (toX == -2 || toY == -2) {
7120             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7121             DrawPosition(FALSE, boards[currentMove]);
7122             return;
7123         } else if (toX >= 0 && toY >= 0) {
7124             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7125                 ChessSquare p = boards[0][rf][ff];
7126                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7127                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7128                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7129                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7130                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7131                     gatingPiece = p;
7132                 }
7133             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7134             boards[0][toY][toX] = boards[0][fromY][fromX];
7135             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7136                 if(boards[0][fromY][0] != EmptySquare) {
7137                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7138                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7139                 }
7140             } else
7141             if(fromX == BOARD_RGHT+1) {
7142                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7143                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7144                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7145                 }
7146             } else
7147             boards[0][fromY][fromX] = gatingPiece;
7148             ClearHighlights();
7149             DrawPosition(FALSE, boards[currentMove]);
7150             return;
7151         }
7152         return;
7153     }
7154
7155     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7156     pup = boards[currentMove][toY][toX];
7157
7158     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7159     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7160          if( pup != EmptySquare ) return;
7161          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7162            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7163                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7164            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7165            if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7166            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7167            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7168          fromY = DROP_RANK;
7169     }
7170
7171     /* [HGM] always test for legality, to get promotion info */
7172     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7173                                          fromY, fromX, toY, toX, promoChar);
7174
7175     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7176
7177     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7178
7179     /* [HGM] but possibly ignore an IllegalMove result */
7180     if (appData.testLegality) {
7181         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7182             DisplayMoveError(_("Illegal move"));
7183             return;
7184         }
7185     }
7186
7187     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7188         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7189              ClearPremoveHighlights(); // was included
7190         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7191         DrawPosition(FALSE, NULL);
7192         return;
7193     }
7194
7195     if(addToBookFlag) { // adding moves to book
7196         char buf[MSG_SIZ], move[MSG_SIZ];
7197         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7198         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7199                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7200         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7201         AddBookMove(buf);
7202         addToBookFlag = FALSE;
7203         ClearHighlights();
7204         return;
7205     }
7206
7207     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7208 }
7209
7210 /* Common tail of UserMoveEvent and DropMenuEvent */
7211 int
7212 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7213 {
7214     char *bookHit = 0;
7215
7216     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7217         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7218         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7219         if(WhiteOnMove(currentMove)) {
7220             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7221         } else {
7222             if(!boards[currentMove][handSize-1-k][1]) return 0;
7223         }
7224     }
7225
7226     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7227        move type in caller when we know the move is a legal promotion */
7228     if(moveType == NormalMove && promoChar)
7229         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7230
7231     /* [HGM] <popupFix> The following if has been moved here from
7232        UserMoveEvent(). Because it seemed to belong here (why not allow
7233        piece drops in training games?), and because it can only be
7234        performed after it is known to what we promote. */
7235     if (gameMode == Training) {
7236       /* compare the move played on the board to the next move in the
7237        * game. If they match, display the move and the opponent's response.
7238        * If they don't match, display an error message.
7239        */
7240       int saveAnimate;
7241       Board testBoard;
7242       CopyBoard(testBoard, boards[currentMove]);
7243       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7244
7245       if (CompareBoards(testBoard, boards[currentMove+1])) {
7246         ForwardInner(currentMove+1);
7247
7248         /* Autoplay the opponent's response.
7249          * if appData.animate was TRUE when Training mode was entered,
7250          * the response will be animated.
7251          */
7252         saveAnimate = appData.animate;
7253         appData.animate = animateTraining;
7254         ForwardInner(currentMove+1);
7255         appData.animate = saveAnimate;
7256
7257         /* check for the end of the game */
7258         if (currentMove >= forwardMostMove) {
7259           gameMode = PlayFromGameFile;
7260           ModeHighlight();
7261           SetTrainingModeOff();
7262           DisplayInformation(_("End of game"));
7263         }
7264       } else {
7265         DisplayError(_("Incorrect move"), 0);
7266       }
7267       return 1;
7268     }
7269
7270   /* Ok, now we know that the move is good, so we can kill
7271      the previous line in Analysis Mode */
7272   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7273                                 && currentMove < forwardMostMove) {
7274     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7275     else forwardMostMove = currentMove;
7276   }
7277
7278   ClearMap();
7279
7280   /* If we need the chess program but it's dead, restart it */
7281   ResurrectChessProgram();
7282
7283   /* A user move restarts a paused game*/
7284   if (pausing)
7285     PauseEvent();
7286
7287   thinkOutput[0] = NULLCHAR;
7288
7289   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7290
7291   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7292     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7293     return 1;
7294   }
7295
7296   if (gameMode == BeginningOfGame) {
7297     if (appData.noChessProgram) {
7298       gameMode = EditGame;
7299       SetGameInfo();
7300     } else {
7301       char buf[MSG_SIZ];
7302       gameMode = MachinePlaysBlack;
7303       StartClocks();
7304       SetGameInfo();
7305       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7306       DisplayTitle(buf);
7307       if (first.sendName) {
7308         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7309         SendToProgram(buf, &first);
7310       }
7311       StartClocks();
7312     }
7313     ModeHighlight();
7314   }
7315
7316   /* Relay move to ICS or chess engine */
7317   if (appData.icsActive) {
7318     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7319         gameMode == IcsExamining) {
7320       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7321         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7322         SendToICS("draw ");
7323         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7324       }
7325       // also send plain move, in case ICS does not understand atomic claims
7326       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7327       ics_user_moved = 1;
7328     }
7329   } else {
7330     if (first.sendTime && (gameMode == BeginningOfGame ||
7331                            gameMode == MachinePlaysWhite ||
7332                            gameMode == MachinePlaysBlack)) {
7333       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7334     }
7335     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7336          // [HGM] book: if program might be playing, let it use book
7337         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7338         first.maybeThinking = TRUE;
7339     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7340         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7341         SendBoard(&first, currentMove+1);
7342         if(second.analyzing) {
7343             if(!second.useSetboard) SendToProgram("undo\n", &second);
7344             SendBoard(&second, currentMove+1);
7345         }
7346     } else {
7347         SendMoveToProgram(forwardMostMove-1, &first);
7348         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7349     }
7350     if (currentMove == cmailOldMove + 1) {
7351       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7352     }
7353   }
7354
7355   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7356
7357   switch (gameMode) {
7358   case EditGame:
7359     if(appData.testLegality)
7360     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7361     case MT_NONE:
7362     case MT_CHECK:
7363       break;
7364     case MT_CHECKMATE:
7365     case MT_STAINMATE:
7366       if (WhiteOnMove(currentMove)) {
7367         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7368       } else {
7369         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7370       }
7371       break;
7372     case MT_STALEMATE:
7373       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7374       break;
7375     }
7376     break;
7377
7378   case MachinePlaysBlack:
7379   case MachinePlaysWhite:
7380     /* disable certain menu options while machine is thinking */
7381     SetMachineThinkingEnables();
7382     break;
7383
7384   default:
7385     break;
7386   }
7387
7388   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7389   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7390
7391   if(bookHit) { // [HGM] book: simulate book reply
7392         static char bookMove[MSG_SIZ]; // a bit generous?
7393
7394         programStats.nodes = programStats.depth = programStats.time =
7395         programStats.score = programStats.got_only_move = 0;
7396         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7397
7398         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7399         strcat(bookMove, bookHit);
7400         HandleMachineMove(bookMove, &first);
7401   }
7402   return 1;
7403 }
7404
7405 void
7406 MarkByFEN(char *fen)
7407 {
7408         int r, f;
7409         if(!appData.markers || !appData.highlightDragging) return;
7410         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7411         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7412         while(*fen) {
7413             int s = 0;
7414             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7415             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7416             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7417             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7418             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7419             if(*fen == 'T') marker[r][f++] = 0; else
7420             if(*fen == 'Y') marker[r][f++] = 1; else
7421             if(*fen == 'G') marker[r][f++] = 3; else
7422             if(*fen == 'B') marker[r][f++] = 4; else
7423             if(*fen == 'C') marker[r][f++] = 5; else
7424             if(*fen == 'M') marker[r][f++] = 6; else
7425             if(*fen == 'W') marker[r][f++] = 7; else
7426             if(*fen == 'D') marker[r][f++] = 8; else
7427             if(*fen == 'R') marker[r][f++] = 2; else {
7428                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7429               f += s; fen -= s>0;
7430             }
7431             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7432             if(r < 0) break;
7433             fen++;
7434         }
7435         DrawPosition(TRUE, NULL);
7436 }
7437
7438 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7439
7440 void
7441 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7442 {
7443     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7444     Markers *m = (Markers *) closure;
7445     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7446                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7447         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7448                          || kind == WhiteCapturesEnPassant
7449                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7450     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7451 }
7452
7453 static int hoverSavedValid;
7454
7455 void
7456 MarkTargetSquares (int clear)
7457 {
7458   int x, y, sum=0;
7459   if(clear) { // no reason to ever suppress clearing
7460     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7461     hoverSavedValid = 0;
7462     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7463   } else {
7464     int capt = 0;
7465     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7466        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7467     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7468     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7469       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7470       if(capt)
7471       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = legal[y][x] = 0;
7472     }
7473   }
7474   DrawPosition(FALSE, NULL);
7475 }
7476
7477 int
7478 Explode (Board board, int fromX, int fromY, int toX, int toY)
7479 {
7480     if(gameInfo.variant == VariantAtomic &&
7481        (board[toY][toX] != EmptySquare ||                     // capture?
7482         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7483                          board[fromY][fromX] == BlackPawn   )
7484       )) {
7485         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7486         return TRUE;
7487     }
7488     return FALSE;
7489 }
7490
7491 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7492
7493 int
7494 CanPromote (ChessSquare piece, int y)
7495 {
7496         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7497         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7498         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7499         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7500            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7501           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7502            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7503         return (piece == BlackPawn && y <= zone ||
7504                 piece == WhitePawn && y >= BOARD_HEIGHT-1-deadRanks-zone ||
7505                 piece == BlackLance && y <= zone ||
7506                 piece == WhiteLance && y >= BOARD_HEIGHT-1-deadRanks-zone );
7507 }
7508
7509 void
7510 HoverEvent (int xPix, int yPix, int x, int y)
7511 {
7512         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7513         int r, f;
7514         if(!first.highlight) return;
7515         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7516         if(x == oldX && y == oldY) return; // only do something if we enter new square
7517         oldFromX = fromX; oldFromY = fromY;
7518         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7519           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7520             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7521           hoverSavedValid = 1;
7522         } else if(oldX != x || oldY != y) {
7523           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7524           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7525           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7526             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7527           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7528             char buf[MSG_SIZ];
7529             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7530             SendToProgram(buf, &first);
7531           }
7532           oldX = x; oldY = y;
7533 //        SetHighlights(fromX, fromY, x, y);
7534         }
7535 }
7536
7537 void ReportClick(char *action, int x, int y)
7538 {
7539         char buf[MSG_SIZ]; // Inform engine of what user does
7540         int r, f;
7541         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7542           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7543             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7544         if(!first.highlight || gameMode == EditPosition) return;
7545         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7546         SendToProgram(buf, &first);
7547 }
7548
7549 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7550 Boolean deferChoice;
7551
7552 void
7553 LeftClick (ClickType clickType, int xPix, int yPix)
7554 {
7555     int x, y;
7556     static Boolean saveAnimate;
7557     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7558     char promoChoice = NULLCHAR;
7559     ChessSquare piece;
7560     static TimeMark lastClickTime, prevClickTime;
7561
7562     if(flashing) return;
7563
7564   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7565     x = EventToSquare(xPix, BOARD_WIDTH);
7566     y = EventToSquare(yPix, BOARD_HEIGHT);
7567     if (!flipView && y >= 0) {
7568         y = BOARD_HEIGHT - 1 - y;
7569     }
7570     if (flipView && x >= 0) {
7571         x = BOARD_WIDTH - 1 - x;
7572     }
7573
7574     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7575         static int dummy;
7576         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7577         right = TRUE;
7578         return;
7579     }
7580
7581     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7582
7583     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7584
7585     if (clickType == Press) ErrorPopDown();
7586     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7587
7588     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7589         defaultPromoChoice = promoSweep;
7590         promoSweep = EmptySquare;   // terminate sweep
7591         promoDefaultAltered = TRUE;
7592         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7593     }
7594
7595     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7596         if(clickType == Release) return; // ignore upclick of click-click destination
7597         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7598         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7599         if(gameInfo.holdingsWidth &&
7600                 (WhiteOnMove(currentMove)
7601                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7602                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7603             // click in right holdings, for determining promotion piece
7604             ChessSquare p = boards[currentMove][y][x];
7605             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7606             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7607             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7608                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7609                 fromX = fromY = -1;
7610                 return;
7611             }
7612         }
7613         DrawPosition(FALSE, boards[currentMove]);
7614         return;
7615     }
7616
7617     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7618     if(clickType == Press
7619             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7620               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7621               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7622         return;
7623
7624     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7625         // could be static click on premove from-square: abort premove
7626         gotPremove = 0;
7627         ClearPremoveHighlights();
7628     }
7629
7630     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7631         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7632
7633     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7634         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7635                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7636         defaultPromoChoice = DefaultPromoChoice(side);
7637     }
7638
7639     autoQueen = appData.alwaysPromoteToQueen;
7640
7641     if (fromX == -1) {
7642       int originalY = y;
7643       gatingPiece = EmptySquare;
7644       if (clickType != Press) {
7645         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7646             DragPieceEnd(xPix, yPix); dragging = 0;
7647             DrawPosition(FALSE, NULL);
7648         }
7649         return;
7650       }
7651       doubleClick = FALSE;
7652       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7653         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7654       }
7655       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7656       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7657          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7658          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7659             /* First square */
7660             if (OKToStartUserMove(fromX, fromY)) {
7661                 second = 0;
7662                 ReportClick("lift", x, y);
7663                 MarkTargetSquares(0);
7664                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7665                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7666                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7667                     promoSweep = defaultPromoChoice;
7668                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7669                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7670                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7671                 }
7672                 if (appData.highlightDragging) {
7673                     SetHighlights(fromX, fromY, -1, -1);
7674                 } else {
7675                     ClearHighlights();
7676                 }
7677             } else fromX = fromY = -1;
7678             return;
7679         }
7680     }
7681
7682     /* fromX != -1 */
7683     if (clickType == Press && gameMode != EditPosition) {
7684         ChessSquare fromP;
7685         ChessSquare toP;
7686         int frc;
7687
7688         // ignore off-board to clicks
7689         if(y < 0 || x < 0) return;
7690
7691         /* Check if clicking again on the same color piece */
7692         fromP = boards[currentMove][fromY][fromX];
7693         toP = boards[currentMove][y][x];
7694         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7695         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7696             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7697            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7698              WhitePawn <= toP && toP <= WhiteKing &&
7699              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7700              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7701             (BlackPawn <= fromP && fromP <= BlackKing &&
7702              BlackPawn <= toP && toP <= BlackKing &&
7703              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7704              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7705             /* Clicked again on same color piece -- changed his mind */
7706             second = (x == fromX && y == fromY);
7707             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7708             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7709                 second = FALSE; // first double-click rather than scond click
7710                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7711             }
7712             promoDefaultAltered = FALSE;
7713            if(!second) MarkTargetSquares(1);
7714            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7715             if (appData.highlightDragging) {
7716                 SetHighlights(x, y, -1, -1);
7717             } else {
7718                 ClearHighlights();
7719             }
7720             if (OKToStartUserMove(x, y)) {
7721                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7722                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7723                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7724                  gatingPiece = boards[currentMove][fromY][fromX];
7725                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7726                 fromX = x;
7727                 fromY = y; dragging = 1;
7728                 if(!second) ReportClick("lift", x, y);
7729                 MarkTargetSquares(0);
7730                 DragPieceBegin(xPix, yPix, FALSE);
7731                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7732                     promoSweep = defaultPromoChoice;
7733                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7734                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7735                 }
7736             }
7737            }
7738            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7739            second = FALSE;
7740         }
7741         // ignore clicks on holdings
7742         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7743     }
7744
7745     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7746         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7747         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7748         return;
7749     }
7750
7751     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7752         DragPieceEnd(xPix, yPix); dragging = 0;
7753         if(clearFlag) {
7754             // a deferred attempt to click-click move an empty square on top of a piece
7755             boards[currentMove][y][x] = EmptySquare;
7756             ClearHighlights();
7757             DrawPosition(FALSE, boards[currentMove]);
7758             fromX = fromY = -1; clearFlag = 0;
7759             return;
7760         }
7761         if (appData.animateDragging) {
7762             /* Undo animation damage if any */
7763             DrawPosition(FALSE, NULL);
7764         }
7765         if (second) {
7766             /* Second up/down in same square; just abort move */
7767             second = 0;
7768             fromX = fromY = -1;
7769             gatingPiece = EmptySquare;
7770             ClearHighlights();
7771             gotPremove = 0;
7772             ClearPremoveHighlights();
7773             MarkTargetSquares(-1);
7774             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7775         } else {
7776             /* First upclick in same square; start click-click mode */
7777             SetHighlights(x, y, -1, -1);
7778         }
7779         return;
7780     }
7781
7782     clearFlag = 0;
7783
7784     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7785        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7786         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7787         DisplayMessage(_("only marked squares are legal"),"");
7788         DrawPosition(TRUE, NULL);
7789         return; // ignore to-click
7790     }
7791
7792     /* we now have a different from- and (possibly off-board) to-square */
7793     /* Completed move */
7794     if(!sweepSelecting) {
7795         toX = x;
7796         toY = y;
7797     }
7798
7799     piece = boards[currentMove][fromY][fromX];
7800
7801     saveAnimate = appData.animate;
7802     if (clickType == Press) {
7803         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7804         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7805             // must be Edit Position mode with empty-square selected
7806             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7807             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7808             return;
7809         }
7810         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7811             return;
7812         }
7813         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7814             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7815         } else
7816         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7817         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7818           if(appData.sweepSelect) {
7819             promoSweep = defaultPromoChoice;
7820             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7821             selectFlag = 0; lastX = xPix; lastY = yPix;
7822             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7823             saveFlash = appData.flashCount; appData.flashCount = 0;
7824             Sweep(0); // Pawn that is going to promote: preview promotion piece
7825             sweepSelecting = 1;
7826             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7827             MarkTargetSquares(1);
7828           }
7829           return; // promo popup appears on up-click
7830         }
7831         /* Finish clickclick move */
7832         if (appData.animate || appData.highlightLastMove) {
7833             SetHighlights(fromX, fromY, toX, toY);
7834         } else {
7835             ClearHighlights();
7836         }
7837         MarkTargetSquares(1);
7838     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7839         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7840         *promoRestrict = 0; appData.flashCount = saveFlash;
7841         if (appData.animate || appData.highlightLastMove) {
7842             SetHighlights(fromX, fromY, toX, toY);
7843         } else {
7844             ClearHighlights();
7845         }
7846         MarkTargetSquares(1);
7847     } else {
7848 #if 0
7849 // [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
7850         /* Finish drag move */
7851         if (appData.highlightLastMove) {
7852             SetHighlights(fromX, fromY, toX, toY);
7853         } else {
7854             ClearHighlights();
7855         }
7856 #endif
7857         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7858           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7859         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7860         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7861           dragging *= 2;            // flag button-less dragging if we are dragging
7862           MarkTargetSquares(1);
7863           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7864           else {
7865             kill2X = killX; kill2Y = killY;
7866             killX = x; killY = y;     // remember this square as intermediate
7867             ReportClick("put", x, y); // and inform engine
7868             ReportClick("lift", x, y);
7869             MarkTargetSquares(0);
7870             return;
7871           }
7872         }
7873         DragPieceEnd(xPix, yPix); dragging = 0;
7874         /* Don't animate move and drag both */
7875         appData.animate = FALSE;
7876         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7877     }
7878
7879     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7880     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7881         ChessSquare piece = boards[currentMove][fromY][fromX];
7882         if(gameMode == EditPosition && piece != EmptySquare &&
7883            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7884             int n;
7885
7886             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7887                 n = PieceToNumber(piece - (int)BlackPawn);
7888                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7889                 boards[currentMove][handSize-1 - n][0] = piece;
7890                 boards[currentMove][handSize-1 - n][1]++;
7891             } else
7892             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7893                 n = PieceToNumber(piece);
7894                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7895                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7896                 boards[currentMove][n][BOARD_WIDTH-2]++;
7897             }
7898             boards[currentMove][fromY][fromX] = EmptySquare;
7899         }
7900         ClearHighlights();
7901         fromX = fromY = -1;
7902         MarkTargetSquares(1);
7903         DrawPosition(TRUE, boards[currentMove]);
7904         return;
7905     }
7906
7907     // off-board moves should not be highlighted
7908     if(x < 0 || y < 0) {
7909         ClearHighlights();
7910         DrawPosition(FALSE, NULL);
7911     } else ReportClick("put", x, y);
7912
7913     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7914  }
7915
7916     if(legal[toY][toX] == 2) { // highlight-induced promotion
7917         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7918         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7919     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7920       if(!*promoRestrict) {           // but has not done that yet
7921         deferChoice = TRUE;           // set up retry for when it does
7922         return;                       // and wait for that
7923       }
7924       promoChoice = ToLower(*promoRestrict); // force engine's choice
7925       deferChoice = FALSE;
7926     }
7927
7928     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7929         SetHighlights(fromX, fromY, toX, toY);
7930         MarkTargetSquares(1);
7931         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7932             // [HGM] super: promotion to captured piece selected from holdings
7933             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7934             promotionChoice = TRUE;
7935             // kludge follows to temporarily execute move on display, without promoting yet
7936             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7937             boards[currentMove][toY][toX] = p;
7938             DrawPosition(FALSE, boards[currentMove]);
7939             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7940             boards[currentMove][toY][toX] = q;
7941             DisplayMessage("Click in holdings to choose piece", "");
7942             return;
7943         }
7944         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7945         PromotionPopUp(promoChoice);
7946     } else {
7947         int oldMove = currentMove;
7948         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7949         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7950         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7951         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7952         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7953            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7954             DrawPosition(TRUE, boards[currentMove]);
7955         else DrawPosition(FALSE, NULL);
7956         fromX = fromY = -1;
7957         flashing = 0;
7958     }
7959     appData.animate = saveAnimate;
7960     if (appData.animate || appData.animateDragging) {
7961         /* Undo animation damage if needed */
7962 //      DrawPosition(FALSE, NULL);
7963     }
7964 }
7965
7966 int
7967 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7968 {   // front-end-free part taken out of PieceMenuPopup
7969     int whichMenu; int xSqr, ySqr;
7970
7971     if(seekGraphUp) { // [HGM] seekgraph
7972         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7973         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7974         return -2;
7975     }
7976
7977     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7978          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7979         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7980         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7981         if(action == Press)   {
7982             originalFlip = flipView;
7983             flipView = !flipView; // temporarily flip board to see game from partners perspective
7984             DrawPosition(TRUE, partnerBoard);
7985             DisplayMessage(partnerStatus, "");
7986             partnerUp = TRUE;
7987         } else if(action == Release) {
7988             flipView = originalFlip;
7989             DrawPosition(TRUE, boards[currentMove]);
7990             partnerUp = FALSE;
7991         }
7992         return -2;
7993     }
7994
7995     xSqr = EventToSquare(x, BOARD_WIDTH);
7996     ySqr = EventToSquare(y, BOARD_HEIGHT);
7997     if (action == Release) {
7998         if(pieceSweep != EmptySquare) {
7999             EditPositionMenuEvent(pieceSweep, toX, toY);
8000             pieceSweep = EmptySquare;
8001         } else UnLoadPV(); // [HGM] pv
8002     }
8003     if (action != Press) return -2; // return code to be ignored
8004     switch (gameMode) {
8005       case IcsExamining:
8006         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8007       case EditPosition:
8008         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8009         if (xSqr < 0 || ySqr < 0) return -1;
8010         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8011         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8012         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8013         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8014         NextPiece(0);
8015         return 2; // grab
8016       case IcsObserving:
8017         if(!appData.icsEngineAnalyze) return -1;
8018       case IcsPlayingWhite:
8019       case IcsPlayingBlack:
8020         if(!appData.zippyPlay) goto noZip;
8021       case AnalyzeMode:
8022       case AnalyzeFile:
8023       case MachinePlaysWhite:
8024       case MachinePlaysBlack:
8025       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8026         if (!appData.dropMenu) {
8027           LoadPV(x, y);
8028           return 2; // flag front-end to grab mouse events
8029         }
8030         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8031            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8032       case EditGame:
8033       noZip:
8034         if (xSqr < 0 || ySqr < 0) return -1;
8035         if (!appData.dropMenu || appData.testLegality &&
8036             gameInfo.variant != VariantBughouse &&
8037             gameInfo.variant != VariantCrazyhouse) return -1;
8038         whichMenu = 1; // drop menu
8039         break;
8040       default:
8041         return -1;
8042     }
8043
8044     if (((*fromX = xSqr) < 0) ||
8045         ((*fromY = ySqr) < 0)) {
8046         *fromX = *fromY = -1;
8047         return -1;
8048     }
8049     if (flipView)
8050       *fromX = BOARD_WIDTH - 1 - *fromX;
8051     else
8052       *fromY = BOARD_HEIGHT - 1 - *fromY;
8053
8054     return whichMenu;
8055 }
8056
8057 void
8058 Wheel (int dir, int x, int y)
8059 {
8060     if(gameMode == EditPosition) {
8061         int xSqr = EventToSquare(x, BOARD_WIDTH);
8062         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8063         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8064         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8065         do {
8066             boards[currentMove][ySqr][xSqr] += dir;
8067             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8068             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8069         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8070         DrawPosition(FALSE, boards[currentMove]);
8071     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8072 }
8073
8074 void
8075 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8076 {
8077 //    char * hint = lastHint;
8078     FrontEndProgramStats stats;
8079
8080     stats.which = cps == &first ? 0 : 1;
8081     stats.depth = cpstats->depth;
8082     stats.nodes = cpstats->nodes;
8083     stats.score = cpstats->score;
8084     stats.time = cpstats->time;
8085     stats.pv = cpstats->movelist;
8086     stats.hint = lastHint;
8087     stats.an_move_index = 0;
8088     stats.an_move_count = 0;
8089
8090     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8091         stats.hint = cpstats->move_name;
8092         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8093         stats.an_move_count = cpstats->nr_moves;
8094     }
8095
8096     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
8097
8098     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8099         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8100
8101     SetProgramStats( &stats );
8102 }
8103
8104 void
8105 ClearEngineOutputPane (int which)
8106 {
8107     static FrontEndProgramStats dummyStats;
8108     dummyStats.which = which;
8109     dummyStats.pv = "#";
8110     SetProgramStats( &dummyStats );
8111 }
8112
8113 #define MAXPLAYERS 500
8114
8115 char *
8116 TourneyStandings (int display)
8117 {
8118     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8119     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8120     char result, *p, *names[MAXPLAYERS];
8121
8122     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8123         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8124     names[0] = p = strdup(appData.participants);
8125     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8126
8127     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8128
8129     while(result = appData.results[nr]) {
8130         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8131         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8132         wScore = bScore = 0;
8133         switch(result) {
8134           case '+': wScore = 2; break;
8135           case '-': bScore = 2; break;
8136           case '=': wScore = bScore = 1; break;
8137           case ' ':
8138           case '*': return strdup("busy"); // tourney not finished
8139         }
8140         score[w] += wScore;
8141         score[b] += bScore;
8142         games[w]++;
8143         games[b]++;
8144         nr++;
8145     }
8146     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8147     for(w=0; w<nPlayers; w++) {
8148         bScore = -1;
8149         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8150         ranking[w] = b; points[w] = bScore; score[b] = -2;
8151     }
8152     p = malloc(nPlayers*34+1);
8153     for(w=0; w<nPlayers && w<display; w++)
8154         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8155     free(names[0]);
8156     return p;
8157 }
8158
8159 void
8160 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8161 {       // count all piece types
8162         int p, f, r;
8163         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8164         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8165         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8166                 p = board[r][f];
8167                 pCnt[p]++;
8168                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8169                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8170                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8171                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8172                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8173                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8174         }
8175 }
8176
8177 int
8178 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8179 {
8180         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8181         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8182
8183         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8184         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8185         if(myPawns == 2 && nMine == 3) // KPP
8186             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8187         if(myPawns == 1 && nMine == 2) // KP
8188             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8189         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8190             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8191         if(myPawns) return FALSE;
8192         if(pCnt[WhiteRook+side])
8193             return pCnt[BlackRook-side] ||
8194                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8195                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8196                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8197         if(pCnt[WhiteCannon+side]) {
8198             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8199             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8200         }
8201         if(pCnt[WhiteKnight+side])
8202             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8203         return FALSE;
8204 }
8205
8206 int
8207 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8208 {
8209         VariantClass v = gameInfo.variant;
8210
8211         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8212         if(v == VariantShatranj) return TRUE; // always winnable through baring
8213         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8214         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8215
8216         if(v == VariantXiangqi) {
8217                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8218
8219                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8220                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8221                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8222                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8223                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8224                 if(stale) // we have at least one last-rank P plus perhaps C
8225                     return majors // KPKX
8226                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8227                 else // KCA*E*
8228                     return pCnt[WhiteFerz+side] // KCAK
8229                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8230                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8231                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8232
8233         } else if(v == VariantKnightmate) {
8234                 if(nMine == 1) return FALSE;
8235                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8236         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8237                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8238
8239                 if(nMine == 1) return FALSE; // bare King
8240                 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
8241                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8242                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8243                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8244                 if(pCnt[WhiteKnight+side])
8245                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8246                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8247                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8248                 if(nBishops)
8249                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8250                 if(pCnt[WhiteAlfil+side])
8251                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8252                 if(pCnt[WhiteWazir+side])
8253                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8254         }
8255
8256         return TRUE;
8257 }
8258
8259 int
8260 CompareWithRights (Board b1, Board b2)
8261 {
8262     int rights = 0;
8263     if(!CompareBoards(b1, b2)) return FALSE;
8264     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8265     /* compare castling rights */
8266     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8267            rights++; /* King lost rights, while rook still had them */
8268     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8269         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8270            rights++; /* but at least one rook lost them */
8271     }
8272     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8273            rights++;
8274     if( b1[CASTLING][5] != NoRights ) {
8275         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8276            rights++;
8277     }
8278     return rights == 0;
8279 }
8280
8281 int
8282 Adjudicate (ChessProgramState *cps)
8283 {       // [HGM] some adjudications useful with buggy engines
8284         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8285         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8286         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8287         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8288         int k, drop, count = 0; static int bare = 1;
8289         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8290         Boolean canAdjudicate = !appData.icsActive;
8291
8292         // most tests only when we understand the game, i.e. legality-checking on
8293             if( appData.testLegality )
8294             {   /* [HGM] Some more adjudications for obstinate engines */
8295                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8296                 static int moveCount = 6;
8297                 ChessMove result;
8298                 char *reason = NULL;
8299
8300                 /* Count what is on board. */
8301                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8302
8303                 /* Some material-based adjudications that have to be made before stalemate test */
8304                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8305                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8306                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8307                      if(canAdjudicate && appData.checkMates) {
8308                          if(engineOpponent)
8309                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8310                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8311                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8312                          return 1;
8313                      }
8314                 }
8315
8316                 /* Bare King in Shatranj (loses) or Losers (wins) */
8317                 if( nrW == 1 || nrB == 1) {
8318                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8319                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8320                      if(canAdjudicate && appData.checkMates) {
8321                          if(engineOpponent)
8322                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8323                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8324                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8325                          return 1;
8326                      }
8327                   } else
8328                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8329                   {    /* bare King */
8330                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8331                         if(canAdjudicate && appData.checkMates) {
8332                             /* but only adjudicate if adjudication enabled */
8333                             if(engineOpponent)
8334                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8335                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8336                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8337                             return 1;
8338                         }
8339                   }
8340                 } else bare = 1;
8341
8342
8343             // don't wait for engine to announce game end if we can judge ourselves
8344             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8345               case MT_CHECK:
8346                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8347                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8348                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8349                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8350                             checkCnt++;
8351                         if(checkCnt >= 2) {
8352                             reason = "Xboard adjudication: 3rd check";
8353                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8354                             break;
8355                         }
8356                     }
8357                 }
8358               case MT_NONE:
8359               default:
8360                 break;
8361               case MT_STEALMATE:
8362               case MT_STALEMATE:
8363               case MT_STAINMATE:
8364                 reason = "Xboard adjudication: Stalemate";
8365                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8366                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8367                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8368                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8369                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8370                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8371                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8372                                                                         EP_CHECKMATE : EP_WINS);
8373                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8374                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8375                 }
8376                 break;
8377               case MT_CHECKMATE:
8378                 reason = "Xboard adjudication: Checkmate";
8379                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8380                 if(gameInfo.variant == VariantShogi) {
8381                     if(forwardMostMove > backwardMostMove
8382                        && moveList[forwardMostMove-1][1] == '@'
8383                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8384                         reason = "XBoard adjudication: pawn-drop mate";
8385                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8386                     }
8387                 }
8388                 break;
8389             }
8390
8391                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8392                     case EP_STALEMATE:
8393                         result = GameIsDrawn; break;
8394                     case EP_CHECKMATE:
8395                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8396                     case EP_WINS:
8397                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8398                     default:
8399                         result = EndOfFile;
8400                 }
8401                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8402                     if(engineOpponent)
8403                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8404                     GameEnds( result, reason, GE_XBOARD );
8405                     return 1;
8406                 }
8407
8408                 /* Next absolutely insufficient mating material. */
8409                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8410                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8411                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8412
8413                      /* always flag draws, for judging claims */
8414                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8415
8416                      if(canAdjudicate && appData.materialDraws) {
8417                          /* but only adjudicate them if adjudication enabled */
8418                          if(engineOpponent) {
8419                            SendToProgram("force\n", engineOpponent); // suppress reply
8420                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8421                          }
8422                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8423                          return 1;
8424                      }
8425                 }
8426
8427                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8428                 if(gameInfo.variant == VariantXiangqi ?
8429                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8430                  : nrW + nrB == 4 &&
8431                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8432                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8433                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8434                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8435                    ) ) {
8436                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8437                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8438                           if(engineOpponent) {
8439                             SendToProgram("force\n", engineOpponent); // suppress reply
8440                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8441                           }
8442                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8443                           return 1;
8444                      }
8445                 } else moveCount = 6;
8446             }
8447
8448         // Repetition draws and 50-move rule can be applied independently of legality testing
8449
8450                 /* Check for rep-draws */
8451                 count = 0;
8452                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8453                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8454                 for(k = forwardMostMove-2;
8455                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8456                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8457                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8458                     k-=2)
8459                 {   int rights=0;
8460                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8461                         /* compare castling rights */
8462                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8463                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8464                                 rights++; /* King lost rights, while rook still had them */
8465                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8466                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8467                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8468                                    rights++; /* but at least one rook lost them */
8469                         }
8470                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8471                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8472                                 rights++;
8473                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8474                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8475                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8476                                    rights++;
8477                         }
8478                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8479                             && appData.drawRepeats > 1) {
8480                              /* adjudicate after user-specified nr of repeats */
8481                              int result = GameIsDrawn;
8482                              char *details = "XBoard adjudication: repetition draw";
8483                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8484                                 // [HGM] xiangqi: check for forbidden perpetuals
8485                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8486                                 for(m=forwardMostMove; m>k; m-=2) {
8487                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8488                                         ourPerpetual = 0; // the current mover did not always check
8489                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8490                                         hisPerpetual = 0; // the opponent did not always check
8491                                 }
8492                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8493                                                                         ourPerpetual, hisPerpetual);
8494                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8495                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8496                                     details = "Xboard adjudication: perpetual checking";
8497                                 } else
8498                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8499                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8500                                 } else
8501                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8502                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8503                                         result = BlackWins;
8504                                         details = "Xboard adjudication: repetition";
8505                                     }
8506                                 } else // it must be XQ
8507                                 // Now check for perpetual chases
8508                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8509                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8510                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8511                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8512                                         static char resdet[MSG_SIZ];
8513                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8514                                         details = resdet;
8515                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8516                                     } else
8517                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8518                                         break; // Abort repetition-checking loop.
8519                                 }
8520                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8521                              }
8522                              if(engineOpponent) {
8523                                SendToProgram("force\n", engineOpponent); // suppress reply
8524                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8525                              }
8526                              GameEnds( result, details, GE_XBOARD );
8527                              return 1;
8528                         }
8529                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8530                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8531                     }
8532                 }
8533
8534                 /* Now we test for 50-move draws. Determine ply count */
8535                 count = forwardMostMove;
8536                 /* look for last irreversble move */
8537                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8538                     count--;
8539                 /* if we hit starting position, add initial plies */
8540                 if( count == backwardMostMove )
8541                     count -= initialRulePlies;
8542                 count = forwardMostMove - count;
8543                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8544                         // adjust reversible move counter for checks in Xiangqi
8545                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8546                         if(i < backwardMostMove) i = backwardMostMove;
8547                         while(i <= forwardMostMove) {
8548                                 lastCheck = inCheck; // check evasion does not count
8549                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8550                                 if(inCheck || lastCheck) count--; // check does not count
8551                                 i++;
8552                         }
8553                 }
8554                 if( count >= 100)
8555                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8556                          /* this is used to judge if draw claims are legal */
8557                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8558                          if(engineOpponent) {
8559                            SendToProgram("force\n", engineOpponent); // suppress reply
8560                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8561                          }
8562                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8563                          return 1;
8564                 }
8565
8566                 /* if draw offer is pending, treat it as a draw claim
8567                  * when draw condition present, to allow engines a way to
8568                  * claim draws before making their move to avoid a race
8569                  * condition occurring after their move
8570                  */
8571                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8572                          char *p = NULL;
8573                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8574                              p = "Draw claim: 50-move rule";
8575                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8576                              p = "Draw claim: 3-fold repetition";
8577                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8578                              p = "Draw claim: insufficient mating material";
8579                          if( p != NULL && canAdjudicate) {
8580                              if(engineOpponent) {
8581                                SendToProgram("force\n", engineOpponent); // suppress reply
8582                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8583                              }
8584                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8585                              return 1;
8586                          }
8587                 }
8588
8589                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8590                     if(engineOpponent) {
8591                       SendToProgram("force\n", engineOpponent); // suppress reply
8592                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8593                     }
8594                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8595                     return 1;
8596                 }
8597         return 0;
8598 }
8599
8600 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8601 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8602 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8603
8604 static int
8605 BitbaseProbe ()
8606 {
8607     int pieces[10], squares[10], cnt=0, r, f, res;
8608     static int loaded;
8609     static PPROBE_EGBB probeBB;
8610     if(!appData.testLegality) return 10;
8611     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8612     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8613     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8614     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8615         ChessSquare piece = boards[forwardMostMove][r][f];
8616         int black = (piece >= BlackPawn);
8617         int type = piece - black*BlackPawn;
8618         if(piece == EmptySquare) continue;
8619         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8620         if(type == WhiteKing) type = WhiteQueen + 1;
8621         type = egbbCode[type];
8622         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8623         pieces[cnt] = type + black*6;
8624         if(++cnt > 5) return 11;
8625     }
8626     pieces[cnt] = squares[cnt] = 0;
8627     // probe EGBB
8628     if(loaded == 2) return 13; // loading failed before
8629     if(loaded == 0) {
8630         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8631         HMODULE lib;
8632         PLOAD_EGBB loadBB;
8633         loaded = 2; // prepare for failure
8634         if(!path) return 13; // no egbb installed
8635         strncpy(buf, path + 8, MSG_SIZ);
8636         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8637         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8638         lib = LoadLibrary(buf);
8639         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8640         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8641         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8642         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8643         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8644         loaded = 1; // success!
8645     }
8646     res = probeBB(forwardMostMove & 1, pieces, squares);
8647     return res > 0 ? 1 : res < 0 ? -1 : 0;
8648 }
8649
8650 char *
8651 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8652 {   // [HGM] book: this routine intercepts moves to simulate book replies
8653     char *bookHit = NULL;
8654
8655     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8656         char buf[MSG_SIZ];
8657         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8658         SendToProgram(buf, cps);
8659     }
8660     //first determine if the incoming move brings opponent into his book
8661     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8662         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8663     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8664     if(bookHit != NULL && !cps->bookSuspend) {
8665         // make sure opponent is not going to reply after receiving move to book position
8666         SendToProgram("force\n", cps);
8667         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8668     }
8669     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8670     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8671     // now arrange restart after book miss
8672     if(bookHit) {
8673         // after a book hit we never send 'go', and the code after the call to this routine
8674         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8675         char buf[MSG_SIZ], *move = bookHit;
8676         if(cps->useSAN) {
8677             int fromX, fromY, toX, toY;
8678             char promoChar;
8679             ChessMove moveType;
8680             move = buf + 30;
8681             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8682                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8683                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8684                                     PosFlags(forwardMostMove),
8685                                     fromY, fromX, toY, toX, promoChar, move);
8686             } else {
8687                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8688                 bookHit = NULL;
8689             }
8690         }
8691         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8692         SendToProgram(buf, cps);
8693         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8694     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8695         SendToProgram("go\n", cps);
8696         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8697     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8698         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8699             SendToProgram("go\n", cps);
8700         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8701     }
8702     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8703 }
8704
8705 int
8706 LoadError (char *errmess, ChessProgramState *cps)
8707 {   // unloads engine and switches back to -ncp mode if it was first
8708     if(cps->initDone) return FALSE;
8709     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8710     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8711     cps->pr = NoProc;
8712     if(cps == &first) {
8713         appData.noChessProgram = TRUE;
8714         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8715         gameMode = BeginningOfGame; ModeHighlight();
8716         SetNCPMode();
8717     }
8718     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8719     DisplayMessage("", ""); // erase waiting message
8720     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8721     return TRUE;
8722 }
8723
8724 char *savedMessage;
8725 ChessProgramState *savedState;
8726 void
8727 DeferredBookMove (void)
8728 {
8729         if(savedState->lastPing != savedState->lastPong)
8730                     ScheduleDelayedEvent(DeferredBookMove, 10);
8731         else
8732         HandleMachineMove(savedMessage, savedState);
8733 }
8734
8735 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8736 static ChessProgramState *stalledEngine;
8737 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8738
8739 void
8740 HandleMachineMove (char *message, ChessProgramState *cps)
8741 {
8742     static char firstLeg[20], legs;
8743     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8744     char realname[MSG_SIZ];
8745     int fromX, fromY, toX, toY;
8746     ChessMove moveType;
8747     char promoChar, roar;
8748     char *p, *pv=buf1;
8749     int oldError;
8750     char *bookHit;
8751
8752     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8753         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8754         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8755             DisplayError(_("Invalid pairing from pairing engine"), 0);
8756             return;
8757         }
8758         pairingReceived = 1;
8759         NextMatchGame();
8760         return; // Skim the pairing messages here.
8761     }
8762
8763     oldError = cps->userError; cps->userError = 0;
8764
8765 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8766     /*
8767      * Kludge to ignore BEL characters
8768      */
8769     while (*message == '\007') message++;
8770
8771     /*
8772      * [HGM] engine debug message: ignore lines starting with '#' character
8773      */
8774     if(cps->debug && *message == '#') return;
8775
8776     /*
8777      * Look for book output
8778      */
8779     if (cps == &first && bookRequested) {
8780         if (message[0] == '\t' || message[0] == ' ') {
8781             /* Part of the book output is here; append it */
8782             strcat(bookOutput, message);
8783             strcat(bookOutput, "  \n");
8784             return;
8785         } else if (bookOutput[0] != NULLCHAR) {
8786             /* All of book output has arrived; display it */
8787             char *p = bookOutput;
8788             while (*p != NULLCHAR) {
8789                 if (*p == '\t') *p = ' ';
8790                 p++;
8791             }
8792             DisplayInformation(bookOutput);
8793             bookRequested = FALSE;
8794             /* Fall through to parse the current output */
8795         }
8796     }
8797
8798     /*
8799      * Look for machine move.
8800      */
8801     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8802         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8803     {
8804         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8805             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8806             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8807             stalledEngine = cps;
8808             if(appData.ponderNextMove) { // bring opponent out of ponder
8809                 if(gameMode == TwoMachinesPlay) {
8810                     if(cps->other->pause)
8811                         PauseEngine(cps->other);
8812                     else
8813                         SendToProgram("easy\n", cps->other);
8814                 }
8815             }
8816             StopClocks();
8817             return;
8818         }
8819
8820       if(cps->usePing) {
8821
8822         /* This method is only useful on engines that support ping */
8823         if(abortEngineThink) {
8824             if (appData.debugMode) {
8825                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8826             }
8827             SendToProgram("undo\n", cps);
8828             return;
8829         }
8830
8831         if (cps->lastPing != cps->lastPong) {
8832             /* Extra move from before last new; ignore */
8833             if (appData.debugMode) {
8834                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8835             }
8836           return;
8837         }
8838
8839       } else {
8840
8841         int machineWhite = FALSE;
8842
8843         switch (gameMode) {
8844           case BeginningOfGame:
8845             /* Extra move from before last reset; ignore */
8846             if (appData.debugMode) {
8847                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8848             }
8849             return;
8850
8851           case EndOfGame:
8852           case IcsIdle:
8853           default:
8854             /* Extra move after we tried to stop.  The mode test is
8855                not a reliable way of detecting this problem, but it's
8856                the best we can do on engines that don't support ping.
8857             */
8858             if (appData.debugMode) {
8859                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8860                         cps->which, gameMode);
8861             }
8862             SendToProgram("undo\n", cps);
8863             return;
8864
8865           case MachinePlaysWhite:
8866           case IcsPlayingWhite:
8867             machineWhite = TRUE;
8868             break;
8869
8870           case MachinePlaysBlack:
8871           case IcsPlayingBlack:
8872             machineWhite = FALSE;
8873             break;
8874
8875           case TwoMachinesPlay:
8876             machineWhite = (cps->twoMachinesColor[0] == 'w');
8877             break;
8878         }
8879         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8880             if (appData.debugMode) {
8881                 fprintf(debugFP,
8882                         "Ignoring move out of turn by %s, gameMode %d"
8883                         ", forwardMost %d\n",
8884                         cps->which, gameMode, forwardMostMove);
8885             }
8886             return;
8887         }
8888       }
8889
8890         if(cps->alphaRank) AlphaRank(machineMove, 4);
8891
8892         // [HGM] lion: (some very limited) support for Alien protocol
8893         killX = killY = kill2X = kill2Y = -1;
8894         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8895             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8896             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8897             return;
8898         }
8899         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8900             char *q = strchr(p+1, ',');            // second comma?
8901             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8902             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8903             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8904         }
8905         if(firstLeg[0]) { // there was a previous leg;
8906             // only support case where same piece makes two step
8907             char buf[20], *p = machineMove+1, *q = buf+1, f;
8908             safeStrCpy(buf, machineMove, 20);
8909             while(isdigit(*q)) q++; // find start of to-square
8910             safeStrCpy(machineMove, firstLeg, 20);
8911             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8912             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
8913             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)
8914             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8915             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8916             firstLeg[0] = NULLCHAR; legs = 0;
8917         }
8918
8919         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8920                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8921             /* Machine move could not be parsed; ignore it. */
8922           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8923                     machineMove, _(cps->which));
8924             DisplayMoveError(buf1);
8925             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8926                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8927             if (gameMode == TwoMachinesPlay) {
8928               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8929                        buf1, GE_XBOARD);
8930             }
8931             return;
8932         }
8933
8934         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8935         /* So we have to redo legality test with true e.p. status here,  */
8936         /* to make sure an illegal e.p. capture does not slip through,   */
8937         /* to cause a forfeit on a justified illegal-move complaint      */
8938         /* of the opponent.                                              */
8939         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8940            ChessMove moveType;
8941            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8942                              fromY, fromX, toY, toX, promoChar);
8943             if(moveType == IllegalMove) {
8944               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8945                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8946                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8947                            buf1, GE_XBOARD);
8948                 return;
8949            } else if(!appData.fischerCastling)
8950            /* [HGM] Kludge to handle engines that send FRC-style castling
8951               when they shouldn't (like TSCP-Gothic) */
8952            switch(moveType) {
8953              case WhiteASideCastleFR:
8954              case BlackASideCastleFR:
8955                toX+=2;
8956                currentMoveString[2]++;
8957                break;
8958              case WhiteHSideCastleFR:
8959              case BlackHSideCastleFR:
8960                toX--;
8961                currentMoveString[2]--;
8962                break;
8963              default: ; // nothing to do, but suppresses warning of pedantic compilers
8964            }
8965         }
8966         hintRequested = FALSE;
8967         lastHint[0] = NULLCHAR;
8968         bookRequested = FALSE;
8969         /* Program may be pondering now */
8970         cps->maybeThinking = TRUE;
8971         if (cps->sendTime == 2) cps->sendTime = 1;
8972         if (cps->offeredDraw) cps->offeredDraw--;
8973
8974         /* [AS] Save move info*/
8975         pvInfoList[ forwardMostMove ].score = programStats.score;
8976         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8977         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8978
8979         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8980
8981         /* Test suites abort the 'game' after one move */
8982         if(*appData.finger) {
8983            static FILE *f;
8984            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8985            if(!f) f = fopen(appData.finger, "w");
8986            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8987            else { DisplayFatalError("Bad output file", errno, 0); return; }
8988            free(fen);
8989            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8990         }
8991         if(appData.epd) {
8992            if(solvingTime >= 0) {
8993               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8994               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8995            } else {
8996               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8997               if(solvingTime == -2) second.matchWins++;
8998            }
8999            OutputKibitz(2, buf1);
9000            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9001         }
9002
9003         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9004         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9005             int count = 0;
9006
9007             while( count < adjudicateLossPlies ) {
9008                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9009
9010                 if( count & 1 ) {
9011                     score = -score; /* Flip score for winning side */
9012                 }
9013
9014                 if( score > appData.adjudicateLossThreshold ) {
9015                     break;
9016                 }
9017
9018                 count++;
9019             }
9020
9021             if( count >= adjudicateLossPlies ) {
9022                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9023
9024                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9025                     "Xboard adjudication",
9026                     GE_XBOARD );
9027
9028                 return;
9029             }
9030         }
9031
9032         if(Adjudicate(cps)) {
9033             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9034             return; // [HGM] adjudicate: for all automatic game ends
9035         }
9036
9037 #if ZIPPY
9038         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9039             first.initDone) {
9040           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9041                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9042                 SendToICS("draw ");
9043                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9044           }
9045           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9046           ics_user_moved = 1;
9047           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9048                 char buf[3*MSG_SIZ];
9049
9050                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9051                         programStats.score / 100.,
9052                         programStats.depth,
9053                         programStats.time / 100.,
9054                         (unsigned int)programStats.nodes,
9055                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9056                         programStats.movelist);
9057                 SendToICS(buf);
9058           }
9059         }
9060 #endif
9061
9062         /* [AS] Clear stats for next move */
9063         ClearProgramStats();
9064         thinkOutput[0] = NULLCHAR;
9065         hiddenThinkOutputState = 0;
9066
9067         bookHit = NULL;
9068         if (gameMode == TwoMachinesPlay) {
9069             /* [HGM] relaying draw offers moved to after reception of move */
9070             /* and interpreting offer as claim if it brings draw condition */
9071             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9072                 SendToProgram("draw\n", cps->other);
9073             }
9074             if (cps->other->sendTime) {
9075                 SendTimeRemaining(cps->other,
9076                                   cps->other->twoMachinesColor[0] == 'w');
9077             }
9078             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9079             if (firstMove && !bookHit) {
9080                 firstMove = FALSE;
9081                 if (cps->other->useColors) {
9082                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9083                 }
9084                 SendToProgram("go\n", cps->other);
9085             }
9086             cps->other->maybeThinking = TRUE;
9087         }
9088
9089         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9090
9091         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9092
9093         if (!pausing && appData.ringBellAfterMoves) {
9094             if(!roar) RingBell();
9095         }
9096
9097         /*
9098          * Reenable menu items that were disabled while
9099          * machine was thinking
9100          */
9101         if (gameMode != TwoMachinesPlay)
9102             SetUserThinkingEnables();
9103
9104         // [HGM] book: after book hit opponent has received move and is now in force mode
9105         // force the book reply into it, and then fake that it outputted this move by jumping
9106         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9107         if(bookHit) {
9108                 static char bookMove[MSG_SIZ]; // a bit generous?
9109
9110                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9111                 strcat(bookMove, bookHit);
9112                 message = bookMove;
9113                 cps = cps->other;
9114                 programStats.nodes = programStats.depth = programStats.time =
9115                 programStats.score = programStats.got_only_move = 0;
9116                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9117
9118                 if(cps->lastPing != cps->lastPong) {
9119                     savedMessage = message; // args for deferred call
9120                     savedState = cps;
9121                     ScheduleDelayedEvent(DeferredBookMove, 10);
9122                     return;
9123                 }
9124                 goto FakeBookMove;
9125         }
9126
9127         return;
9128     }
9129
9130     /* Set special modes for chess engines.  Later something general
9131      *  could be added here; for now there is just one kludge feature,
9132      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9133      *  when "xboard" is given as an interactive command.
9134      */
9135     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9136         cps->useSigint = FALSE;
9137         cps->useSigterm = FALSE;
9138     }
9139     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9140       ParseFeatures(message+8, cps);
9141       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9142     }
9143
9144     if (!strncmp(message, "setup ", 6) && 
9145         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9146           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9147                                         ) { // [HGM] allow first engine to define opening position
9148       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9149       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9150       *buf = NULLCHAR;
9151       if(sscanf(message, "setup (%s", buf) == 1) {
9152         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9153         ASSIGN(appData.pieceToCharTable, buf);
9154       }
9155       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9156       if(dummy >= 3) {
9157         while(message[s] && message[s++] != ' ');
9158         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9159            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9160             if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9161             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9162             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9163           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9164           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9165           startedFromSetupPosition = FALSE;
9166         }
9167       }
9168       if(startedFromSetupPosition) return;
9169       ParseFEN(boards[0], &dummy, message+s, FALSE);
9170       DrawPosition(TRUE, boards[0]);
9171       CopyBoard(initialPosition, boards[0]);
9172       startedFromSetupPosition = TRUE;
9173       return;
9174     }
9175     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9176       ChessSquare piece = WhitePawn;
9177       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9178       if(*p == '+') promoted++, ID = *++p;
9179       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9180       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9181       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9182       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9183       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9184       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9185       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9186       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9187                                                && gameInfo.variant != VariantGreat
9188                                                && gameInfo.variant != VariantFairy    ) return;
9189       if(piece < EmptySquare) {
9190         pieceDefs = TRUE;
9191         ASSIGN(pieceDesc[piece], buf1);
9192         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9193       }
9194       return;
9195     }
9196     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9197       if(deferChoice) {
9198         LeftClick(Press, 0, 0); // finish the click that was interrupted
9199       } else if(promoSweep != EmptySquare) {
9200         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9201         if(strlen(promoRestrict) > 1) Sweep(0);
9202       }
9203       return;
9204     }
9205     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9206      * want this, I was asked to put it in, and obliged.
9207      */
9208     if (!strncmp(message, "setboard ", 9)) {
9209         Board initial_position;
9210
9211         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9212
9213         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9214             DisplayError(_("Bad FEN received from engine"), 0);
9215             return ;
9216         } else {
9217            Reset(TRUE, FALSE);
9218            CopyBoard(boards[0], initial_position);
9219            initialRulePlies = FENrulePlies;
9220            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9221            else gameMode = MachinePlaysBlack;
9222            DrawPosition(FALSE, boards[currentMove]);
9223         }
9224         return;
9225     }
9226
9227     /*
9228      * Look for communication commands
9229      */
9230     if (!strncmp(message, "telluser ", 9)) {
9231         if(message[9] == '\\' && message[10] == '\\')
9232             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9233         PlayTellSound();
9234         DisplayNote(message + 9);
9235         return;
9236     }
9237     if (!strncmp(message, "tellusererror ", 14)) {
9238         cps->userError = 1;
9239         if(message[14] == '\\' && message[15] == '\\')
9240             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9241         PlayTellSound();
9242         DisplayError(message + 14, 0);
9243         return;
9244     }
9245     if (!strncmp(message, "tellopponent ", 13)) {
9246       if (appData.icsActive) {
9247         if (loggedOn) {
9248           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9249           SendToICS(buf1);
9250         }
9251       } else {
9252         DisplayNote(message + 13);
9253       }
9254       return;
9255     }
9256     if (!strncmp(message, "tellothers ", 11)) {
9257       if (appData.icsActive) {
9258         if (loggedOn) {
9259           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9260           SendToICS(buf1);
9261         }
9262       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9263       return;
9264     }
9265     if (!strncmp(message, "tellall ", 8)) {
9266       if (appData.icsActive) {
9267         if (loggedOn) {
9268           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9269           SendToICS(buf1);
9270         }
9271       } else {
9272         DisplayNote(message + 8);
9273       }
9274       return;
9275     }
9276     if (strncmp(message, "warning", 7) == 0) {
9277         /* Undocumented feature, use tellusererror in new code */
9278         DisplayError(message, 0);
9279         return;
9280     }
9281     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9282         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9283         strcat(realname, " query");
9284         AskQuestion(realname, buf2, buf1, cps->pr);
9285         return;
9286     }
9287     /* Commands from the engine directly to ICS.  We don't allow these to be
9288      *  sent until we are logged on. Crafty kibitzes have been known to
9289      *  interfere with the login process.
9290      */
9291     if (loggedOn) {
9292         if (!strncmp(message, "tellics ", 8)) {
9293             SendToICS(message + 8);
9294             SendToICS("\n");
9295             return;
9296         }
9297         if (!strncmp(message, "tellicsnoalias ", 15)) {
9298             SendToICS(ics_prefix);
9299             SendToICS(message + 15);
9300             SendToICS("\n");
9301             return;
9302         }
9303         /* The following are for backward compatibility only */
9304         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9305             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9306             SendToICS(ics_prefix);
9307             SendToICS(message);
9308             SendToICS("\n");
9309             return;
9310         }
9311     }
9312     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9313         if(initPing == cps->lastPong) {
9314             if(gameInfo.variant == VariantUnknown) {
9315                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9316                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9317                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9318             }
9319             initPing = -1;
9320         }
9321         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9322             abortEngineThink = FALSE;
9323             DisplayMessage("", "");
9324             ThawUI();
9325         }
9326         return;
9327     }
9328     if(!strncmp(message, "highlight ", 10)) {
9329         if(appData.testLegality && !*engineVariant && appData.markers) return;
9330         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9331         return;
9332     }
9333     if(!strncmp(message, "click ", 6)) {
9334         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9335         if(appData.testLegality || !appData.oneClick) return;
9336         sscanf(message+6, "%c%d%c", &f, &y, &c);
9337         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9338         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9339         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9340         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9341         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9342         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9343             LeftClick(Release, lastLeftX, lastLeftY);
9344         controlKey  = (c == ',');
9345         LeftClick(Press, x, y);
9346         LeftClick(Release, x, y);
9347         first.highlight = f;
9348         return;
9349     }
9350     /*
9351      * If the move is illegal, cancel it and redraw the board.
9352      * Also deal with other error cases.  Matching is rather loose
9353      * here to accommodate engines written before the spec.
9354      */
9355     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9356         strncmp(message, "Error", 5) == 0) {
9357         if (StrStr(message, "name") ||
9358             StrStr(message, "rating") || StrStr(message, "?") ||
9359             StrStr(message, "result") || StrStr(message, "board") ||
9360             StrStr(message, "bk") || StrStr(message, "computer") ||
9361             StrStr(message, "variant") || StrStr(message, "hint") ||
9362             StrStr(message, "random") || StrStr(message, "depth") ||
9363             StrStr(message, "accepted")) {
9364             return;
9365         }
9366         if (StrStr(message, "protover")) {
9367           /* Program is responding to input, so it's apparently done
9368              initializing, and this error message indicates it is
9369              protocol version 1.  So we don't need to wait any longer
9370              for it to initialize and send feature commands. */
9371           FeatureDone(cps, 1);
9372           cps->protocolVersion = 1;
9373           return;
9374         }
9375         cps->maybeThinking = FALSE;
9376
9377         if (StrStr(message, "draw")) {
9378             /* Program doesn't have "draw" command */
9379             cps->sendDrawOffers = 0;
9380             return;
9381         }
9382         if (cps->sendTime != 1 &&
9383             (StrStr(message, "time") || StrStr(message, "otim"))) {
9384           /* Program apparently doesn't have "time" or "otim" command */
9385           cps->sendTime = 0;
9386           return;
9387         }
9388         if (StrStr(message, "analyze")) {
9389             cps->analysisSupport = FALSE;
9390             cps->analyzing = FALSE;
9391 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9392             EditGameEvent(); // [HGM] try to preserve loaded game
9393             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9394             DisplayError(buf2, 0);
9395             return;
9396         }
9397         if (StrStr(message, "(no matching move)st")) {
9398           /* Special kludge for GNU Chess 4 only */
9399           cps->stKludge = TRUE;
9400           SendTimeControl(cps, movesPerSession, timeControl,
9401                           timeIncrement, appData.searchDepth,
9402                           searchTime);
9403           return;
9404         }
9405         if (StrStr(message, "(no matching move)sd")) {
9406           /* Special kludge for GNU Chess 4 only */
9407           cps->sdKludge = TRUE;
9408           SendTimeControl(cps, movesPerSession, timeControl,
9409                           timeIncrement, appData.searchDepth,
9410                           searchTime);
9411           return;
9412         }
9413         if (!StrStr(message, "llegal")) {
9414             return;
9415         }
9416         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9417             gameMode == IcsIdle) return;
9418         if (forwardMostMove <= backwardMostMove) return;
9419         if (pausing) PauseEvent();
9420       if(appData.forceIllegal) {
9421             // [HGM] illegal: machine refused move; force position after move into it
9422           SendToProgram("force\n", cps);
9423           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9424                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9425                 // when black is to move, while there might be nothing on a2 or black
9426                 // might already have the move. So send the board as if white has the move.
9427                 // But first we must change the stm of the engine, as it refused the last move
9428                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9429                 if(WhiteOnMove(forwardMostMove)) {
9430                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9431                     SendBoard(cps, forwardMostMove); // kludgeless board
9432                 } else {
9433                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9434                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9435                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9436                 }
9437           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9438             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9439                  gameMode == TwoMachinesPlay)
9440               SendToProgram("go\n", cps);
9441             return;
9442       } else
9443         if (gameMode == PlayFromGameFile) {
9444             /* Stop reading this game file */
9445             gameMode = EditGame;
9446             ModeHighlight();
9447         }
9448         /* [HGM] illegal-move claim should forfeit game when Xboard */
9449         /* only passes fully legal moves                            */
9450         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9451             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9452                                 "False illegal-move claim", GE_XBOARD );
9453             return; // do not take back move we tested as valid
9454         }
9455         currentMove = forwardMostMove-1;
9456         DisplayMove(currentMove-1); /* before DisplayMoveError */
9457         SwitchClocks(forwardMostMove-1); // [HGM] race
9458         DisplayBothClocks();
9459         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9460                 parseList[currentMove], _(cps->which));
9461         DisplayMoveError(buf1);
9462         DrawPosition(FALSE, boards[currentMove]);
9463
9464         SetUserThinkingEnables();
9465         return;
9466     }
9467     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9468         /* Program has a broken "time" command that
9469            outputs a string not ending in newline.
9470            Don't use it. */
9471         cps->sendTime = 0;
9472     }
9473     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9474         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9475             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9476     }
9477
9478     /*
9479      * If chess program startup fails, exit with an error message.
9480      * Attempts to recover here are futile. [HGM] Well, we try anyway
9481      */
9482     if ((StrStr(message, "unknown host") != NULL)
9483         || (StrStr(message, "No remote directory") != NULL)
9484         || (StrStr(message, "not found") != NULL)
9485         || (StrStr(message, "No such file") != NULL)
9486         || (StrStr(message, "can't alloc") != NULL)
9487         || (StrStr(message, "Permission denied") != NULL)) {
9488
9489         cps->maybeThinking = FALSE;
9490         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9491                 _(cps->which), cps->program, cps->host, message);
9492         RemoveInputSource(cps->isr);
9493         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9494             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9495             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9496         }
9497         return;
9498     }
9499
9500     /*
9501      * Look for hint output
9502      */
9503     if (sscanf(message, "Hint: %s", buf1) == 1) {
9504         if (cps == &first && hintRequested) {
9505             hintRequested = FALSE;
9506             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9507                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9508                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9509                                     PosFlags(forwardMostMove),
9510                                     fromY, fromX, toY, toX, promoChar, buf1);
9511                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9512                 DisplayInformation(buf2);
9513             } else {
9514                 /* Hint move could not be parsed!? */
9515               snprintf(buf2, sizeof(buf2),
9516                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9517                         buf1, _(cps->which));
9518                 DisplayError(buf2, 0);
9519             }
9520         } else {
9521           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9522         }
9523         return;
9524     }
9525
9526     /*
9527      * Ignore other messages if game is not in progress
9528      */
9529     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9530         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9531
9532     /*
9533      * look for win, lose, draw, or draw offer
9534      */
9535     if (strncmp(message, "1-0", 3) == 0) {
9536         char *p, *q, *r = "";
9537         p = strchr(message, '{');
9538         if (p) {
9539             q = strchr(p, '}');
9540             if (q) {
9541                 *q = NULLCHAR;
9542                 r = p + 1;
9543             }
9544         }
9545         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9546         return;
9547     } else if (strncmp(message, "0-1", 3) == 0) {
9548         char *p, *q, *r = "";
9549         p = strchr(message, '{');
9550         if (p) {
9551             q = strchr(p, '}');
9552             if (q) {
9553                 *q = NULLCHAR;
9554                 r = p + 1;
9555             }
9556         }
9557         /* Kludge for Arasan 4.1 bug */
9558         if (strcmp(r, "Black resigns") == 0) {
9559             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9560             return;
9561         }
9562         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9563         return;
9564     } else if (strncmp(message, "1/2", 3) == 0) {
9565         char *p, *q, *r = "";
9566         p = strchr(message, '{');
9567         if (p) {
9568             q = strchr(p, '}');
9569             if (q) {
9570                 *q = NULLCHAR;
9571                 r = p + 1;
9572             }
9573         }
9574
9575         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9576         return;
9577
9578     } else if (strncmp(message, "White resign", 12) == 0) {
9579         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9580         return;
9581     } else if (strncmp(message, "Black resign", 12) == 0) {
9582         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9583         return;
9584     } else if (strncmp(message, "White matches", 13) == 0 ||
9585                strncmp(message, "Black matches", 13) == 0   ) {
9586         /* [HGM] ignore GNUShogi noises */
9587         return;
9588     } else if (strncmp(message, "White", 5) == 0 &&
9589                message[5] != '(' &&
9590                StrStr(message, "Black") == NULL) {
9591         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9592         return;
9593     } else if (strncmp(message, "Black", 5) == 0 &&
9594                message[5] != '(') {
9595         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9596         return;
9597     } else if (strcmp(message, "resign") == 0 ||
9598                strcmp(message, "computer resigns") == 0) {
9599         switch (gameMode) {
9600           case MachinePlaysBlack:
9601           case IcsPlayingBlack:
9602             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9603             break;
9604           case MachinePlaysWhite:
9605           case IcsPlayingWhite:
9606             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9607             break;
9608           case TwoMachinesPlay:
9609             if (cps->twoMachinesColor[0] == 'w')
9610               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9611             else
9612               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9613             break;
9614           default:
9615             /* can't happen */
9616             break;
9617         }
9618         return;
9619     } else if (strncmp(message, "opponent mates", 14) == 0) {
9620         switch (gameMode) {
9621           case MachinePlaysBlack:
9622           case IcsPlayingBlack:
9623             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9624             break;
9625           case MachinePlaysWhite:
9626           case IcsPlayingWhite:
9627             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9628             break;
9629           case TwoMachinesPlay:
9630             if (cps->twoMachinesColor[0] == 'w')
9631               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9632             else
9633               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9634             break;
9635           default:
9636             /* can't happen */
9637             break;
9638         }
9639         return;
9640     } else if (strncmp(message, "computer mates", 14) == 0) {
9641         switch (gameMode) {
9642           case MachinePlaysBlack:
9643           case IcsPlayingBlack:
9644             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9645             break;
9646           case MachinePlaysWhite:
9647           case IcsPlayingWhite:
9648             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9649             break;
9650           case TwoMachinesPlay:
9651             if (cps->twoMachinesColor[0] == 'w')
9652               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9653             else
9654               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9655             break;
9656           default:
9657             /* can't happen */
9658             break;
9659         }
9660         return;
9661     } else if (strncmp(message, "checkmate", 9) == 0) {
9662         if (WhiteOnMove(forwardMostMove)) {
9663             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9664         } else {
9665             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9666         }
9667         return;
9668     } else if (strstr(message, "Draw") != NULL ||
9669                strstr(message, "game is a draw") != NULL) {
9670         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9671         return;
9672     } else if (strstr(message, "offer") != NULL &&
9673                strstr(message, "draw") != NULL) {
9674 #if ZIPPY
9675         if (appData.zippyPlay && first.initDone) {
9676             /* Relay offer to ICS */
9677             SendToICS(ics_prefix);
9678             SendToICS("draw\n");
9679         }
9680 #endif
9681         cps->offeredDraw = 2; /* valid until this engine moves twice */
9682         if (gameMode == TwoMachinesPlay) {
9683             if (cps->other->offeredDraw) {
9684                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9685             /* [HGM] in two-machine mode we delay relaying draw offer      */
9686             /* until after we also have move, to see if it is really claim */
9687             }
9688         } else if (gameMode == MachinePlaysWhite ||
9689                    gameMode == MachinePlaysBlack) {
9690           if (userOfferedDraw) {
9691             DisplayInformation(_("Machine accepts your draw offer"));
9692             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9693           } else {
9694             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9695           }
9696         }
9697     }
9698
9699
9700     /*
9701      * Look for thinking output
9702      */
9703     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9704           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9705                                 ) {
9706         int plylev, mvleft, mvtot, curscore, time;
9707         char mvname[MOVE_LEN];
9708         u64 nodes; // [DM]
9709         char plyext;
9710         int ignore = FALSE;
9711         int prefixHint = FALSE;
9712         mvname[0] = NULLCHAR;
9713
9714         switch (gameMode) {
9715           case MachinePlaysBlack:
9716           case IcsPlayingBlack:
9717             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9718             break;
9719           case MachinePlaysWhite:
9720           case IcsPlayingWhite:
9721             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9722             break;
9723           case AnalyzeMode:
9724           case AnalyzeFile:
9725             break;
9726           case IcsObserving: /* [DM] icsEngineAnalyze */
9727             if (!appData.icsEngineAnalyze) ignore = TRUE;
9728             break;
9729           case TwoMachinesPlay:
9730             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9731                 ignore = TRUE;
9732             }
9733             break;
9734           default:
9735             ignore = TRUE;
9736             break;
9737         }
9738
9739         if (!ignore) {
9740             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9741             int solved = 0;
9742             buf1[0] = NULLCHAR;
9743             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9744                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9745                 char score_buf[MSG_SIZ];
9746
9747                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9748                     nodes += u64Const(0x100000000);
9749
9750                 if (plyext != ' ' && plyext != '\t') {
9751                     time *= 100;
9752                 }
9753
9754                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9755                 if( cps->scoreIsAbsolute &&
9756                     ( gameMode == MachinePlaysBlack ||
9757                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9758                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9759                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9760                      !WhiteOnMove(currentMove)
9761                     ) )
9762                 {
9763                     curscore = -curscore;
9764                 }
9765
9766                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9767
9768                 if(*bestMove) { // rememer time best EPD move was first found
9769                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9770                     ChessMove mt; char *p = bestMove;
9771                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9772                     solved = 0;
9773                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9774                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9775                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9776                             solved = 1;
9777                             break;
9778                         }
9779                         while(*p && *p != ' ') p++;
9780                         while(*p == ' ') p++;
9781                     }
9782                     if(!solved) solvingTime = -1;
9783                 }
9784                 if(*avoidMove && !solved) {
9785                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9786                     ChessMove mt; char *p = avoidMove, solved = 1;
9787                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9788                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9789                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9790                             solved = 0; solvingTime = -2;
9791                             break;
9792                         }
9793                         while(*p && *p != ' ') p++;
9794                         while(*p == ' ') p++;
9795                     }
9796                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9797                 }
9798
9799                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9800                         char buf[MSG_SIZ];
9801                         FILE *f;
9802                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9803                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9804                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9805                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9806                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9807                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9808                                 fclose(f);
9809                         }
9810                         else
9811                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9812                           DisplayError(_("failed writing PV"), 0);
9813                 }
9814
9815                 tempStats.depth = plylev;
9816                 tempStats.nodes = nodes;
9817                 tempStats.time = time;
9818                 tempStats.score = curscore;
9819                 tempStats.got_only_move = 0;
9820
9821                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9822                         int ticklen;
9823
9824                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9825                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9826                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9827                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9828                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9829                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9830                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9831                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9832                 }
9833
9834                 /* Buffer overflow protection */
9835                 if (pv[0] != NULLCHAR) {
9836                     if (strlen(pv) >= sizeof(tempStats.movelist)
9837                         && appData.debugMode) {
9838                         fprintf(debugFP,
9839                                 "PV is too long; using the first %u bytes.\n",
9840                                 (unsigned) sizeof(tempStats.movelist) - 1);
9841                     }
9842
9843                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9844                 } else {
9845                     sprintf(tempStats.movelist, " no PV\n");
9846                 }
9847
9848                 if (tempStats.seen_stat) {
9849                     tempStats.ok_to_send = 1;
9850                 }
9851
9852                 if (strchr(tempStats.movelist, '(') != NULL) {
9853                     tempStats.line_is_book = 1;
9854                     tempStats.nr_moves = 0;
9855                     tempStats.moves_left = 0;
9856                 } else {
9857                     tempStats.line_is_book = 0;
9858                 }
9859
9860                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9861                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9862
9863                 SendProgramStatsToFrontend( cps, &tempStats );
9864
9865                 /*
9866                     [AS] Protect the thinkOutput buffer from overflow... this
9867                     is only useful if buf1 hasn't overflowed first!
9868                 */
9869                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9870                 if(curscore >= MATE_SCORE) 
9871                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9872                 else if(curscore <= -MATE_SCORE) 
9873                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9874                 else
9875                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9876                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9877                          plylev,
9878                          (gameMode == TwoMachinesPlay ?
9879                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9880                          score_buf,
9881                          prefixHint ? lastHint : "",
9882                          prefixHint ? " " : "" );
9883
9884                 if( buf1[0] != NULLCHAR ) {
9885                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9886
9887                     if( strlen(pv) > max_len ) {
9888                         if( appData.debugMode) {
9889                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9890                         }
9891                         pv[max_len+1] = '\0';
9892                     }
9893
9894                     strcat( thinkOutput, pv);
9895                 }
9896
9897                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9898                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9899                     DisplayMove(currentMove - 1);
9900                 }
9901                 return;
9902
9903             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9904                 /* crafty (9.25+) says "(only move) <move>"
9905                  * if there is only 1 legal move
9906                  */
9907                 sscanf(p, "(only move) %s", buf1);
9908                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9909                 sprintf(programStats.movelist, "%s (only move)", buf1);
9910                 programStats.depth = 1;
9911                 programStats.nr_moves = 1;
9912                 programStats.moves_left = 1;
9913                 programStats.nodes = 1;
9914                 programStats.time = 1;
9915                 programStats.got_only_move = 1;
9916
9917                 /* Not really, but we also use this member to
9918                    mean "line isn't going to change" (Crafty
9919                    isn't searching, so stats won't change) */
9920                 programStats.line_is_book = 1;
9921
9922                 SendProgramStatsToFrontend( cps, &programStats );
9923
9924                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9925                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9926                     DisplayMove(currentMove - 1);
9927                 }
9928                 return;
9929             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9930                               &time, &nodes, &plylev, &mvleft,
9931                               &mvtot, mvname) >= 5) {
9932                 /* The stat01: line is from Crafty (9.29+) in response
9933                    to the "." command */
9934                 programStats.seen_stat = 1;
9935                 cps->maybeThinking = TRUE;
9936
9937                 if (programStats.got_only_move || !appData.periodicUpdates)
9938                   return;
9939
9940                 programStats.depth = plylev;
9941                 programStats.time = time;
9942                 programStats.nodes = nodes;
9943                 programStats.moves_left = mvleft;
9944                 programStats.nr_moves = mvtot;
9945                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9946                 programStats.ok_to_send = 1;
9947                 programStats.movelist[0] = '\0';
9948
9949                 SendProgramStatsToFrontend( cps, &programStats );
9950
9951                 return;
9952
9953             } else if (strncmp(message,"++",2) == 0) {
9954                 /* Crafty 9.29+ outputs this */
9955                 programStats.got_fail = 2;
9956                 return;
9957
9958             } else if (strncmp(message,"--",2) == 0) {
9959                 /* Crafty 9.29+ outputs this */
9960                 programStats.got_fail = 1;
9961                 return;
9962
9963             } else if (thinkOutput[0] != NULLCHAR &&
9964                        strncmp(message, "    ", 4) == 0) {
9965                 unsigned message_len;
9966
9967                 p = message;
9968                 while (*p && *p == ' ') p++;
9969
9970                 message_len = strlen( p );
9971
9972                 /* [AS] Avoid buffer overflow */
9973                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9974                     strcat(thinkOutput, " ");
9975                     strcat(thinkOutput, p);
9976                 }
9977
9978                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9979                     strcat(programStats.movelist, " ");
9980                     strcat(programStats.movelist, p);
9981                 }
9982
9983                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9984                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9985                     DisplayMove(currentMove - 1);
9986                 }
9987                 return;
9988             }
9989         }
9990         else {
9991             buf1[0] = NULLCHAR;
9992
9993             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9994                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9995             {
9996                 ChessProgramStats cpstats;
9997
9998                 if (plyext != ' ' && plyext != '\t') {
9999                     time *= 100;
10000                 }
10001
10002                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10003                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10004                     curscore = -curscore;
10005                 }
10006
10007                 cpstats.depth = plylev;
10008                 cpstats.nodes = nodes;
10009                 cpstats.time = time;
10010                 cpstats.score = curscore;
10011                 cpstats.got_only_move = 0;
10012                 cpstats.movelist[0] = '\0';
10013
10014                 if (buf1[0] != NULLCHAR) {
10015                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10016                 }
10017
10018                 cpstats.ok_to_send = 0;
10019                 cpstats.line_is_book = 0;
10020                 cpstats.nr_moves = 0;
10021                 cpstats.moves_left = 0;
10022
10023                 SendProgramStatsToFrontend( cps, &cpstats );
10024             }
10025         }
10026     }
10027 }
10028
10029
10030 /* Parse a game score from the character string "game", and
10031    record it as the history of the current game.  The game
10032    score is NOT assumed to start from the standard position.
10033    The display is not updated in any way.
10034    */
10035 void
10036 ParseGameHistory (char *game)
10037 {
10038     ChessMove moveType;
10039     int fromX, fromY, toX, toY, boardIndex, mask;
10040     char promoChar;
10041     char *p, *q;
10042     char buf[MSG_SIZ];
10043
10044     if (appData.debugMode)
10045       fprintf(debugFP, "Parsing game history: %s\n", game);
10046
10047     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10048     gameInfo.site = StrSave(appData.icsHost);
10049     gameInfo.date = PGNDate();
10050     gameInfo.round = StrSave("-");
10051
10052     /* Parse out names of players */
10053     while (*game == ' ') game++;
10054     p = buf;
10055     while (*game != ' ') *p++ = *game++;
10056     *p = NULLCHAR;
10057     gameInfo.white = StrSave(buf);
10058     while (*game == ' ') game++;
10059     p = buf;
10060     while (*game != ' ' && *game != '\n') *p++ = *game++;
10061     *p = NULLCHAR;
10062     gameInfo.black = StrSave(buf);
10063
10064     /* Parse moves */
10065     boardIndex = blackPlaysFirst ? 1 : 0;
10066     yynewstr(game);
10067     for (;;) {
10068         yyboardindex = boardIndex;
10069         moveType = (ChessMove) Myylex();
10070         switch (moveType) {
10071           case IllegalMove:             /* maybe suicide chess, etc. */
10072   if (appData.debugMode) {
10073     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10074     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10075     setbuf(debugFP, NULL);
10076   }
10077           case WhitePromotion:
10078           case BlackPromotion:
10079           case WhiteNonPromotion:
10080           case BlackNonPromotion:
10081           case NormalMove:
10082           case FirstLeg:
10083           case WhiteCapturesEnPassant:
10084           case BlackCapturesEnPassant:
10085           case WhiteKingSideCastle:
10086           case WhiteQueenSideCastle:
10087           case BlackKingSideCastle:
10088           case BlackQueenSideCastle:
10089           case WhiteKingSideCastleWild:
10090           case WhiteQueenSideCastleWild:
10091           case BlackKingSideCastleWild:
10092           case BlackQueenSideCastleWild:
10093           /* PUSH Fabien */
10094           case WhiteHSideCastleFR:
10095           case WhiteASideCastleFR:
10096           case BlackHSideCastleFR:
10097           case BlackASideCastleFR:
10098           /* POP Fabien */
10099             fromX = currentMoveString[0] - AAA;
10100             fromY = currentMoveString[1] - ONE;
10101             toX = currentMoveString[2] - AAA;
10102             toY = currentMoveString[3] - ONE;
10103             promoChar = currentMoveString[4];
10104             break;
10105           case WhiteDrop:
10106           case BlackDrop:
10107             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10108             fromX = moveType == WhiteDrop ?
10109               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10110             (int) CharToPiece(ToLower(currentMoveString[0]));
10111             fromY = DROP_RANK;
10112             toX = currentMoveString[2] - AAA;
10113             toY = currentMoveString[3] - ONE;
10114             promoChar = NULLCHAR;
10115             break;
10116           case AmbiguousMove:
10117             /* bug? */
10118             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10119   if (appData.debugMode) {
10120     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10121     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10122     setbuf(debugFP, NULL);
10123   }
10124             DisplayError(buf, 0);
10125             return;
10126           case ImpossibleMove:
10127             /* bug? */
10128             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10129   if (appData.debugMode) {
10130     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10131     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10132     setbuf(debugFP, NULL);
10133   }
10134             DisplayError(buf, 0);
10135             return;
10136           case EndOfFile:
10137             if (boardIndex < backwardMostMove) {
10138                 /* Oops, gap.  How did that happen? */
10139                 DisplayError(_("Gap in move list"), 0);
10140                 return;
10141             }
10142             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10143             if (boardIndex > forwardMostMove) {
10144                 forwardMostMove = boardIndex;
10145             }
10146             return;
10147           case ElapsedTime:
10148             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10149                 strcat(parseList[boardIndex-1], " ");
10150                 strcat(parseList[boardIndex-1], yy_text);
10151             }
10152             continue;
10153           case Comment:
10154           case PGNTag:
10155           case NAG:
10156           default:
10157             /* ignore */
10158             continue;
10159           case WhiteWins:
10160           case BlackWins:
10161           case GameIsDrawn:
10162           case GameUnfinished:
10163             if (gameMode == IcsExamining) {
10164                 if (boardIndex < backwardMostMove) {
10165                     /* Oops, gap.  How did that happen? */
10166                     return;
10167                 }
10168                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10169                 return;
10170             }
10171             gameInfo.result = moveType;
10172             p = strchr(yy_text, '{');
10173             if (p == NULL) p = strchr(yy_text, '(');
10174             if (p == NULL) {
10175                 p = yy_text;
10176                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10177             } else {
10178                 q = strchr(p, *p == '{' ? '}' : ')');
10179                 if (q != NULL) *q = NULLCHAR;
10180                 p++;
10181             }
10182             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10183             gameInfo.resultDetails = StrSave(p);
10184             continue;
10185         }
10186         if (boardIndex >= forwardMostMove &&
10187             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10188             backwardMostMove = blackPlaysFirst ? 1 : 0;
10189             return;
10190         }
10191         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10192                                  fromY, fromX, toY, toX, promoChar,
10193                                  parseList[boardIndex]);
10194         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10195         /* currentMoveString is set as a side-effect of yylex */
10196         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10197         strcat(moveList[boardIndex], "\n");
10198         boardIndex++;
10199         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10200         mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10201         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10202           case MT_NONE:
10203           case MT_STALEMATE:
10204           default:
10205             break;
10206           case MT_CHECK:
10207             if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10208             if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10209                 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10210                 break;
10211             }
10212           case MT_CHECKMATE:
10213           case MT_STAINMATE:
10214             strcat(parseList[boardIndex - 1], "#");
10215             break;
10216         }
10217     }
10218 }
10219
10220
10221 /* Apply a move to the given board  */
10222 void
10223 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10224 {
10225   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10226   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10227
10228     /* [HGM] compute & store e.p. status and castling rights for new position */
10229     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10230
10231       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10232       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10233       board[EP_STATUS] = EP_NONE;
10234       board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10235
10236   if (fromY == DROP_RANK) {
10237         /* must be first */
10238         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10239             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10240             return;
10241         }
10242         piece = board[toY][toX] = (ChessSquare) fromX;
10243   } else {
10244 //      ChessSquare victim;
10245       int i;
10246
10247       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10248 //           victim = board[killY][killX],
10249            killed = board[killY][killX],
10250            board[killY][killX] = EmptySquare,
10251            board[EP_STATUS] = EP_CAPTURE;
10252            if( kill2X >= 0 && kill2Y >= 0)
10253              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10254       }
10255
10256       if( board[toY][toX] != EmptySquare ) {
10257            board[EP_STATUS] = EP_CAPTURE;
10258            if( (fromX != toX || fromY != toY) && // not igui!
10259                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10260                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10261                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10262            }
10263       }
10264
10265       pawn = board[fromY][fromX];
10266       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10267         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10268             captured = board[lastRank][lastFile]; // remove victim
10269             board[lastRank][lastFile] = EmptySquare;
10270             pawn = EmptySquare; // kludge to suppress old e.p. code
10271         }
10272       }
10273       if( pawn == WhiteLance || pawn == BlackLance ) {
10274            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10275                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10276                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10277            }
10278       }
10279       if( pawn == WhitePawn ) {
10280            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10281                board[EP_STATUS] = EP_PAWN_MOVE;
10282            if( toY-fromY>=2) {
10283                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10284                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10285                         gameInfo.variant != VariantBerolina || toX < fromX)
10286                       board[EP_STATUS] = toX | berolina;
10287                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10288                         gameInfo.variant != VariantBerolina || toX > fromX)
10289                       board[EP_STATUS] = toX;
10290                board[LAST_TO] = toX + 256*toY;
10291            }
10292       } else
10293       if( pawn == BlackPawn ) {
10294            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10295                board[EP_STATUS] = EP_PAWN_MOVE;
10296            if( toY-fromY<= -2) {
10297                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10298                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10299                         gameInfo.variant != VariantBerolina || toX < fromX)
10300                       board[EP_STATUS] = toX | berolina;
10301                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10302                         gameInfo.variant != VariantBerolina || toX > fromX)
10303                       board[EP_STATUS] = toX;
10304                board[LAST_TO] = toX + 256*toY;
10305            }
10306        }
10307
10308        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10309        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10310        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10311        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10312
10313        for(i=0; i<nrCastlingRights; i++) {
10314            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10315               board[CASTLING][i] == toX   && castlingRank[i] == toY
10316              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10317        }
10318
10319        if(gameInfo.variant == VariantSChess) { // update virginity
10320            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10321            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10322            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10323            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10324        }
10325
10326      if (fromX == toX && fromY == toY && killX < 0) return;
10327
10328      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10329      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10330      if(gameInfo.variant == VariantKnightmate)
10331          king += (int) WhiteUnicorn - (int) WhiteKing;
10332
10333     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10334        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10335         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10336         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10337         board[EP_STATUS] = EP_NONE; // capture was fake!
10338     } else
10339     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10340         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10341         board[toY][toX] = piece;
10342         board[EP_STATUS] = EP_NONE; // capture was fake!
10343     } else
10344     /* Code added by Tord: */
10345     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10346     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10347         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10348       board[EP_STATUS] = EP_NONE; // capture was fake!
10349       board[fromY][fromX] = EmptySquare;
10350       board[toY][toX] = EmptySquare;
10351       if((toX > fromX) != (piece == WhiteRook)) {
10352         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10353       } else {
10354         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10355       }
10356     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10357                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10358       board[EP_STATUS] = EP_NONE;
10359       board[fromY][fromX] = EmptySquare;
10360       board[toY][toX] = EmptySquare;
10361       if((toX > fromX) != (piece == BlackRook)) {
10362         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10363       } else {
10364         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10365       }
10366     /* End of code added by Tord */
10367
10368     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10369         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10370         board[toY][toX] = piece;
10371     } else if (board[fromY][fromX] == king
10372         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10373         && toY == fromY && toX > fromX+1) {
10374         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10375                                                                                              ; // castle with nearest piece
10376         board[fromY][toX-1] = board[fromY][rookX];
10377         board[fromY][rookX] = EmptySquare;
10378         board[fromY][fromX] = EmptySquare;
10379         board[toY][toX] = king;
10380     } else if (board[fromY][fromX] == king
10381         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10382                && toY == fromY && toX < fromX-1) {
10383         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10384                                                                                   ; // castle with nearest piece
10385         board[fromY][toX+1] = board[fromY][rookX];
10386         board[fromY][rookX] = EmptySquare;
10387         board[fromY][fromX] = EmptySquare;
10388         board[toY][toX] = king;
10389     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10390                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10391                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10392                ) {
10393         /* white pawn promotion */
10394         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10395         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10396             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10397         board[fromY][fromX] = EmptySquare;
10398     } else if ((fromY >= BOARD_HEIGHT>>1)
10399                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10400                && (toX != fromX)
10401                && gameInfo.variant != VariantXiangqi
10402                && gameInfo.variant != VariantBerolina
10403                && (pawn == WhitePawn)
10404                && (board[toY][toX] == EmptySquare)) {
10405         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10406         board[fromY][fromX] = EmptySquare;
10407         board[toY][toX] = piece;
10408         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10409     } else if ((fromY == BOARD_HEIGHT-4)
10410                && (toX == fromX)
10411                && gameInfo.variant == VariantBerolina
10412                && (board[fromY][fromX] == WhitePawn)
10413                && (board[toY][toX] == EmptySquare)) {
10414         board[fromY][fromX] = EmptySquare;
10415         board[toY][toX] = WhitePawn;
10416         if(oldEP & EP_BEROLIN_A) {
10417                 captured = board[fromY][fromX-1];
10418                 board[fromY][fromX-1] = EmptySquare;
10419         }else{  captured = board[fromY][fromX+1];
10420                 board[fromY][fromX+1] = EmptySquare;
10421         }
10422     } else if (board[fromY][fromX] == king
10423         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10424                && toY == fromY && toX > fromX+1) {
10425         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10426                                                                                              ;
10427         board[fromY][toX-1] = board[fromY][rookX];
10428         board[fromY][rookX] = EmptySquare;
10429         board[fromY][fromX] = EmptySquare;
10430         board[toY][toX] = king;
10431     } else if (board[fromY][fromX] == king
10432         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10433                && toY == fromY && toX < fromX-1) {
10434         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10435                                                                                 ;
10436         board[fromY][toX+1] = board[fromY][rookX];
10437         board[fromY][rookX] = EmptySquare;
10438         board[fromY][fromX] = EmptySquare;
10439         board[toY][toX] = king;
10440     } else if (fromY == 7 && fromX == 3
10441                && board[fromY][fromX] == BlackKing
10442                && toY == 7 && toX == 5) {
10443         board[fromY][fromX] = EmptySquare;
10444         board[toY][toX] = BlackKing;
10445         board[fromY][7] = EmptySquare;
10446         board[toY][4] = BlackRook;
10447     } else if (fromY == 7 && fromX == 3
10448                && board[fromY][fromX] == BlackKing
10449                && toY == 7 && toX == 1) {
10450         board[fromY][fromX] = EmptySquare;
10451         board[toY][toX] = BlackKing;
10452         board[fromY][0] = EmptySquare;
10453         board[toY][2] = BlackRook;
10454     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10455                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10456                && toY < promoRank && promoChar
10457                ) {
10458         /* black pawn promotion */
10459         board[toY][toX] = CharToPiece(ToLower(promoChar));
10460         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10461             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10462         board[fromY][fromX] = EmptySquare;
10463     } else if ((fromY < BOARD_HEIGHT>>1)
10464                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10465                && (toX != fromX)
10466                && gameInfo.variant != VariantXiangqi
10467                && gameInfo.variant != VariantBerolina
10468                && (pawn == BlackPawn)
10469                && (board[toY][toX] == EmptySquare)) {
10470         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10471         board[fromY][fromX] = EmptySquare;
10472         board[toY][toX] = piece;
10473         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10474     } else if ((fromY == 3)
10475                && (toX == fromX)
10476                && gameInfo.variant == VariantBerolina
10477                && (board[fromY][fromX] == BlackPawn)
10478                && (board[toY][toX] == EmptySquare)) {
10479         board[fromY][fromX] = EmptySquare;
10480         board[toY][toX] = BlackPawn;
10481         if(oldEP & EP_BEROLIN_A) {
10482                 captured = board[fromY][fromX-1];
10483                 board[fromY][fromX-1] = EmptySquare;
10484         }else{  captured = board[fromY][fromX+1];
10485                 board[fromY][fromX+1] = EmptySquare;
10486         }
10487     } else {
10488         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10489         board[fromY][fromX] = EmptySquare;
10490         board[toY][toX] = piece;
10491     }
10492   }
10493
10494     if (gameInfo.holdingsWidth != 0) {
10495
10496       /* !!A lot more code needs to be written to support holdings  */
10497       /* [HGM] OK, so I have written it. Holdings are stored in the */
10498       /* penultimate board files, so they are automaticlly stored   */
10499       /* in the game history.                                       */
10500       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10501                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10502         /* Delete from holdings, by decreasing count */
10503         /* and erasing image if necessary            */
10504         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10505         if(p < (int) BlackPawn) { /* white drop */
10506              p -= (int)WhitePawn;
10507                  p = PieceToNumber((ChessSquare)p);
10508              if(p >= gameInfo.holdingsSize) p = 0;
10509              if(--board[p][BOARD_WIDTH-2] <= 0)
10510                   board[p][BOARD_WIDTH-1] = EmptySquare;
10511              if((int)board[p][BOARD_WIDTH-2] < 0)
10512                         board[p][BOARD_WIDTH-2] = 0;
10513         } else {                  /* black drop */
10514              p -= (int)BlackPawn;
10515                  p = PieceToNumber((ChessSquare)p);
10516              if(p >= gameInfo.holdingsSize) p = 0;
10517              if(--board[handSize-1-p][1] <= 0)
10518                   board[handSize-1-p][0] = EmptySquare;
10519              if((int)board[handSize-1-p][1] < 0)
10520                         board[handSize-1-p][1] = 0;
10521         }
10522       }
10523       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10524           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10525         /* [HGM] holdings: Add to holdings, if holdings exist */
10526         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10527                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10528                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10529         }
10530         p = (int) captured;
10531         if (p >= (int) BlackPawn) {
10532           p -= (int)BlackPawn;
10533           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10534                   /* Restore shogi-promoted piece to its original  first */
10535                   captured = (ChessSquare) (DEMOTED(captured));
10536                   p = DEMOTED(p);
10537           }
10538           p = PieceToNumber((ChessSquare)p);
10539           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10540           board[p][BOARD_WIDTH-2]++;
10541           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10542         } else {
10543           p -= (int)WhitePawn;
10544           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10545                   captured = (ChessSquare) (DEMOTED(captured));
10546                   p = DEMOTED(p);
10547           }
10548           p = PieceToNumber((ChessSquare)p);
10549           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10550           board[handSize-1-p][1]++;
10551           board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10552         }
10553       }
10554     } else if (gameInfo.variant == VariantAtomic) {
10555       if (captured != EmptySquare) {
10556         int y, x;
10557         for (y = toY-1; y <= toY+1; y++) {
10558           for (x = toX-1; x <= toX+1; x++) {
10559             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10560                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10561               board[y][x] = EmptySquare;
10562             }
10563           }
10564         }
10565         board[toY][toX] = EmptySquare;
10566       }
10567     }
10568
10569     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10570         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10571     } else
10572     if(promoChar == '+') {
10573         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10574         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10575         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10576           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10577     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10578         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10579         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10580            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10581         board[toY][toX] = newPiece;
10582     }
10583     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10584                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10585         // [HGM] superchess: take promotion piece out of holdings
10586         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10587         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10588             if(!--board[k][BOARD_WIDTH-2])
10589                 board[k][BOARD_WIDTH-1] = EmptySquare;
10590         } else {
10591             if(!--board[handSize-1-k][1])
10592                 board[handSize-1-k][0] = EmptySquare;
10593         }
10594     }
10595 }
10596
10597 /* Updates forwardMostMove */
10598 void
10599 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10600 {
10601     int x = toX, y = toY, mask;
10602     char *s = parseList[forwardMostMove];
10603     ChessSquare p = boards[forwardMostMove][toY][toX];
10604 //    forwardMostMove++; // [HGM] bare: moved downstream
10605
10606     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10607     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10608     (void) CoordsToAlgebraic(boards[forwardMostMove],
10609                              PosFlags(forwardMostMove),
10610                              fromY, fromX, y, x, (killX < 0)*promoChar,
10611                              s);
10612     if(kill2X >= 0 && kill2Y >= 0)
10613         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10614     if(killX >= 0 && killY >= 0)
10615         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10616                                            toX + AAA, toY + ONE - '0', promoChar);
10617
10618     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10619         int timeLeft; static int lastLoadFlag=0; int king, piece;
10620         piece = boards[forwardMostMove][fromY][fromX];
10621         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10622         if(gameInfo.variant == VariantKnightmate)
10623             king += (int) WhiteUnicorn - (int) WhiteKing;
10624         if(forwardMostMove == 0) {
10625             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10626                 fprintf(serverMoves, "%s;", UserName());
10627             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10628                 fprintf(serverMoves, "%s;", second.tidy);
10629             fprintf(serverMoves, "%s;", first.tidy);
10630             if(gameMode == MachinePlaysWhite)
10631                 fprintf(serverMoves, "%s;", UserName());
10632             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10633                 fprintf(serverMoves, "%s;", second.tidy);
10634         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10635         lastLoadFlag = loadFlag;
10636         // print base move
10637         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10638         // print castling suffix
10639         if( toY == fromY && piece == king ) {
10640             if(toX-fromX > 1)
10641                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10642             if(fromX-toX >1)
10643                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10644         }
10645         // e.p. suffix
10646         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10647              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10648              boards[forwardMostMove][toY][toX] == EmptySquare
10649              && fromX != toX && fromY != toY)
10650                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10651         // promotion suffix
10652         if(promoChar != NULLCHAR) {
10653             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10654                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10655                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10656             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10657         }
10658         if(!loadFlag) {
10659                 char buf[MOVE_LEN*2], *p; int len;
10660             fprintf(serverMoves, "/%d/%d",
10661                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10662             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10663             else                      timeLeft = blackTimeRemaining/1000;
10664             fprintf(serverMoves, "/%d", timeLeft);
10665                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10666                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10667                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10668                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10669             fprintf(serverMoves, "/%s", buf);
10670         }
10671         fflush(serverMoves);
10672     }
10673
10674     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10675         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10676       return;
10677     }
10678     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10679     if (commentList[forwardMostMove+1] != NULL) {
10680         free(commentList[forwardMostMove+1]);
10681         commentList[forwardMostMove+1] = NULL;
10682     }
10683     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10684     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10685     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10686     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10687     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10688     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10689     adjustedClock = FALSE;
10690     gameInfo.result = GameUnfinished;
10691     if (gameInfo.resultDetails != NULL) {
10692         free(gameInfo.resultDetails);
10693         gameInfo.resultDetails = NULL;
10694     }
10695     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10696                               moveList[forwardMostMove - 1]);
10697     mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10698     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10699       case MT_NONE:
10700       case MT_STALEMATE:
10701       default:
10702         break;
10703       case MT_CHECK:
10704         if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10705         if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10706             if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10707             break;
10708         }
10709       case MT_CHECKMATE:
10710       case MT_STAINMATE:
10711         strcat(parseList[forwardMostMove - 1], "#");
10712         break;
10713     }
10714 }
10715
10716 /* Updates currentMove if not pausing */
10717 void
10718 ShowMove (int fromX, int fromY, int toX, int toY)
10719 {
10720     int instant = (gameMode == PlayFromGameFile) ?
10721         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10722     if(appData.noGUI) return;
10723     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10724         if (!instant) {
10725             if (forwardMostMove == currentMove + 1) {
10726                 AnimateMove(boards[forwardMostMove - 1],
10727                             fromX, fromY, toX, toY);
10728             }
10729         }
10730         currentMove = forwardMostMove;
10731     }
10732
10733     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10734
10735     if (instant) return;
10736
10737     DisplayMove(currentMove - 1);
10738     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10739             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10740                 SetHighlights(fromX, fromY, toX, toY);
10741             }
10742     }
10743     DrawPosition(FALSE, boards[currentMove]);
10744     DisplayBothClocks();
10745     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10746 }
10747
10748 void
10749 SendEgtPath (ChessProgramState *cps)
10750 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10751         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10752
10753         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10754
10755         while(*p) {
10756             char c, *q = name+1, *r, *s;
10757
10758             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10759             while(*p && *p != ',') *q++ = *p++;
10760             *q++ = ':'; *q = 0;
10761             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10762                 strcmp(name, ",nalimov:") == 0 ) {
10763                 // take nalimov path from the menu-changeable option first, if it is defined
10764               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10765                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10766             } else
10767             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10768                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10769                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10770                 s = r = StrStr(s, ":") + 1; // beginning of path info
10771                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10772                 c = *r; *r = 0;             // temporarily null-terminate path info
10773                     *--q = 0;               // strip of trailig ':' from name
10774                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10775                 *r = c;
10776                 SendToProgram(buf,cps);     // send egtbpath command for this format
10777             }
10778             if(*p == ',') p++; // read away comma to position for next format name
10779         }
10780 }
10781
10782 static int
10783 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10784 {
10785       int width = 8, height = 8, holdings = 0;             // most common sizes
10786       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10787       // correct the deviations default for each variant
10788       if( v == VariantXiangqi ) width = 9,  height = 10;
10789       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10790       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10791       if( v == VariantCapablanca || v == VariantCapaRandom ||
10792           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10793                                 width = 10;
10794       if( v == VariantCourier ) width = 12;
10795       if( v == VariantSuper )                            holdings = 8;
10796       if( v == VariantGreat )   width = 10,              holdings = 8;
10797       if( v == VariantSChess )                           holdings = 7;
10798       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10799       if( v == VariantChuChess) width = 10, height = 10;
10800       if( v == VariantChu )     width = 12, height = 12;
10801       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10802              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10803              holdingsSize >= 0 && holdingsSize != holdings;
10804 }
10805
10806 char variantError[MSG_SIZ];
10807
10808 char *
10809 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10810 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10811       char *p, *variant = VariantName(v);
10812       static char b[MSG_SIZ];
10813       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10814            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10815                                                holdingsSize, variant); // cook up sized variant name
10816            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10817            if(StrStr(list, b) == NULL) {
10818                // specific sized variant not known, check if general sizing allowed
10819                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10820                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10821                             boardWidth, boardHeight, holdingsSize, engine);
10822                    return NULL;
10823                }
10824                /* [HGM] here we really should compare with the maximum supported board size */
10825            }
10826       } else snprintf(b, MSG_SIZ,"%s", variant);
10827       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10828       p = StrStr(list, b);
10829       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10830       if(p == NULL) {
10831           // occurs not at all in list, or only as sub-string
10832           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10833           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10834               int l = strlen(variantError);
10835               char *q;
10836               while(p != list && p[-1] != ',') p--;
10837               q = strchr(p, ',');
10838               if(q) *q = NULLCHAR;
10839               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10840               if(q) *q= ',';
10841           }
10842           return NULL;
10843       }
10844       return b;
10845 }
10846
10847 void
10848 InitChessProgram (ChessProgramState *cps, int setup)
10849 /* setup needed to setup FRC opening position */
10850 {
10851     char buf[MSG_SIZ], *b;
10852     if (appData.noChessProgram) return;
10853     hintRequested = FALSE;
10854     bookRequested = FALSE;
10855
10856     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10857     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10858     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10859     if(cps->memSize) { /* [HGM] memory */
10860       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10861         SendToProgram(buf, cps);
10862     }
10863     SendEgtPath(cps); /* [HGM] EGT */
10864     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10865       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10866         SendToProgram(buf, cps);
10867     }
10868
10869     setboardSpoiledMachineBlack = FALSE;
10870     SendToProgram(cps->initString, cps);
10871     if (gameInfo.variant != VariantNormal &&
10872         gameInfo.variant != VariantLoadable
10873         /* [HGM] also send variant if board size non-standard */
10874         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10875
10876       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10877                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10878
10879       if (b == NULL) {
10880         VariantClass v;
10881         char c, *q = cps->variants, *p = strchr(q, ',');
10882         if(p) *p = NULLCHAR;
10883         v = StringToVariant(q);
10884         DisplayError(variantError, 0);
10885         if(v != VariantUnknown && cps == &first) {
10886             int w, h, s;
10887             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10888                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10889             ASSIGN(appData.variant, q);
10890             Reset(TRUE, FALSE);
10891         }
10892         if(p) *p = ',';
10893         return;
10894       }
10895
10896       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10897       SendToProgram(buf, cps);
10898     }
10899     currentlyInitializedVariant = gameInfo.variant;
10900
10901     /* [HGM] send opening position in FRC to first engine */
10902     if(setup) {
10903           SendToProgram("force\n", cps);
10904           SendBoard(cps, 0);
10905           /* engine is now in force mode! Set flag to wake it up after first move. */
10906           setboardSpoiledMachineBlack = 1;
10907     }
10908
10909     if (cps->sendICS) {
10910       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10911       SendToProgram(buf, cps);
10912     }
10913     cps->maybeThinking = FALSE;
10914     cps->offeredDraw = 0;
10915     if (!appData.icsActive) {
10916         SendTimeControl(cps, movesPerSession, timeControl,
10917                         timeIncrement, appData.searchDepth,
10918                         searchTime);
10919     }
10920     if (appData.showThinking
10921         // [HGM] thinking: four options require thinking output to be sent
10922         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10923                                 ) {
10924         SendToProgram("post\n", cps);
10925     }
10926     SendToProgram("hard\n", cps);
10927     if (!appData.ponderNextMove) {
10928         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10929            it without being sure what state we are in first.  "hard"
10930            is not a toggle, so that one is OK.
10931          */
10932         SendToProgram("easy\n", cps);
10933     }
10934     if (cps->usePing) {
10935       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10936       SendToProgram(buf, cps);
10937     }
10938     cps->initDone = TRUE;
10939     ClearEngineOutputPane(cps == &second);
10940 }
10941
10942
10943 char *
10944 ResendOptions (ChessProgramState *cps, int toEngine)
10945 { // send the stored value of the options
10946   int i;
10947   static char buf2[MSG_SIZ*10];
10948   char buf[MSG_SIZ], *p = buf2;
10949   Option *opt = cps->option;
10950   *p = NULLCHAR;
10951   for(i=0; i<cps->nrOptions; i++, opt++) {
10952       *buf = NULLCHAR;
10953       switch(opt->type) {
10954         case Spin:
10955         case Slider:
10956         case CheckBox:
10957             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10958             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
10959           break;
10960         case ComboBox:
10961             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10962             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
10963           break;
10964         default:
10965             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
10966             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
10967           break;
10968         case Button:
10969         case SaveButton:
10970           continue;
10971       }
10972       if(*buf) {
10973         if(toEngine) {
10974           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
10975           SendToProgram(buf2, cps);
10976         } else {
10977           if(p != buf2) *p++ = ',';
10978           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
10979           while(*p) p++;
10980         }
10981       }
10982   }
10983   return buf2;
10984 }
10985
10986 void
10987 StartChessProgram (ChessProgramState *cps)
10988 {
10989     char buf[MSG_SIZ];
10990     int err;
10991
10992     if (appData.noChessProgram) return;
10993     cps->initDone = FALSE;
10994
10995     if (strcmp(cps->host, "localhost") == 0) {
10996         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10997     } else if (*appData.remoteShell == NULLCHAR) {
10998         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10999     } else {
11000         if (*appData.remoteUser == NULLCHAR) {
11001           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11002                     cps->program);
11003         } else {
11004           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11005                     cps->host, appData.remoteUser, cps->program);
11006         }
11007         err = StartChildProcess(buf, "", &cps->pr);
11008     }
11009
11010     if (err != 0) {
11011       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11012         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11013         if(cps != &first) return;
11014         appData.noChessProgram = TRUE;
11015         ThawUI();
11016         SetNCPMode();
11017 //      DisplayFatalError(buf, err, 1);
11018 //      cps->pr = NoProc;
11019 //      cps->isr = NULL;
11020         return;
11021     }
11022
11023     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11024     if (cps->protocolVersion > 1) {
11025       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11026       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11027         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11028         cps->comboCnt = 0;  //                and values of combo boxes
11029       }
11030       SendToProgram(buf, cps);
11031       if(cps->reload) ResendOptions(cps, TRUE);
11032     } else {
11033       SendToProgram("xboard\n", cps);
11034     }
11035 }
11036
11037 void
11038 TwoMachinesEventIfReady P((void))
11039 {
11040   static int curMess = 0;
11041   if (first.lastPing != first.lastPong) {
11042     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11043     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11044     return;
11045   }
11046   if (second.lastPing != second.lastPong) {
11047     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11048     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11049     return;
11050   }
11051   DisplayMessage("", ""); curMess = 0;
11052   TwoMachinesEvent();
11053 }
11054
11055 char *
11056 MakeName (char *template)
11057 {
11058     time_t clock;
11059     struct tm *tm;
11060     static char buf[MSG_SIZ];
11061     char *p = buf;
11062     int i;
11063
11064     clock = time((time_t *)NULL);
11065     tm = localtime(&clock);
11066
11067     while(*p++ = *template++) if(p[-1] == '%') {
11068         switch(*template++) {
11069           case 0:   *p = 0; return buf;
11070           case 'Y': i = tm->tm_year+1900; break;
11071           case 'y': i = tm->tm_year-100; break;
11072           case 'M': i = tm->tm_mon+1; break;
11073           case 'd': i = tm->tm_mday; break;
11074           case 'h': i = tm->tm_hour; break;
11075           case 'm': i = tm->tm_min; break;
11076           case 's': i = tm->tm_sec; break;
11077           default:  i = 0;
11078         }
11079         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11080     }
11081     return buf;
11082 }
11083
11084 int
11085 CountPlayers (char *p)
11086 {
11087     int n = 0;
11088     while(p = strchr(p, '\n')) p++, n++; // count participants
11089     return n;
11090 }
11091
11092 FILE *
11093 WriteTourneyFile (char *results, FILE *f)
11094 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11095     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11096     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11097         // create a file with tournament description
11098         fprintf(f, "-participants {%s}\n", appData.participants);
11099         fprintf(f, "-seedBase %d\n", appData.seedBase);
11100         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11101         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11102         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11103         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11104         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11105         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11106         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11107         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11108         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11109         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11110         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11111         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11112         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11113         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11114         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11115         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11116         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11117         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11118         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11119         fprintf(f, "-smpCores %d\n", appData.smpCores);
11120         if(searchTime > 0)
11121                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11122         else {
11123                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11124                 fprintf(f, "-tc %s\n", appData.timeControl);
11125                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11126         }
11127         fprintf(f, "-results \"%s\"\n", results);
11128     }
11129     return f;
11130 }
11131
11132 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11133
11134 void
11135 Substitute (char *participants, int expunge)
11136 {
11137     int i, changed, changes=0, nPlayers=0;
11138     char *p, *q, *r, buf[MSG_SIZ];
11139     if(participants == NULL) return;
11140     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11141     r = p = participants; q = appData.participants;
11142     while(*p && *p == *q) {
11143         if(*p == '\n') r = p+1, nPlayers++;
11144         p++; q++;
11145     }
11146     if(*p) { // difference
11147         while(*p && *p++ != '\n')
11148                                  ;
11149         while(*q && *q++ != '\n')
11150                                  ;
11151       changed = nPlayers;
11152         changes = 1 + (strcmp(p, q) != 0);
11153     }
11154     if(changes == 1) { // a single engine mnemonic was changed
11155         q = r; while(*q) nPlayers += (*q++ == '\n');
11156         p = buf; while(*r && (*p = *r++) != '\n') p++;
11157         *p = NULLCHAR;
11158         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11159         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11160         if(mnemonic[i]) { // The substitute is valid
11161             FILE *f;
11162             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11163                 flock(fileno(f), LOCK_EX);
11164                 ParseArgsFromFile(f);
11165                 fseek(f, 0, SEEK_SET);
11166                 FREE(appData.participants); appData.participants = participants;
11167                 if(expunge) { // erase results of replaced engine
11168                     int len = strlen(appData.results), w, b, dummy;
11169                     for(i=0; i<len; i++) {
11170                         Pairing(i, nPlayers, &w, &b, &dummy);
11171                         if((w == changed || b == changed) && appData.results[i] == '*') {
11172                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11173                             fclose(f);
11174                             return;
11175                         }
11176                     }
11177                     for(i=0; i<len; i++) {
11178                         Pairing(i, nPlayers, &w, &b, &dummy);
11179                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11180                     }
11181                 }
11182                 WriteTourneyFile(appData.results, f);
11183                 fclose(f); // release lock
11184                 return;
11185             }
11186         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11187     }
11188     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11189     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11190     free(participants);
11191     return;
11192 }
11193
11194 int
11195 CheckPlayers (char *participants)
11196 {
11197         int i;
11198         char buf[MSG_SIZ], *p;
11199         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11200         while(p = strchr(participants, '\n')) {
11201             *p = NULLCHAR;
11202             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11203             if(!mnemonic[i]) {
11204                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11205                 *p = '\n';
11206                 DisplayError(buf, 0);
11207                 return 1;
11208             }
11209             *p = '\n';
11210             participants = p + 1;
11211         }
11212         return 0;
11213 }
11214
11215 int
11216 CreateTourney (char *name)
11217 {
11218         FILE *f;
11219         if(matchMode && strcmp(name, appData.tourneyFile)) {
11220              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11221         }
11222         if(name[0] == NULLCHAR) {
11223             if(appData.participants[0])
11224                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11225             return 0;
11226         }
11227         f = fopen(name, "r");
11228         if(f) { // file exists
11229             ASSIGN(appData.tourneyFile, name);
11230             ParseArgsFromFile(f); // parse it
11231         } else {
11232             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11233             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11234                 DisplayError(_("Not enough participants"), 0);
11235                 return 0;
11236             }
11237             if(CheckPlayers(appData.participants)) return 0;
11238             ASSIGN(appData.tourneyFile, name);
11239             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11240             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11241         }
11242         fclose(f);
11243         appData.noChessProgram = FALSE;
11244         appData.clockMode = TRUE;
11245         SetGNUMode();
11246         return 1;
11247 }
11248
11249 int
11250 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11251 {
11252     char buf[2*MSG_SIZ], *p, *q;
11253     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11254     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11255     skip = !all && group[0]; // if group requested, we start in skip mode
11256     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11257         p = names; q = buf; header = 0;
11258         while(*p && *p != '\n') *q++ = *p++;
11259         *q = 0;
11260         if(*p == '\n') p++;
11261         if(buf[0] == '#') {
11262             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11263             depth++; // we must be entering a new group
11264             if(all) continue; // suppress printing group headers when complete list requested
11265             header = 1;
11266             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11267         }
11268         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11269         if(engineList[i]) free(engineList[i]);
11270         engineList[i] = strdup(buf);
11271         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11272         if(engineMnemonic[i]) free(engineMnemonic[i]);
11273         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11274             strcat(buf, " (");
11275             sscanf(q + 8, "%s", buf + strlen(buf));
11276             strcat(buf, ")");
11277         }
11278         engineMnemonic[i] = strdup(buf);
11279         i++;
11280     }
11281     engineList[i] = engineMnemonic[i] = NULL;
11282     return i;
11283 }
11284
11285 void
11286 SaveEngineSettings (int n)
11287 {
11288     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11289     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11290     p = strstr(firstChessProgramNames, currentEngine[n]);
11291     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11292     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11293     len = strlen(currentEngine[n]);
11294     q = p + len; *p = 0; // cut list into head and tail piece
11295     s = strstr(currentEngine[n], "firstOptions");
11296     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11297         char *r = s + 14;
11298         while(*r && *r != s[13]) r++;
11299         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11300         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11301     } else if(*optionSettings) {
11302         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11303     }
11304     ASSIGN(currentEngine[n], buf); // updated engine line
11305     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11306     s = malloc(len);
11307     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11308     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11309 }
11310
11311 // following implemented as macro to avoid type limitations
11312 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11313
11314 void
11315 SwapEngines (int n)
11316 {   // swap settings for first engine and other engine (so far only some selected options)
11317     int h;
11318     char *p;
11319     if(n == 0) return;
11320     SWAP(directory, p)
11321     SWAP(chessProgram, p)
11322     SWAP(isUCI, h)
11323     SWAP(hasOwnBookUCI, h)
11324     SWAP(protocolVersion, h)
11325     SWAP(reuse, h)
11326     SWAP(scoreIsAbsolute, h)
11327     SWAP(timeOdds, h)
11328     SWAP(logo, p)
11329     SWAP(pgnName, p)
11330     SWAP(pvSAN, h)
11331     SWAP(engOptions, p)
11332     SWAP(engInitString, p)
11333     SWAP(computerString, p)
11334     SWAP(features, p)
11335     SWAP(fenOverride, p)
11336     SWAP(NPS, h)
11337     SWAP(accumulateTC, h)
11338     SWAP(drawDepth, h)
11339     SWAP(host, p)
11340     SWAP(pseudo, h)
11341 }
11342
11343 int
11344 GetEngineLine (char *s, int n)
11345 {
11346     int i;
11347     char buf[MSG_SIZ];
11348     extern char *icsNames;
11349     if(!s || !*s) return 0;
11350     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11351     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11352     if(!mnemonic[i]) return 0;
11353     if(n == 11) return 1; // just testing if there was a match
11354     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11355     if(n == 1) SwapEngines(n);
11356     ParseArgsFromString(buf);
11357     if(n == 1) SwapEngines(n);
11358     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11359     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11360         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11361         ParseArgsFromString(buf);
11362     }
11363     return 1;
11364 }
11365
11366 int
11367 SetPlayer (int player, char *p)
11368 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11369     int i;
11370     char buf[MSG_SIZ], *engineName;
11371     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11372     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11373     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11374     if(mnemonic[i]) {
11375         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11376         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11377         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11378         ParseArgsFromString(buf);
11379     } else { // no engine with this nickname is installed!
11380         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11381         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11382         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11383         ModeHighlight();
11384         DisplayError(buf, 0);
11385         return 0;
11386     }
11387     free(engineName);
11388     return i;
11389 }
11390
11391 char *recentEngines;
11392
11393 void
11394 RecentEngineEvent (int nr)
11395 {
11396     int n;
11397 //    SwapEngines(1); // bump first to second
11398 //    ReplaceEngine(&second, 1); // and load it there
11399     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11400     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11401     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11402         ReplaceEngine(&first, 0);
11403         FloatToFront(&appData.recentEngineList, command[n]);
11404     }
11405 }
11406
11407 int
11408 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11409 {   // determine players from game number
11410     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11411
11412     if(appData.tourneyType == 0) {
11413         roundsPerCycle = (nPlayers - 1) | 1;
11414         pairingsPerRound = nPlayers / 2;
11415     } else if(appData.tourneyType > 0) {
11416         roundsPerCycle = nPlayers - appData.tourneyType;
11417         pairingsPerRound = appData.tourneyType;
11418     }
11419     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11420     gamesPerCycle = gamesPerRound * roundsPerCycle;
11421     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11422     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11423     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11424     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11425     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11426     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11427
11428     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11429     if(appData.roundSync) *syncInterval = gamesPerRound;
11430
11431     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11432
11433     if(appData.tourneyType == 0) {
11434         if(curPairing == (nPlayers-1)/2 ) {
11435             *whitePlayer = curRound;
11436             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11437         } else {
11438             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11439             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11440             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11441             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11442         }
11443     } else if(appData.tourneyType > 1) {
11444         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11445         *whitePlayer = curRound + appData.tourneyType;
11446     } else if(appData.tourneyType > 0) {
11447         *whitePlayer = curPairing;
11448         *blackPlayer = curRound + appData.tourneyType;
11449     }
11450
11451     // take care of white/black alternation per round.
11452     // For cycles and games this is already taken care of by default, derived from matchGame!
11453     return curRound & 1;
11454 }
11455
11456 int
11457 NextTourneyGame (int nr, int *swapColors)
11458 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11459     char *p, *q;
11460     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11461     FILE *tf;
11462     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11463     tf = fopen(appData.tourneyFile, "r");
11464     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11465     ParseArgsFromFile(tf); fclose(tf);
11466     InitTimeControls(); // TC might be altered from tourney file
11467
11468     nPlayers = CountPlayers(appData.participants); // count participants
11469     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11470     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11471
11472     if(syncInterval) {
11473         p = q = appData.results;
11474         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11475         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11476             DisplayMessage(_("Waiting for other game(s)"),"");
11477             waitingForGame = TRUE;
11478             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11479             return 0;
11480         }
11481         waitingForGame = FALSE;
11482     }
11483
11484     if(appData.tourneyType < 0) {
11485         if(nr>=0 && !pairingReceived) {
11486             char buf[1<<16];
11487             if(pairing.pr == NoProc) {
11488                 if(!appData.pairingEngine[0]) {
11489                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11490                     return 0;
11491                 }
11492                 StartChessProgram(&pairing); // starts the pairing engine
11493             }
11494             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11495             SendToProgram(buf, &pairing);
11496             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11497             SendToProgram(buf, &pairing);
11498             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11499         }
11500         pairingReceived = 0;                              // ... so we continue here
11501         *swapColors = 0;
11502         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11503         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11504         matchGame = 1; roundNr = nr / syncInterval + 1;
11505     }
11506
11507     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11508
11509     // redefine engines, engine dir, etc.
11510     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11511     if(first.pr == NoProc) {
11512       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11513       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11514     }
11515     if(second.pr == NoProc) {
11516       SwapEngines(1);
11517       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11518       SwapEngines(1);         // and make that valid for second engine by swapping
11519       InitEngine(&second, 1);
11520     }
11521     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11522     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11523     return OK;
11524 }
11525
11526 void
11527 NextMatchGame ()
11528 {   // performs game initialization that does not invoke engines, and then tries to start the game
11529     int res, firstWhite, swapColors = 0;
11530     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11531     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
11532         char buf[MSG_SIZ];
11533         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11534         if(strcmp(buf, currentDebugFile)) { // name has changed
11535             FILE *f = fopen(buf, "w");
11536             if(f) { // if opening the new file failed, just keep using the old one
11537                 ASSIGN(currentDebugFile, buf);
11538                 fclose(debugFP);
11539                 debugFP = f;
11540             }
11541             if(appData.serverFileName) {
11542                 if(serverFP) fclose(serverFP);
11543                 serverFP = fopen(appData.serverFileName, "w");
11544                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11545                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11546             }
11547         }
11548     }
11549     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11550     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11551     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11552     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11553     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11554     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11555     Reset(FALSE, first.pr != NoProc);
11556     res = LoadGameOrPosition(matchGame); // setup game
11557     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11558     if(!res) return; // abort when bad game/pos file
11559     if(appData.epd) {// in EPD mode we make sure first engine is to move
11560         firstWhite = !(forwardMostMove & 1);
11561         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11562         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11563     }
11564     TwoMachinesEvent();
11565 }
11566
11567 void
11568 UserAdjudicationEvent (int result)
11569 {
11570     ChessMove gameResult = GameIsDrawn;
11571
11572     if( result > 0 ) {
11573         gameResult = WhiteWins;
11574     }
11575     else if( result < 0 ) {
11576         gameResult = BlackWins;
11577     }
11578
11579     if( gameMode == TwoMachinesPlay ) {
11580         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11581     }
11582 }
11583
11584
11585 // [HGM] save: calculate checksum of game to make games easily identifiable
11586 int
11587 StringCheckSum (char *s)
11588 {
11589         int i = 0;
11590         if(s==NULL) return 0;
11591         while(*s) i = i*259 + *s++;
11592         return i;
11593 }
11594
11595 int
11596 GameCheckSum ()
11597 {
11598         int i, sum=0;
11599         for(i=backwardMostMove; i<forwardMostMove; i++) {
11600                 sum += pvInfoList[i].depth;
11601                 sum += StringCheckSum(parseList[i]);
11602                 sum += StringCheckSum(commentList[i]);
11603                 sum *= 261;
11604         }
11605         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11606         return sum + StringCheckSum(commentList[i]);
11607 } // end of save patch
11608
11609 void
11610 GameEnds (ChessMove result, char *resultDetails, int whosays)
11611 {
11612     GameMode nextGameMode;
11613     int isIcsGame;
11614     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11615
11616     if(endingGame) return; /* [HGM] crash: forbid recursion */
11617     endingGame = 1;
11618     if(twoBoards) { // [HGM] dual: switch back to one board
11619         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11620         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11621     }
11622     if (appData.debugMode) {
11623       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11624               result, resultDetails ? resultDetails : "(null)", whosays);
11625     }
11626
11627     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11628
11629     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11630
11631     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11632         /* If we are playing on ICS, the server decides when the
11633            game is over, but the engine can offer to draw, claim
11634            a draw, or resign.
11635          */
11636 #if ZIPPY
11637         if (appData.zippyPlay && first.initDone) {
11638             if (result == GameIsDrawn) {
11639                 /* In case draw still needs to be claimed */
11640                 SendToICS(ics_prefix);
11641                 SendToICS("draw\n");
11642             } else if (StrCaseStr(resultDetails, "resign")) {
11643                 SendToICS(ics_prefix);
11644                 SendToICS("resign\n");
11645             }
11646         }
11647 #endif
11648         endingGame = 0; /* [HGM] crash */
11649         return;
11650     }
11651
11652     /* If we're loading the game from a file, stop */
11653     if (whosays == GE_FILE) {
11654       (void) StopLoadGameTimer();
11655       gameFileFP = NULL;
11656     }
11657
11658     /* Cancel draw offers */
11659     first.offeredDraw = second.offeredDraw = 0;
11660
11661     /* If this is an ICS game, only ICS can really say it's done;
11662        if not, anyone can. */
11663     isIcsGame = (gameMode == IcsPlayingWhite ||
11664                  gameMode == IcsPlayingBlack ||
11665                  gameMode == IcsObserving    ||
11666                  gameMode == IcsExamining);
11667
11668     if (!isIcsGame || whosays == GE_ICS) {
11669         /* OK -- not an ICS game, or ICS said it was done */
11670         StopClocks();
11671         if (!isIcsGame && !appData.noChessProgram)
11672           SetUserThinkingEnables();
11673
11674         /* [HGM] if a machine claims the game end we verify this claim */
11675         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11676             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11677                 char claimer;
11678                 ChessMove trueResult = (ChessMove) -1;
11679
11680                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11681                                             first.twoMachinesColor[0] :
11682                                             second.twoMachinesColor[0] ;
11683
11684                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11685                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11686                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11687                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11688                 } else
11689                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11690                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11691                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11692                 } else
11693                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11694                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11695                 }
11696
11697                 // now verify win claims, but not in drop games, as we don't understand those yet
11698                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11699                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11700                     (result == WhiteWins && claimer == 'w' ||
11701                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11702                       if (appData.debugMode) {
11703                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11704                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11705                       }
11706                       if(result != trueResult) {
11707                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11708                               result = claimer == 'w' ? BlackWins : WhiteWins;
11709                               resultDetails = buf;
11710                       }
11711                 } else
11712                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11713                     && (forwardMostMove <= backwardMostMove ||
11714                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11715                         (claimer=='b')==(forwardMostMove&1))
11716                                                                                   ) {
11717                       /* [HGM] verify: draws that were not flagged are false claims */
11718                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11719                       result = claimer == 'w' ? BlackWins : WhiteWins;
11720                       resultDetails = buf;
11721                 }
11722                 /* (Claiming a loss is accepted no questions asked!) */
11723             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11724                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11725                 result = GameUnfinished;
11726                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11727             }
11728             /* [HGM] bare: don't allow bare King to win */
11729             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11730                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11731                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11732                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11733                && result != GameIsDrawn)
11734             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11735                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11736                         int p = (int)boards[forwardMostMove][i][j] - color;
11737                         if(p >= 0 && p <= (int)WhiteKing) k++;
11738                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11739                 }
11740                 if (appData.debugMode) {
11741                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11742                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11743                 }
11744                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11745                         result = GameIsDrawn;
11746                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11747                         resultDetails = buf;
11748                 }
11749             }
11750         }
11751
11752
11753         if(serverMoves != NULL && !loadFlag) { char c = '=';
11754             if(result==WhiteWins) c = '+';
11755             if(result==BlackWins) c = '-';
11756             if(resultDetails != NULL)
11757                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11758         }
11759         if (resultDetails != NULL) {
11760             gameInfo.result = result;
11761             gameInfo.resultDetails = StrSave(resultDetails);
11762
11763             /* display last move only if game was not loaded from file */
11764             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11765                 DisplayMove(currentMove - 1);
11766
11767             if (forwardMostMove != 0) {
11768                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11769                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11770                                                                 ) {
11771                     if (*appData.saveGameFile != NULLCHAR) {
11772                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11773                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11774                         else
11775                         SaveGameToFile(appData.saveGameFile, TRUE);
11776                     } else if (appData.autoSaveGames) {
11777                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11778                     }
11779                     if (*appData.savePositionFile != NULLCHAR) {
11780                         SavePositionToFile(appData.savePositionFile);
11781                     }
11782                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11783                 }
11784             }
11785
11786             /* Tell program how game ended in case it is learning */
11787             /* [HGM] Moved this to after saving the PGN, just in case */
11788             /* engine died and we got here through time loss. In that */
11789             /* case we will get a fatal error writing the pipe, which */
11790             /* would otherwise lose us the PGN.                       */
11791             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11792             /* output during GameEnds should never be fatal anymore   */
11793             if (gameMode == MachinePlaysWhite ||
11794                 gameMode == MachinePlaysBlack ||
11795                 gameMode == TwoMachinesPlay ||
11796                 gameMode == IcsPlayingWhite ||
11797                 gameMode == IcsPlayingBlack ||
11798                 gameMode == BeginningOfGame) {
11799                 char buf[MSG_SIZ];
11800                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11801                         resultDetails);
11802                 if (first.pr != NoProc) {
11803                     SendToProgram(buf, &first);
11804                 }
11805                 if (second.pr != NoProc &&
11806                     gameMode == TwoMachinesPlay) {
11807                     SendToProgram(buf, &second);
11808                 }
11809             }
11810         }
11811
11812         if (appData.icsActive) {
11813             if (appData.quietPlay &&
11814                 (gameMode == IcsPlayingWhite ||
11815                  gameMode == IcsPlayingBlack)) {
11816                 SendToICS(ics_prefix);
11817                 SendToICS("set shout 1\n");
11818             }
11819             nextGameMode = IcsIdle;
11820             ics_user_moved = FALSE;
11821             /* clean up premove.  It's ugly when the game has ended and the
11822              * premove highlights are still on the board.
11823              */
11824             if (gotPremove) {
11825               gotPremove = FALSE;
11826               ClearPremoveHighlights();
11827               DrawPosition(FALSE, boards[currentMove]);
11828             }
11829             if (whosays == GE_ICS) {
11830                 switch (result) {
11831                 case WhiteWins:
11832                     if (gameMode == IcsPlayingWhite)
11833                         PlayIcsWinSound();
11834                     else if(gameMode == IcsPlayingBlack)
11835                         PlayIcsLossSound();
11836                     break;
11837                 case BlackWins:
11838                     if (gameMode == IcsPlayingBlack)
11839                         PlayIcsWinSound();
11840                     else if(gameMode == IcsPlayingWhite)
11841                         PlayIcsLossSound();
11842                     break;
11843                 case GameIsDrawn:
11844                     PlayIcsDrawSound();
11845                     break;
11846                 default:
11847                     PlayIcsUnfinishedSound();
11848                 }
11849             }
11850             if(appData.quitNext) { ExitEvent(0); return; }
11851         } else if (gameMode == EditGame ||
11852                    gameMode == PlayFromGameFile ||
11853                    gameMode == AnalyzeMode ||
11854                    gameMode == AnalyzeFile) {
11855             nextGameMode = gameMode;
11856         } else {
11857             nextGameMode = EndOfGame;
11858         }
11859         pausing = FALSE;
11860         ModeHighlight();
11861     } else {
11862         nextGameMode = gameMode;
11863     }
11864
11865     if (appData.noChessProgram) {
11866         gameMode = nextGameMode;
11867         ModeHighlight();
11868         endingGame = 0; /* [HGM] crash */
11869         return;
11870     }
11871
11872     if (first.reuse) {
11873         /* Put first chess program into idle state */
11874         if (first.pr != NoProc &&
11875             (gameMode == MachinePlaysWhite ||
11876              gameMode == MachinePlaysBlack ||
11877              gameMode == TwoMachinesPlay ||
11878              gameMode == IcsPlayingWhite ||
11879              gameMode == IcsPlayingBlack ||
11880              gameMode == BeginningOfGame)) {
11881             SendToProgram("force\n", &first);
11882             if (first.usePing) {
11883               char buf[MSG_SIZ];
11884               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11885               SendToProgram(buf, &first);
11886             }
11887         }
11888     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11889         /* Kill off first chess program */
11890         if (first.isr != NULL)
11891           RemoveInputSource(first.isr);
11892         first.isr = NULL;
11893
11894         if (first.pr != NoProc) {
11895             ExitAnalyzeMode();
11896             DoSleep( appData.delayBeforeQuit );
11897             SendToProgram("quit\n", &first);
11898             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11899             first.reload = TRUE;
11900         }
11901         first.pr = NoProc;
11902     }
11903     if (second.reuse) {
11904         /* Put second chess program into idle state */
11905         if (second.pr != NoProc &&
11906             gameMode == TwoMachinesPlay) {
11907             SendToProgram("force\n", &second);
11908             if (second.usePing) {
11909               char buf[MSG_SIZ];
11910               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11911               SendToProgram(buf, &second);
11912             }
11913         }
11914     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11915         /* Kill off second chess program */
11916         if (second.isr != NULL)
11917           RemoveInputSource(second.isr);
11918         second.isr = NULL;
11919
11920         if (second.pr != NoProc) {
11921             DoSleep( appData.delayBeforeQuit );
11922             SendToProgram("quit\n", &second);
11923             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11924             second.reload = TRUE;
11925         }
11926         second.pr = NoProc;
11927     }
11928
11929     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11930         char resChar = '=';
11931         switch (result) {
11932         case WhiteWins:
11933           resChar = '+';
11934           if (first.twoMachinesColor[0] == 'w') {
11935             first.matchWins++;
11936           } else {
11937             second.matchWins++;
11938           }
11939           break;
11940         case BlackWins:
11941           resChar = '-';
11942           if (first.twoMachinesColor[0] == 'b') {
11943             first.matchWins++;
11944           } else {
11945             second.matchWins++;
11946           }
11947           break;
11948         case GameUnfinished:
11949           resChar = ' ';
11950         default:
11951           break;
11952         }
11953
11954         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11955         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11956             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11957             ReserveGame(nextGame, resChar); // sets nextGame
11958             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11959             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11960         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11961
11962         if (nextGame <= appData.matchGames && !abortMatch) {
11963             gameMode = nextGameMode;
11964             matchGame = nextGame; // this will be overruled in tourney mode!
11965             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11966             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11967             endingGame = 0; /* [HGM] crash */
11968             return;
11969         } else {
11970             gameMode = nextGameMode;
11971             if(appData.epd) {
11972                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11973                 OutputKibitz(2, buf);
11974                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11975                 OutputKibitz(2, buf);
11976                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11977                 if(second.matchWins) OutputKibitz(2, buf);
11978                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11979                 OutputKibitz(2, buf);
11980             }
11981             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11982                      first.tidy, second.tidy,
11983                      first.matchWins, second.matchWins,
11984                      appData.matchGames - (first.matchWins + second.matchWins));
11985             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11986             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11987             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11988             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11989                 first.twoMachinesColor = "black\n";
11990                 second.twoMachinesColor = "white\n";
11991             } else {
11992                 first.twoMachinesColor = "white\n";
11993                 second.twoMachinesColor = "black\n";
11994             }
11995         }
11996     }
11997     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11998         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11999       ExitAnalyzeMode();
12000     gameMode = nextGameMode;
12001     ModeHighlight();
12002     endingGame = 0;  /* [HGM] crash */
12003     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12004         if(matchMode == TRUE) { // match through command line: exit with or without popup
12005             if(ranking) {
12006                 ToNrEvent(forwardMostMove);
12007                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12008                 else ExitEvent(0);
12009             } else DisplayFatalError(buf, 0, 0);
12010         } else { // match through menu; just stop, with or without popup
12011             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12012             ModeHighlight();
12013             if(ranking){
12014                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12015             } else DisplayNote(buf);
12016       }
12017       if(ranking) free(ranking);
12018     }
12019 }
12020
12021 /* Assumes program was just initialized (initString sent).
12022    Leaves program in force mode. */
12023 void
12024 FeedMovesToProgram (ChessProgramState *cps, int upto)
12025 {
12026     int i;
12027
12028     if (appData.debugMode)
12029       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12030               startedFromSetupPosition ? "position and " : "",
12031               backwardMostMove, upto, cps->which);
12032     if(currentlyInitializedVariant != gameInfo.variant) {
12033       char buf[MSG_SIZ];
12034         // [HGM] variantswitch: make engine aware of new variant
12035         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12036                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12037                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12038         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12039         SendToProgram(buf, cps);
12040         currentlyInitializedVariant = gameInfo.variant;
12041     }
12042     SendToProgram("force\n", cps);
12043     if (startedFromSetupPosition) {
12044         SendBoard(cps, backwardMostMove);
12045     if (appData.debugMode) {
12046         fprintf(debugFP, "feedMoves\n");
12047     }
12048     }
12049     for (i = backwardMostMove; i < upto; i++) {
12050         SendMoveToProgram(i, cps);
12051     }
12052 }
12053
12054
12055 int
12056 ResurrectChessProgram ()
12057 {
12058      /* The chess program may have exited.
12059         If so, restart it and feed it all the moves made so far. */
12060     static int doInit = 0;
12061
12062     if (appData.noChessProgram) return 1;
12063
12064     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12065         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12066         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12067         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12068     } else {
12069         if (first.pr != NoProc) return 1;
12070         StartChessProgram(&first);
12071     }
12072     InitChessProgram(&first, FALSE);
12073     FeedMovesToProgram(&first, currentMove);
12074
12075     if (!first.sendTime) {
12076         /* can't tell gnuchess what its clock should read,
12077            so we bow to its notion. */
12078         ResetClocks();
12079         timeRemaining[0][currentMove] = whiteTimeRemaining;
12080         timeRemaining[1][currentMove] = blackTimeRemaining;
12081     }
12082
12083     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12084                 appData.icsEngineAnalyze) && first.analysisSupport) {
12085       SendToProgram("analyze\n", &first);
12086       first.analyzing = TRUE;
12087     }
12088     return 1;
12089 }
12090
12091 /*
12092  * Button procedures
12093  */
12094 void
12095 Reset (int redraw, int init)
12096 {
12097     int i;
12098
12099     if (appData.debugMode) {
12100         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12101                 redraw, init, gameMode);
12102     }
12103     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12104     deadRanks = 0; // assume entire board is used
12105     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12106     CleanupTail(); // [HGM] vari: delete any stored variations
12107     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12108     pausing = pauseExamInvalid = FALSE;
12109     startedFromSetupPosition = blackPlaysFirst = FALSE;
12110     firstMove = TRUE;
12111     whiteFlag = blackFlag = FALSE;
12112     userOfferedDraw = FALSE;
12113     hintRequested = bookRequested = FALSE;
12114     first.maybeThinking = FALSE;
12115     second.maybeThinking = FALSE;
12116     first.bookSuspend = FALSE; // [HGM] book
12117     second.bookSuspend = FALSE;
12118     thinkOutput[0] = NULLCHAR;
12119     lastHint[0] = NULLCHAR;
12120     ClearGameInfo(&gameInfo);
12121     gameInfo.variant = StringToVariant(appData.variant);
12122     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12123         gameInfo.variant = VariantUnknown;
12124         strncpy(engineVariant, appData.variant, MSG_SIZ);
12125     }
12126     ics_user_moved = ics_clock_paused = FALSE;
12127     ics_getting_history = H_FALSE;
12128     ics_gamenum = -1;
12129     white_holding[0] = black_holding[0] = NULLCHAR;
12130     ClearProgramStats();
12131     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12132
12133     ResetFrontEnd();
12134     ClearHighlights();
12135     flipView = appData.flipView;
12136     ClearPremoveHighlights();
12137     gotPremove = FALSE;
12138     alarmSounded = FALSE;
12139     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12140
12141     GameEnds(EndOfFile, NULL, GE_PLAYER);
12142     if(appData.serverMovesName != NULL) {
12143         /* [HGM] prepare to make moves file for broadcasting */
12144         clock_t t = clock();
12145         if(serverMoves != NULL) fclose(serverMoves);
12146         serverMoves = fopen(appData.serverMovesName, "r");
12147         if(serverMoves != NULL) {
12148             fclose(serverMoves);
12149             /* delay 15 sec before overwriting, so all clients can see end */
12150             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12151         }
12152         serverMoves = fopen(appData.serverMovesName, "w");
12153     }
12154
12155     ExitAnalyzeMode();
12156     gameMode = BeginningOfGame;
12157     ModeHighlight();
12158     if(appData.icsActive) gameInfo.variant = VariantNormal;
12159     currentMove = forwardMostMove = backwardMostMove = 0;
12160     MarkTargetSquares(1);
12161     InitPosition(redraw);
12162     for (i = 0; i < MAX_MOVES; i++) {
12163         if (commentList[i] != NULL) {
12164             free(commentList[i]);
12165             commentList[i] = NULL;
12166         }
12167     }
12168     ResetClocks();
12169     timeRemaining[0][0] = whiteTimeRemaining;
12170     timeRemaining[1][0] = blackTimeRemaining;
12171
12172     if (first.pr == NoProc) {
12173         StartChessProgram(&first);
12174     }
12175     if (init) {
12176             InitChessProgram(&first, startedFromSetupPosition);
12177     }
12178     DisplayTitle("");
12179     DisplayMessage("", "");
12180     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12181     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12182     ClearMap();        // [HGM] exclude: invalidate map
12183 }
12184
12185 void
12186 AutoPlayGameLoop ()
12187 {
12188     for (;;) {
12189         if (!AutoPlayOneMove())
12190           return;
12191         if (matchMode || appData.timeDelay == 0)
12192           continue;
12193         if (appData.timeDelay < 0)
12194           return;
12195         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12196         break;
12197     }
12198 }
12199
12200 void
12201 AnalyzeNextGame()
12202 {
12203     ReloadGame(1); // next game
12204 }
12205
12206 int
12207 AutoPlayOneMove ()
12208 {
12209     int fromX, fromY, toX, toY;
12210
12211     if (appData.debugMode) {
12212       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12213     }
12214
12215     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12216       return FALSE;
12217
12218     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12219       pvInfoList[currentMove].depth = programStats.depth;
12220       pvInfoList[currentMove].score = programStats.score;
12221       pvInfoList[currentMove].time  = 0;
12222       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12223       else { // append analysis of final position as comment
12224         char buf[MSG_SIZ];
12225         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12226         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12227       }
12228       programStats.depth = 0;
12229     }
12230
12231     if (currentMove >= forwardMostMove) {
12232       if(gameMode == AnalyzeFile) {
12233           if(appData.loadGameIndex == -1) {
12234             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12235           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12236           } else {
12237           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12238         }
12239       }
12240 //      gameMode = EndOfGame;
12241 //      ModeHighlight();
12242
12243       /* [AS] Clear current move marker at the end of a game */
12244       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12245
12246       return FALSE;
12247     }
12248
12249     toX = moveList[currentMove][2] - AAA;
12250     toY = moveList[currentMove][3] - ONE;
12251
12252     if (moveList[currentMove][1] == '@') {
12253         if (appData.highlightLastMove) {
12254             SetHighlights(-1, -1, toX, toY);
12255         }
12256     } else {
12257         fromX = moveList[currentMove][0] - AAA;
12258         fromY = moveList[currentMove][1] - ONE;
12259
12260         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12261
12262         if(moveList[currentMove][4] == ';') { // multi-leg
12263             killX = moveList[currentMove][5] - AAA;
12264             killY = moveList[currentMove][6] - ONE;
12265         }
12266         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12267         killX = killY = -1;
12268
12269         if (appData.highlightLastMove) {
12270             SetHighlights(fromX, fromY, toX, toY);
12271         }
12272     }
12273     DisplayMove(currentMove);
12274     SendMoveToProgram(currentMove++, &first);
12275     DisplayBothClocks();
12276     DrawPosition(FALSE, boards[currentMove]);
12277     // [HGM] PV info: always display, routine tests if empty
12278     DisplayComment(currentMove - 1, commentList[currentMove]);
12279     return TRUE;
12280 }
12281
12282
12283 int
12284 LoadGameOneMove (ChessMove readAhead)
12285 {
12286     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12287     char promoChar = NULLCHAR;
12288     ChessMove moveType;
12289     char move[MSG_SIZ];
12290     char *p, *q;
12291
12292     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12293         gameMode != AnalyzeMode && gameMode != Training) {
12294         gameFileFP = NULL;
12295         return FALSE;
12296     }
12297
12298     yyboardindex = forwardMostMove;
12299     if (readAhead != EndOfFile) {
12300       moveType = readAhead;
12301     } else {
12302       if (gameFileFP == NULL)
12303           return FALSE;
12304       moveType = (ChessMove) Myylex();
12305     }
12306
12307     done = FALSE;
12308     switch (moveType) {
12309       case Comment:
12310         if (appData.debugMode)
12311           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12312         p = yy_text;
12313
12314         /* append the comment but don't display it */
12315         AppendComment(currentMove, p, FALSE);
12316         return TRUE;
12317
12318       case WhiteCapturesEnPassant:
12319       case BlackCapturesEnPassant:
12320       case WhitePromotion:
12321       case BlackPromotion:
12322       case WhiteNonPromotion:
12323       case BlackNonPromotion:
12324       case NormalMove:
12325       case FirstLeg:
12326       case WhiteKingSideCastle:
12327       case WhiteQueenSideCastle:
12328       case BlackKingSideCastle:
12329       case BlackQueenSideCastle:
12330       case WhiteKingSideCastleWild:
12331       case WhiteQueenSideCastleWild:
12332       case BlackKingSideCastleWild:
12333       case BlackQueenSideCastleWild:
12334       /* PUSH Fabien */
12335       case WhiteHSideCastleFR:
12336       case WhiteASideCastleFR:
12337       case BlackHSideCastleFR:
12338       case BlackASideCastleFR:
12339       /* POP Fabien */
12340         if (appData.debugMode)
12341           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12342         fromX = currentMoveString[0] - AAA;
12343         fromY = currentMoveString[1] - ONE;
12344         toX = currentMoveString[2] - AAA;
12345         toY = currentMoveString[3] - ONE;
12346         promoChar = currentMoveString[4];
12347         if(promoChar == ';') promoChar = currentMoveString[7];
12348         break;
12349
12350       case WhiteDrop:
12351       case BlackDrop:
12352         if (appData.debugMode)
12353           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12354         fromX = moveType == WhiteDrop ?
12355           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12356         (int) CharToPiece(ToLower(currentMoveString[0]));
12357         fromY = DROP_RANK;
12358         toX = currentMoveString[2] - AAA;
12359         toY = currentMoveString[3] - ONE;
12360         break;
12361
12362       case WhiteWins:
12363       case BlackWins:
12364       case GameIsDrawn:
12365       case GameUnfinished:
12366         if (appData.debugMode)
12367           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12368         p = strchr(yy_text, '{');
12369         if (p == NULL) p = strchr(yy_text, '(');
12370         if (p == NULL) {
12371             p = yy_text;
12372             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12373         } else {
12374             q = strchr(p, *p == '{' ? '}' : ')');
12375             if (q != NULL) *q = NULLCHAR;
12376             p++;
12377         }
12378         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12379         GameEnds(moveType, p, GE_FILE);
12380         done = TRUE;
12381         if (cmailMsgLoaded) {
12382             ClearHighlights();
12383             flipView = WhiteOnMove(currentMove);
12384             if (moveType == GameUnfinished) flipView = !flipView;
12385             if (appData.debugMode)
12386               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12387         }
12388         break;
12389
12390       case EndOfFile:
12391         if (appData.debugMode)
12392           fprintf(debugFP, "Parser hit end of file\n");
12393         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12394           case MT_NONE:
12395           case MT_CHECK:
12396             break;
12397           case MT_CHECKMATE:
12398           case MT_STAINMATE:
12399             if (WhiteOnMove(currentMove)) {
12400                 GameEnds(BlackWins, "Black mates", GE_FILE);
12401             } else {
12402                 GameEnds(WhiteWins, "White mates", GE_FILE);
12403             }
12404             break;
12405           case MT_STALEMATE:
12406             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12407             break;
12408         }
12409         done = TRUE;
12410         break;
12411
12412       case MoveNumberOne:
12413         if (lastLoadGameStart == GNUChessGame) {
12414             /* GNUChessGames have numbers, but they aren't move numbers */
12415             if (appData.debugMode)
12416               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12417                       yy_text, (int) moveType);
12418             return LoadGameOneMove(EndOfFile); /* tail recursion */
12419         }
12420         /* else fall thru */
12421
12422       case XBoardGame:
12423       case GNUChessGame:
12424       case PGNTag:
12425         /* Reached start of next game in file */
12426         if (appData.debugMode)
12427           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12428         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12429           case MT_NONE:
12430           case MT_CHECK:
12431             break;
12432           case MT_CHECKMATE:
12433           case MT_STAINMATE:
12434             if (WhiteOnMove(currentMove)) {
12435                 GameEnds(BlackWins, "Black mates", GE_FILE);
12436             } else {
12437                 GameEnds(WhiteWins, "White mates", GE_FILE);
12438             }
12439             break;
12440           case MT_STALEMATE:
12441             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12442             break;
12443         }
12444         done = TRUE;
12445         break;
12446
12447       case PositionDiagram:     /* should not happen; ignore */
12448       case ElapsedTime:         /* ignore */
12449       case NAG:                 /* ignore */
12450         if (appData.debugMode)
12451           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12452                   yy_text, (int) moveType);
12453         return LoadGameOneMove(EndOfFile); /* tail recursion */
12454
12455       case IllegalMove:
12456         if (appData.testLegality) {
12457             if (appData.debugMode)
12458               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12459             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12460                     (forwardMostMove / 2) + 1,
12461                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12462             DisplayError(move, 0);
12463             done = TRUE;
12464         } else {
12465             if (appData.debugMode)
12466               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12467                       yy_text, currentMoveString);
12468             if(currentMoveString[1] == '@') {
12469                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12470                 fromY = DROP_RANK;
12471             } else {
12472                 fromX = currentMoveString[0] - AAA;
12473                 fromY = currentMoveString[1] - ONE;
12474             }
12475             toX = currentMoveString[2] - AAA;
12476             toY = currentMoveString[3] - ONE;
12477             promoChar = currentMoveString[4];
12478         }
12479         break;
12480
12481       case AmbiguousMove:
12482         if (appData.debugMode)
12483           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12484         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12485                 (forwardMostMove / 2) + 1,
12486                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12487         DisplayError(move, 0);
12488         done = TRUE;
12489         break;
12490
12491       default:
12492       case ImpossibleMove:
12493         if (appData.debugMode)
12494           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12495         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12496                 (forwardMostMove / 2) + 1,
12497                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12498         DisplayError(move, 0);
12499         done = TRUE;
12500         break;
12501     }
12502
12503     if (done) {
12504         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12505             DrawPosition(FALSE, boards[currentMove]);
12506             DisplayBothClocks();
12507             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12508               DisplayComment(currentMove - 1, commentList[currentMove]);
12509         }
12510         (void) StopLoadGameTimer();
12511         gameFileFP = NULL;
12512         cmailOldMove = forwardMostMove;
12513         return FALSE;
12514     } else {
12515         /* currentMoveString is set as a side-effect of yylex */
12516
12517         thinkOutput[0] = NULLCHAR;
12518         MakeMove(fromX, fromY, toX, toY, promoChar);
12519         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12520         currentMove = forwardMostMove;
12521         return TRUE;
12522     }
12523 }
12524
12525 /* Load the nth game from the given file */
12526 int
12527 LoadGameFromFile (char *filename, int n, char *title, int useList)
12528 {
12529     FILE *f;
12530     char buf[MSG_SIZ];
12531
12532     if (strcmp(filename, "-") == 0) {
12533         f = stdin;
12534         title = "stdin";
12535     } else {
12536         f = fopen(filename, "rb");
12537         if (f == NULL) {
12538           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12539             DisplayError(buf, errno);
12540             return FALSE;
12541         }
12542     }
12543     if (fseek(f, 0, 0) == -1) {
12544         /* f is not seekable; probably a pipe */
12545         useList = FALSE;
12546     }
12547     if (useList && n == 0) {
12548         int error = GameListBuild(f);
12549         if (error) {
12550             DisplayError(_("Cannot build game list"), error);
12551         } else if (!ListEmpty(&gameList) &&
12552                    ((ListGame *) gameList.tailPred)->number > 1) {
12553             GameListPopUp(f, title);
12554             return TRUE;
12555         }
12556         GameListDestroy();
12557         n = 1;
12558     }
12559     if (n == 0) n = 1;
12560     return LoadGame(f, n, title, FALSE);
12561 }
12562
12563
12564 void
12565 MakeRegisteredMove ()
12566 {
12567     int fromX, fromY, toX, toY;
12568     char promoChar;
12569     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12570         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12571           case CMAIL_MOVE:
12572           case CMAIL_DRAW:
12573             if (appData.debugMode)
12574               fprintf(debugFP, "Restoring %s for game %d\n",
12575                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12576
12577             thinkOutput[0] = NULLCHAR;
12578             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12579             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12580             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12581             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12582             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12583             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12584             MakeMove(fromX, fromY, toX, toY, promoChar);
12585             ShowMove(fromX, fromY, toX, toY);
12586
12587             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12588               case MT_NONE:
12589               case MT_CHECK:
12590                 break;
12591
12592               case MT_CHECKMATE:
12593               case MT_STAINMATE:
12594                 if (WhiteOnMove(currentMove)) {
12595                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12596                 } else {
12597                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12598                 }
12599                 break;
12600
12601               case MT_STALEMATE:
12602                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12603                 break;
12604             }
12605
12606             break;
12607
12608           case CMAIL_RESIGN:
12609             if (WhiteOnMove(currentMove)) {
12610                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12611             } else {
12612                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12613             }
12614             break;
12615
12616           case CMAIL_ACCEPT:
12617             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12618             break;
12619
12620           default:
12621             break;
12622         }
12623     }
12624
12625     return;
12626 }
12627
12628 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12629 int
12630 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12631 {
12632     int retVal;
12633
12634     if (gameNumber > nCmailGames) {
12635         DisplayError(_("No more games in this message"), 0);
12636         return FALSE;
12637     }
12638     if (f == lastLoadGameFP) {
12639         int offset = gameNumber - lastLoadGameNumber;
12640         if (offset == 0) {
12641             cmailMsg[0] = NULLCHAR;
12642             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12643                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12644                 nCmailMovesRegistered--;
12645             }
12646             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12647             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12648                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12649             }
12650         } else {
12651             if (! RegisterMove()) return FALSE;
12652         }
12653     }
12654
12655     retVal = LoadGame(f, gameNumber, title, useList);
12656
12657     /* Make move registered during previous look at this game, if any */
12658     MakeRegisteredMove();
12659
12660     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12661         commentList[currentMove]
12662           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12663         DisplayComment(currentMove - 1, commentList[currentMove]);
12664     }
12665
12666     return retVal;
12667 }
12668
12669 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12670 int
12671 ReloadGame (int offset)
12672 {
12673     int gameNumber = lastLoadGameNumber + offset;
12674     if (lastLoadGameFP == NULL) {
12675         DisplayError(_("No game has been loaded yet"), 0);
12676         return FALSE;
12677     }
12678     if (gameNumber <= 0) {
12679         DisplayError(_("Can't back up any further"), 0);
12680         return FALSE;
12681     }
12682     if (cmailMsgLoaded) {
12683         return CmailLoadGame(lastLoadGameFP, gameNumber,
12684                              lastLoadGameTitle, lastLoadGameUseList);
12685     } else {
12686         return LoadGame(lastLoadGameFP, gameNumber,
12687                         lastLoadGameTitle, lastLoadGameUseList);
12688     }
12689 }
12690
12691 int keys[EmptySquare+1];
12692
12693 int
12694 PositionMatches (Board b1, Board b2)
12695 {
12696     int r, f, sum=0;
12697     switch(appData.searchMode) {
12698         case 1: return CompareWithRights(b1, b2);
12699         case 2:
12700             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12701                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12702             }
12703             return TRUE;
12704         case 3:
12705             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12706               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12707                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12708             }
12709             return sum==0;
12710         case 4:
12711             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12712                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12713             }
12714             return sum==0;
12715     }
12716     return TRUE;
12717 }
12718
12719 #define Q_PROMO  4
12720 #define Q_EP     3
12721 #define Q_BCASTL 2
12722 #define Q_WCASTL 1
12723
12724 int pieceList[256], quickBoard[256];
12725 ChessSquare pieceType[256] = { EmptySquare };
12726 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12727 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12728 int soughtTotal, turn;
12729 Boolean epOK, flipSearch;
12730
12731 typedef struct {
12732     unsigned char piece, to;
12733 } Move;
12734
12735 #define DSIZE (250000)
12736
12737 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12738 Move *moveDatabase = initialSpace;
12739 unsigned int movePtr, dataSize = DSIZE;
12740
12741 int
12742 MakePieceList (Board board, int *counts)
12743 {
12744     int r, f, n=Q_PROMO, total=0;
12745     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12746     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12747         int sq = f + (r<<4);
12748         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12749             quickBoard[sq] = ++n;
12750             pieceList[n] = sq;
12751             pieceType[n] = board[r][f];
12752             counts[board[r][f]]++;
12753             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12754             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12755             total++;
12756         }
12757     }
12758     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12759     return total;
12760 }
12761
12762 void
12763 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12764 {
12765     int sq = fromX + (fromY<<4);
12766     int piece = quickBoard[sq], rook;
12767     quickBoard[sq] = 0;
12768     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12769     if(piece == pieceList[1] && fromY == toY) {
12770       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12771         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12772         moveDatabase[movePtr++].piece = Q_WCASTL;
12773         quickBoard[sq] = piece;
12774         piece = quickBoard[from]; quickBoard[from] = 0;
12775         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12776       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12777         quickBoard[sq] = 0; // remove Rook
12778         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12779         moveDatabase[movePtr++].piece = Q_WCASTL;
12780         quickBoard[sq] = pieceList[1]; // put King
12781         piece = rook;
12782         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12783       }
12784     } else
12785     if(piece == pieceList[2] && fromY == toY) {
12786       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12787         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12788         moveDatabase[movePtr++].piece = Q_BCASTL;
12789         quickBoard[sq] = piece;
12790         piece = quickBoard[from]; quickBoard[from] = 0;
12791         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12792       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12793         quickBoard[sq] = 0; // remove Rook
12794         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12795         moveDatabase[movePtr++].piece = Q_BCASTL;
12796         quickBoard[sq] = pieceList[2]; // put King
12797         piece = rook;
12798         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12799       }
12800     } else
12801     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12802         quickBoard[(fromY<<4)+toX] = 0;
12803         moveDatabase[movePtr].piece = Q_EP;
12804         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12805         moveDatabase[movePtr].to = sq;
12806     } else
12807     if(promoPiece != pieceType[piece]) {
12808         moveDatabase[movePtr++].piece = Q_PROMO;
12809         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12810     }
12811     moveDatabase[movePtr].piece = piece;
12812     quickBoard[sq] = piece;
12813     movePtr++;
12814 }
12815
12816 int
12817 PackGame (Board board)
12818 {
12819     Move *newSpace = NULL;
12820     moveDatabase[movePtr].piece = 0; // terminate previous game
12821     if(movePtr > dataSize) {
12822         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12823         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12824         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12825         if(newSpace) {
12826             int i;
12827             Move *p = moveDatabase, *q = newSpace;
12828             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12829             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12830             moveDatabase = newSpace;
12831         } else { // calloc failed, we must be out of memory. Too bad...
12832             dataSize = 0; // prevent calloc events for all subsequent games
12833             return 0;     // and signal this one isn't cached
12834         }
12835     }
12836     movePtr++;
12837     MakePieceList(board, counts);
12838     return movePtr;
12839 }
12840
12841 int
12842 QuickCompare (Board board, int *minCounts, int *maxCounts)
12843 {   // compare according to search mode
12844     int r, f;
12845     switch(appData.searchMode)
12846     {
12847       case 1: // exact position match
12848         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12849         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12850             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12851         }
12852         break;
12853       case 2: // can have extra material on empty squares
12854         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12855             if(board[r][f] == EmptySquare) continue;
12856             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12857         }
12858         break;
12859       case 3: // material with exact Pawn structure
12860         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12861             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12862             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12863         } // fall through to material comparison
12864       case 4: // exact material
12865         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12866         break;
12867       case 6: // material range with given imbalance
12868         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12869         // fall through to range comparison
12870       case 5: // material range
12871         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12872     }
12873     return TRUE;
12874 }
12875
12876 int
12877 QuickScan (Board board, Move *move)
12878 {   // reconstruct game,and compare all positions in it
12879     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12880     do {
12881         int piece = move->piece;
12882         int to = move->to, from = pieceList[piece];
12883         if(found < 0) { // if already found just scan to game end for final piece count
12884           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12885            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12886            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12887                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12888             ) {
12889             static int lastCounts[EmptySquare+1];
12890             int i;
12891             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12892             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12893           } else stretch = 0;
12894           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12895           if(found >= 0 && !appData.minPieces) return found;
12896         }
12897         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12898           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12899           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12900             piece = (++move)->piece;
12901             from = pieceList[piece];
12902             counts[pieceType[piece]]--;
12903             pieceType[piece] = (ChessSquare) move->to;
12904             counts[move->to]++;
12905           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12906             counts[pieceType[quickBoard[to]]]--;
12907             quickBoard[to] = 0; total--;
12908             move++;
12909             continue;
12910           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12911             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12912             from  = pieceList[piece]; // so this must be King
12913             quickBoard[from] = 0;
12914             pieceList[piece] = to;
12915             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12916             quickBoard[from] = 0; // rook
12917             quickBoard[to] = piece;
12918             to = move->to; piece = move->piece;
12919             goto aftercastle;
12920           }
12921         }
12922         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12923         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12924         quickBoard[from] = 0;
12925       aftercastle:
12926         quickBoard[to] = piece;
12927         pieceList[piece] = to;
12928         cnt++; turn ^= 3;
12929         move++;
12930     } while(1);
12931 }
12932
12933 void
12934 InitSearch ()
12935 {
12936     int r, f;
12937     flipSearch = FALSE;
12938     CopyBoard(soughtBoard, boards[currentMove]);
12939     soughtTotal = MakePieceList(soughtBoard, maxSought);
12940     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12941     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12942     CopyBoard(reverseBoard, boards[currentMove]);
12943     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12944         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12945         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12946         reverseBoard[r][f] = piece;
12947     }
12948     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12949     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12950     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12951                  || (boards[currentMove][CASTLING][2] == NoRights ||
12952                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12953                  && (boards[currentMove][CASTLING][5] == NoRights ||
12954                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12955       ) {
12956         flipSearch = TRUE;
12957         CopyBoard(flipBoard, soughtBoard);
12958         CopyBoard(rotateBoard, reverseBoard);
12959         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12960             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12961             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12962         }
12963     }
12964     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12965     if(appData.searchMode >= 5) {
12966         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12967         MakePieceList(soughtBoard, minSought);
12968         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12969     }
12970     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12971         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12972 }
12973
12974 GameInfo dummyInfo;
12975 static int creatingBook;
12976
12977 int
12978 GameContainsPosition (FILE *f, ListGame *lg)
12979 {
12980     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12981     int fromX, fromY, toX, toY;
12982     char promoChar;
12983     static int initDone=FALSE;
12984
12985     // weed out games based on numerical tag comparison
12986     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12987     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12988     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12989     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12990     if(!initDone) {
12991         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12992         initDone = TRUE;
12993     }
12994     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12995     else CopyBoard(boards[scratch], initialPosition); // default start position
12996     if(lg->moves) {
12997         turn = btm + 1;
12998         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12999         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13000     }
13001     if(btm) plyNr++;
13002     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13003     fseek(f, lg->offset, 0);
13004     yynewfile(f);
13005     while(1) {
13006         yyboardindex = scratch;
13007         quickFlag = plyNr+1;
13008         next = Myylex();
13009         quickFlag = 0;
13010         switch(next) {
13011             case PGNTag:
13012                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13013             default:
13014                 continue;
13015
13016             case XBoardGame:
13017             case GNUChessGame:
13018                 if(plyNr) return -1; // after we have seen moves, this is for new game
13019               continue;
13020
13021             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13022             case ImpossibleMove:
13023             case WhiteWins: // game ends here with these four
13024             case BlackWins:
13025             case GameIsDrawn:
13026             case GameUnfinished:
13027                 return -1;
13028
13029             case IllegalMove:
13030                 if(appData.testLegality) return -1;
13031             case WhiteCapturesEnPassant:
13032             case BlackCapturesEnPassant:
13033             case WhitePromotion:
13034             case BlackPromotion:
13035             case WhiteNonPromotion:
13036             case BlackNonPromotion:
13037             case NormalMove:
13038             case FirstLeg:
13039             case WhiteKingSideCastle:
13040             case WhiteQueenSideCastle:
13041             case BlackKingSideCastle:
13042             case BlackQueenSideCastle:
13043             case WhiteKingSideCastleWild:
13044             case WhiteQueenSideCastleWild:
13045             case BlackKingSideCastleWild:
13046             case BlackQueenSideCastleWild:
13047             case WhiteHSideCastleFR:
13048             case WhiteASideCastleFR:
13049             case BlackHSideCastleFR:
13050             case BlackASideCastleFR:
13051                 fromX = currentMoveString[0] - AAA;
13052                 fromY = currentMoveString[1] - ONE;
13053                 toX = currentMoveString[2] - AAA;
13054                 toY = currentMoveString[3] - ONE;
13055                 promoChar = currentMoveString[4];
13056                 break;
13057             case WhiteDrop:
13058             case BlackDrop:
13059                 fromX = next == WhiteDrop ?
13060                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13061                   (int) CharToPiece(ToLower(currentMoveString[0]));
13062                 fromY = DROP_RANK;
13063                 toX = currentMoveString[2] - AAA;
13064                 toY = currentMoveString[3] - ONE;
13065                 promoChar = 0;
13066                 break;
13067         }
13068         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13069         plyNr++;
13070         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13071         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13072         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13073         if(appData.findMirror) {
13074             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13075             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13076         }
13077     }
13078 }
13079
13080 /* Load the nth game from open file f */
13081 int
13082 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13083 {
13084     ChessMove cm;
13085     char buf[MSG_SIZ];
13086     int gn = gameNumber;
13087     ListGame *lg = NULL;
13088     int numPGNTags = 0, i;
13089     int err, pos = -1;
13090     GameMode oldGameMode;
13091     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13092     char oldName[MSG_SIZ];
13093
13094     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13095
13096     if (appData.debugMode)
13097         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13098
13099     if (gameMode == Training )
13100         SetTrainingModeOff();
13101
13102     oldGameMode = gameMode;
13103     if (gameMode != BeginningOfGame) {
13104       Reset(FALSE, TRUE);
13105     }
13106     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13107
13108     gameFileFP = f;
13109     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13110         fclose(lastLoadGameFP);
13111     }
13112
13113     if (useList) {
13114         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13115
13116         if (lg) {
13117             fseek(f, lg->offset, 0);
13118             GameListHighlight(gameNumber);
13119             pos = lg->position;
13120             gn = 1;
13121         }
13122         else {
13123             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13124               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13125             else
13126             DisplayError(_("Game number out of range"), 0);
13127             return FALSE;
13128         }
13129     } else {
13130         GameListDestroy();
13131         if (fseek(f, 0, 0) == -1) {
13132             if (f == lastLoadGameFP ?
13133                 gameNumber == lastLoadGameNumber + 1 :
13134                 gameNumber == 1) {
13135                 gn = 1;
13136             } else {
13137                 DisplayError(_("Can't seek on game file"), 0);
13138                 return FALSE;
13139             }
13140         }
13141     }
13142     lastLoadGameFP = f;
13143     lastLoadGameNumber = gameNumber;
13144     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13145     lastLoadGameUseList = useList;
13146
13147     yynewfile(f);
13148
13149     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13150       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13151                 lg->gameInfo.black);
13152             DisplayTitle(buf);
13153     } else if (*title != NULLCHAR) {
13154         if (gameNumber > 1) {
13155           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13156             DisplayTitle(buf);
13157         } else {
13158             DisplayTitle(title);
13159         }
13160     }
13161
13162     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13163         gameMode = PlayFromGameFile;
13164         ModeHighlight();
13165     }
13166
13167     currentMove = forwardMostMove = backwardMostMove = 0;
13168     CopyBoard(boards[0], initialPosition);
13169     StopClocks();
13170
13171     /*
13172      * Skip the first gn-1 games in the file.
13173      * Also skip over anything that precedes an identifiable
13174      * start of game marker, to avoid being confused by
13175      * garbage at the start of the file.  Currently
13176      * recognized start of game markers are the move number "1",
13177      * the pattern "gnuchess .* game", the pattern
13178      * "^[#;%] [^ ]* game file", and a PGN tag block.
13179      * A game that starts with one of the latter two patterns
13180      * will also have a move number 1, possibly
13181      * following a position diagram.
13182      * 5-4-02: Let's try being more lenient and allowing a game to
13183      * start with an unnumbered move.  Does that break anything?
13184      */
13185     cm = lastLoadGameStart = EndOfFile;
13186     while (gn > 0) {
13187         yyboardindex = forwardMostMove;
13188         cm = (ChessMove) Myylex();
13189         switch (cm) {
13190           case EndOfFile:
13191             if (cmailMsgLoaded) {
13192                 nCmailGames = CMAIL_MAX_GAMES - gn;
13193             } else {
13194                 Reset(TRUE, TRUE);
13195                 DisplayError(_("Game not found in file"), 0);
13196             }
13197             return FALSE;
13198
13199           case GNUChessGame:
13200           case XBoardGame:
13201             gn--;
13202             lastLoadGameStart = cm;
13203             break;
13204
13205           case MoveNumberOne:
13206             switch (lastLoadGameStart) {
13207               case GNUChessGame:
13208               case XBoardGame:
13209               case PGNTag:
13210                 break;
13211               case MoveNumberOne:
13212               case EndOfFile:
13213                 gn--;           /* count this game */
13214                 lastLoadGameStart = cm;
13215                 break;
13216               default:
13217                 /* impossible */
13218                 break;
13219             }
13220             break;
13221
13222           case PGNTag:
13223             switch (lastLoadGameStart) {
13224               case GNUChessGame:
13225               case PGNTag:
13226               case MoveNumberOne:
13227               case EndOfFile:
13228                 gn--;           /* count this game */
13229                 lastLoadGameStart = cm;
13230                 break;
13231               case XBoardGame:
13232                 lastLoadGameStart = cm; /* game counted already */
13233                 break;
13234               default:
13235                 /* impossible */
13236                 break;
13237             }
13238             if (gn > 0) {
13239                 do {
13240                     yyboardindex = forwardMostMove;
13241                     cm = (ChessMove) Myylex();
13242                 } while (cm == PGNTag || cm == Comment);
13243             }
13244             break;
13245
13246           case WhiteWins:
13247           case BlackWins:
13248           case GameIsDrawn:
13249             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13250                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13251                     != CMAIL_OLD_RESULT) {
13252                     nCmailResults ++ ;
13253                     cmailResult[  CMAIL_MAX_GAMES
13254                                 - gn - 1] = CMAIL_OLD_RESULT;
13255                 }
13256             }
13257             break;
13258
13259           case NormalMove:
13260           case FirstLeg:
13261             /* Only a NormalMove can be at the start of a game
13262              * without a position diagram. */
13263             if (lastLoadGameStart == EndOfFile ) {
13264               gn--;
13265               lastLoadGameStart = MoveNumberOne;
13266             }
13267             break;
13268
13269           default:
13270             break;
13271         }
13272     }
13273
13274     if (appData.debugMode)
13275       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13276
13277     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13278
13279     if (cm == XBoardGame) {
13280         /* Skip any header junk before position diagram and/or move 1 */
13281         for (;;) {
13282             yyboardindex = forwardMostMove;
13283             cm = (ChessMove) Myylex();
13284
13285             if (cm == EndOfFile ||
13286                 cm == GNUChessGame || cm == XBoardGame) {
13287                 /* Empty game; pretend end-of-file and handle later */
13288                 cm = EndOfFile;
13289                 break;
13290             }
13291
13292             if (cm == MoveNumberOne || cm == PositionDiagram ||
13293                 cm == PGNTag || cm == Comment)
13294               break;
13295         }
13296     } else if (cm == GNUChessGame) {
13297         if (gameInfo.event != NULL) {
13298             free(gameInfo.event);
13299         }
13300         gameInfo.event = StrSave(yy_text);
13301     }
13302
13303     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13304     while (cm == PGNTag) {
13305         if (appData.debugMode)
13306           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13307         err = ParsePGNTag(yy_text, &gameInfo);
13308         if (!err) numPGNTags++;
13309
13310         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13311         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13312             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13313             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13314             InitPosition(TRUE);
13315             oldVariant = gameInfo.variant;
13316             if (appData.debugMode)
13317               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13318         }
13319
13320
13321         if (gameInfo.fen != NULL) {
13322           Board initial_position;
13323           startedFromSetupPosition = TRUE;
13324           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13325             Reset(TRUE, TRUE);
13326             DisplayError(_("Bad FEN position in file"), 0);
13327             return FALSE;
13328           }
13329           CopyBoard(boards[0], initial_position);
13330           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13331             CopyBoard(initialPosition, initial_position);
13332           if (blackPlaysFirst) {
13333             currentMove = forwardMostMove = backwardMostMove = 1;
13334             CopyBoard(boards[1], initial_position);
13335             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13336             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13337             timeRemaining[0][1] = whiteTimeRemaining;
13338             timeRemaining[1][1] = blackTimeRemaining;
13339             if (commentList[0] != NULL) {
13340               commentList[1] = commentList[0];
13341               commentList[0] = NULL;
13342             }
13343           } else {
13344             currentMove = forwardMostMove = backwardMostMove = 0;
13345           }
13346           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13347           {   int i;
13348               initialRulePlies = FENrulePlies;
13349               for( i=0; i< nrCastlingRights; i++ )
13350                   initialRights[i] = initial_position[CASTLING][i];
13351           }
13352           yyboardindex = forwardMostMove;
13353           free(gameInfo.fen);
13354           gameInfo.fen = NULL;
13355         }
13356
13357         yyboardindex = forwardMostMove;
13358         cm = (ChessMove) Myylex();
13359
13360         /* Handle comments interspersed among the tags */
13361         while (cm == Comment) {
13362             char *p;
13363             if (appData.debugMode)
13364               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13365             p = yy_text;
13366             AppendComment(currentMove, p, FALSE);
13367             yyboardindex = forwardMostMove;
13368             cm = (ChessMove) Myylex();
13369         }
13370     }
13371
13372     /* don't rely on existence of Event tag since if game was
13373      * pasted from clipboard the Event tag may not exist
13374      */
13375     if (numPGNTags > 0){
13376         char *tags;
13377         if (gameInfo.variant == VariantNormal) {
13378           VariantClass v = StringToVariant(gameInfo.event);
13379           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13380           if(v < VariantShogi) gameInfo.variant = v;
13381         }
13382         if (!matchMode) {
13383           if( appData.autoDisplayTags ) {
13384             tags = PGNTags(&gameInfo);
13385             TagsPopUp(tags, CmailMsg());
13386             free(tags);
13387           }
13388         }
13389     } else {
13390         /* Make something up, but don't display it now */
13391         SetGameInfo();
13392         TagsPopDown();
13393     }
13394
13395     if (cm == PositionDiagram) {
13396         int i, j;
13397         char *p;
13398         Board initial_position;
13399
13400         if (appData.debugMode)
13401           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13402
13403         if (!startedFromSetupPosition) {
13404             p = yy_text;
13405             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13406               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13407                 switch (*p) {
13408                   case '{':
13409                   case '[':
13410                   case '-':
13411                   case ' ':
13412                   case '\t':
13413                   case '\n':
13414                   case '\r':
13415                     break;
13416                   default:
13417                     initial_position[i][j++] = CharToPiece(*p);
13418                     break;
13419                 }
13420             while (*p == ' ' || *p == '\t' ||
13421                    *p == '\n' || *p == '\r') p++;
13422
13423             if (strncmp(p, "black", strlen("black"))==0)
13424               blackPlaysFirst = TRUE;
13425             else
13426               blackPlaysFirst = FALSE;
13427             startedFromSetupPosition = TRUE;
13428
13429             CopyBoard(boards[0], initial_position);
13430             if (blackPlaysFirst) {
13431                 currentMove = forwardMostMove = backwardMostMove = 1;
13432                 CopyBoard(boards[1], initial_position);
13433                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13434                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13435                 timeRemaining[0][1] = whiteTimeRemaining;
13436                 timeRemaining[1][1] = blackTimeRemaining;
13437                 if (commentList[0] != NULL) {
13438                     commentList[1] = commentList[0];
13439                     commentList[0] = NULL;
13440                 }
13441             } else {
13442                 currentMove = forwardMostMove = backwardMostMove = 0;
13443             }
13444         }
13445         yyboardindex = forwardMostMove;
13446         cm = (ChessMove) Myylex();
13447     }
13448
13449   if(!creatingBook) {
13450     if (first.pr == NoProc) {
13451         StartChessProgram(&first);
13452     }
13453     InitChessProgram(&first, FALSE);
13454     if(gameInfo.variant == VariantUnknown && *oldName) {
13455         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13456         gameInfo.variant = v;
13457     }
13458     SendToProgram("force\n", &first);
13459     if (startedFromSetupPosition) {
13460         SendBoard(&first, forwardMostMove);
13461     if (appData.debugMode) {
13462         fprintf(debugFP, "Load Game\n");
13463     }
13464         DisplayBothClocks();
13465     }
13466   }
13467
13468     /* [HGM] server: flag to write setup moves in broadcast file as one */
13469     loadFlag = appData.suppressLoadMoves;
13470
13471     while (cm == Comment) {
13472         char *p;
13473         if (appData.debugMode)
13474           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13475         p = yy_text;
13476         AppendComment(currentMove, p, FALSE);
13477         yyboardindex = forwardMostMove;
13478         cm = (ChessMove) Myylex();
13479     }
13480
13481     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13482         cm == WhiteWins || cm == BlackWins ||
13483         cm == GameIsDrawn || cm == GameUnfinished) {
13484         DisplayMessage("", _("No moves in game"));
13485         if (cmailMsgLoaded) {
13486             if (appData.debugMode)
13487               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13488             ClearHighlights();
13489             flipView = FALSE;
13490         }
13491         DrawPosition(FALSE, boards[currentMove]);
13492         DisplayBothClocks();
13493         gameMode = EditGame;
13494         ModeHighlight();
13495         gameFileFP = NULL;
13496         cmailOldMove = 0;
13497         return TRUE;
13498     }
13499
13500     // [HGM] PV info: routine tests if comment empty
13501     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13502         DisplayComment(currentMove - 1, commentList[currentMove]);
13503     }
13504     if (!matchMode && appData.timeDelay != 0)
13505       DrawPosition(FALSE, boards[currentMove]);
13506
13507     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13508       programStats.ok_to_send = 1;
13509     }
13510
13511     /* if the first token after the PGN tags is a move
13512      * and not move number 1, retrieve it from the parser
13513      */
13514     if (cm != MoveNumberOne)
13515         LoadGameOneMove(cm);
13516
13517     /* load the remaining moves from the file */
13518     while (LoadGameOneMove(EndOfFile)) {
13519       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13520       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13521     }
13522
13523     /* rewind to the start of the game */
13524     currentMove = backwardMostMove;
13525
13526     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13527
13528     if (oldGameMode == AnalyzeFile) {
13529       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13530       AnalyzeFileEvent();
13531     } else
13532     if (oldGameMode == AnalyzeMode) {
13533       AnalyzeFileEvent();
13534     }
13535
13536     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13537         long int w, b; // [HGM] adjourn: restore saved clock times
13538         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13539         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13540             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13541             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13542         }
13543     }
13544
13545     if(creatingBook) return TRUE;
13546     if (!matchMode && pos > 0) {
13547         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13548     } else
13549     if (matchMode || appData.timeDelay == 0) {
13550       ToEndEvent();
13551     } else if (appData.timeDelay > 0) {
13552       AutoPlayGameLoop();
13553     }
13554
13555     if (appData.debugMode)
13556         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13557
13558     loadFlag = 0; /* [HGM] true game starts */
13559     return TRUE;
13560 }
13561
13562 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13563 int
13564 ReloadPosition (int offset)
13565 {
13566     int positionNumber = lastLoadPositionNumber + offset;
13567     if (lastLoadPositionFP == NULL) {
13568         DisplayError(_("No position has been loaded yet"), 0);
13569         return FALSE;
13570     }
13571     if (positionNumber <= 0) {
13572         DisplayError(_("Can't back up any further"), 0);
13573         return FALSE;
13574     }
13575     return LoadPosition(lastLoadPositionFP, positionNumber,
13576                         lastLoadPositionTitle);
13577 }
13578
13579 /* Load the nth position from the given file */
13580 int
13581 LoadPositionFromFile (char *filename, int n, char *title)
13582 {
13583     FILE *f;
13584     char buf[MSG_SIZ];
13585
13586     if (strcmp(filename, "-") == 0) {
13587         return LoadPosition(stdin, n, "stdin");
13588     } else {
13589         f = fopen(filename, "rb");
13590         if (f == NULL) {
13591             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13592             DisplayError(buf, errno);
13593             return FALSE;
13594         } else {
13595             return LoadPosition(f, n, title);
13596         }
13597     }
13598 }
13599
13600 /* Load the nth position from the given open file, and close it */
13601 int
13602 LoadPosition (FILE *f, int positionNumber, char *title)
13603 {
13604     char *p, line[MSG_SIZ];
13605     Board initial_position;
13606     int i, j, fenMode, pn;
13607
13608     if (gameMode == Training )
13609         SetTrainingModeOff();
13610
13611     if (gameMode != BeginningOfGame) {
13612         Reset(FALSE, TRUE);
13613     }
13614     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13615         fclose(lastLoadPositionFP);
13616     }
13617     if (positionNumber == 0) positionNumber = 1;
13618     lastLoadPositionFP = f;
13619     lastLoadPositionNumber = positionNumber;
13620     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13621     if (first.pr == NoProc && !appData.noChessProgram) {
13622       StartChessProgram(&first);
13623       InitChessProgram(&first, FALSE);
13624     }
13625     pn = positionNumber;
13626     if (positionNumber < 0) {
13627         /* Negative position number means to seek to that byte offset */
13628         if (fseek(f, -positionNumber, 0) == -1) {
13629             DisplayError(_("Can't seek on position file"), 0);
13630             return FALSE;
13631         };
13632         pn = 1;
13633     } else {
13634         if (fseek(f, 0, 0) == -1) {
13635             if (f == lastLoadPositionFP ?
13636                 positionNumber == lastLoadPositionNumber + 1 :
13637                 positionNumber == 1) {
13638                 pn = 1;
13639             } else {
13640                 DisplayError(_("Can't seek on position file"), 0);
13641                 return FALSE;
13642             }
13643         }
13644     }
13645     /* See if this file is FEN or old-style xboard */
13646     if (fgets(line, MSG_SIZ, f) == NULL) {
13647         DisplayError(_("Position not found in file"), 0);
13648         return FALSE;
13649     }
13650     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13651     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13652
13653     if (pn >= 2) {
13654         if (fenMode || line[0] == '#') pn--;
13655         while (pn > 0) {
13656             /* skip positions before number pn */
13657             if (fgets(line, MSG_SIZ, f) == NULL) {
13658                 Reset(TRUE, TRUE);
13659                 DisplayError(_("Position not found in file"), 0);
13660                 return FALSE;
13661             }
13662             if (fenMode || line[0] == '#') pn--;
13663         }
13664     }
13665
13666     if (fenMode) {
13667         char *p;
13668         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13669             DisplayError(_("Bad FEN position in file"), 0);
13670             return FALSE;
13671         }
13672         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13673             sscanf(p+4, "%[^;]", bestMove);
13674         } else *bestMove = NULLCHAR;
13675         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13676             sscanf(p+4, "%[^;]", avoidMove);
13677         } else *avoidMove = NULLCHAR;
13678     } else {
13679         (void) fgets(line, MSG_SIZ, f);
13680         (void) fgets(line, MSG_SIZ, f);
13681
13682         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13683             (void) fgets(line, MSG_SIZ, f);
13684             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13685                 if (*p == ' ')
13686                   continue;
13687                 initial_position[i][j++] = CharToPiece(*p);
13688             }
13689         }
13690
13691         blackPlaysFirst = FALSE;
13692         if (!feof(f)) {
13693             (void) fgets(line, MSG_SIZ, f);
13694             if (strncmp(line, "black", strlen("black"))==0)
13695               blackPlaysFirst = TRUE;
13696         }
13697     }
13698     startedFromSetupPosition = TRUE;
13699
13700     CopyBoard(boards[0], initial_position);
13701     if (blackPlaysFirst) {
13702         currentMove = forwardMostMove = backwardMostMove = 1;
13703         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13704         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13705         CopyBoard(boards[1], initial_position);
13706         DisplayMessage("", _("Black to play"));
13707     } else {
13708         currentMove = forwardMostMove = backwardMostMove = 0;
13709         DisplayMessage("", _("White to play"));
13710     }
13711     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13712     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13713         SendToProgram("force\n", &first);
13714         SendBoard(&first, forwardMostMove);
13715     }
13716     if (appData.debugMode) {
13717 int i, j;
13718   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13719   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13720         fprintf(debugFP, "Load Position\n");
13721     }
13722
13723     if (positionNumber > 1) {
13724       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13725         DisplayTitle(line);
13726     } else {
13727         DisplayTitle(title);
13728     }
13729     gameMode = EditGame;
13730     ModeHighlight();
13731     ResetClocks();
13732     timeRemaining[0][1] = whiteTimeRemaining;
13733     timeRemaining[1][1] = blackTimeRemaining;
13734     DrawPosition(FALSE, boards[currentMove]);
13735
13736     return TRUE;
13737 }
13738
13739
13740 void
13741 CopyPlayerNameIntoFileName (char **dest, char *src)
13742 {
13743     while (*src != NULLCHAR && *src != ',') {
13744         if (*src == ' ') {
13745             *(*dest)++ = '_';
13746             src++;
13747         } else {
13748             *(*dest)++ = *src++;
13749         }
13750     }
13751 }
13752
13753 char *
13754 DefaultFileName (char *ext)
13755 {
13756     static char def[MSG_SIZ];
13757     char *p;
13758
13759     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13760         p = def;
13761         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13762         *p++ = '-';
13763         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13764         *p++ = '.';
13765         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13766     } else {
13767         def[0] = NULLCHAR;
13768     }
13769     return def;
13770 }
13771
13772 /* Save the current game to the given file */
13773 int
13774 SaveGameToFile (char *filename, int append)
13775 {
13776     FILE *f;
13777     char buf[MSG_SIZ];
13778     int result, i, t,tot=0;
13779
13780     if (strcmp(filename, "-") == 0) {
13781         return SaveGame(stdout, 0, NULL);
13782     } else {
13783         for(i=0; i<10; i++) { // upto 10 tries
13784              f = fopen(filename, append ? "a" : "w");
13785              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13786              if(f || errno != 13) break;
13787              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13788              tot += t;
13789         }
13790         if (f == NULL) {
13791             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13792             DisplayError(buf, errno);
13793             return FALSE;
13794         } else {
13795             safeStrCpy(buf, lastMsg, MSG_SIZ);
13796             DisplayMessage(_("Waiting for access to save file"), "");
13797             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13798             DisplayMessage(_("Saving game"), "");
13799             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13800             result = SaveGame(f, 0, NULL);
13801             DisplayMessage(buf, "");
13802             return result;
13803         }
13804     }
13805 }
13806
13807 char *
13808 SavePart (char *str)
13809 {
13810     static char buf[MSG_SIZ];
13811     char *p;
13812
13813     p = strchr(str, ' ');
13814     if (p == NULL) return str;
13815     strncpy(buf, str, p - str);
13816     buf[p - str] = NULLCHAR;
13817     return buf;
13818 }
13819
13820 #define PGN_MAX_LINE 75
13821
13822 #define PGN_SIDE_WHITE  0
13823 #define PGN_SIDE_BLACK  1
13824
13825 static int
13826 FindFirstMoveOutOfBook (int side)
13827 {
13828     int result = -1;
13829
13830     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13831         int index = backwardMostMove;
13832         int has_book_hit = 0;
13833
13834         if( (index % 2) != side ) {
13835             index++;
13836         }
13837
13838         while( index < forwardMostMove ) {
13839             /* Check to see if engine is in book */
13840             int depth = pvInfoList[index].depth;
13841             int score = pvInfoList[index].score;
13842             int in_book = 0;
13843
13844             if( depth <= 2 ) {
13845                 in_book = 1;
13846             }
13847             else if( score == 0 && depth == 63 ) {
13848                 in_book = 1; /* Zappa */
13849             }
13850             else if( score == 2 && depth == 99 ) {
13851                 in_book = 1; /* Abrok */
13852             }
13853
13854             has_book_hit += in_book;
13855
13856             if( ! in_book ) {
13857                 result = index;
13858
13859                 break;
13860             }
13861
13862             index += 2;
13863         }
13864     }
13865
13866     return result;
13867 }
13868
13869 void
13870 GetOutOfBookInfo (char * buf)
13871 {
13872     int oob[2];
13873     int i;
13874     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13875
13876     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13877     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13878
13879     *buf = '\0';
13880
13881     if( oob[0] >= 0 || oob[1] >= 0 ) {
13882         for( i=0; i<2; i++ ) {
13883             int idx = oob[i];
13884
13885             if( idx >= 0 ) {
13886                 if( i > 0 && oob[0] >= 0 ) {
13887                     strcat( buf, "   " );
13888                 }
13889
13890                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13891                 sprintf( buf+strlen(buf), "%s%.2f",
13892                     pvInfoList[idx].score >= 0 ? "+" : "",
13893                     pvInfoList[idx].score / 100.0 );
13894             }
13895         }
13896     }
13897 }
13898
13899 /* Save game in PGN style */
13900 static void
13901 SaveGamePGN2 (FILE *f)
13902 {
13903     int i, offset, linelen, newblock;
13904 //    char *movetext;
13905     char numtext[32];
13906     int movelen, numlen, blank;
13907     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13908
13909     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13910
13911     PrintPGNTags(f, &gameInfo);
13912
13913     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13914
13915     if (backwardMostMove > 0 || startedFromSetupPosition) {
13916         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13917         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13918         fprintf(f, "\n{--------------\n");
13919         PrintPosition(f, backwardMostMove);
13920         fprintf(f, "--------------}\n");
13921         free(fen);
13922     }
13923     else {
13924         /* [AS] Out of book annotation */
13925         if( appData.saveOutOfBookInfo ) {
13926             char buf[64];
13927
13928             GetOutOfBookInfo( buf );
13929
13930             if( buf[0] != '\0' ) {
13931                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13932             }
13933         }
13934
13935         fprintf(f, "\n");
13936     }
13937
13938     i = backwardMostMove;
13939     linelen = 0;
13940     newblock = TRUE;
13941
13942     while (i < forwardMostMove) {
13943         /* Print comments preceding this move */
13944         if (commentList[i] != NULL) {
13945             if (linelen > 0) fprintf(f, "\n");
13946             fprintf(f, "%s", commentList[i]);
13947             linelen = 0;
13948             newblock = TRUE;
13949         }
13950
13951         /* Format move number */
13952         if ((i % 2) == 0)
13953           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13954         else
13955           if (newblock)
13956             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13957           else
13958             numtext[0] = NULLCHAR;
13959
13960         numlen = strlen(numtext);
13961         newblock = FALSE;
13962
13963         /* Print move number */
13964         blank = linelen > 0 && numlen > 0;
13965         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13966             fprintf(f, "\n");
13967             linelen = 0;
13968             blank = 0;
13969         }
13970         if (blank) {
13971             fprintf(f, " ");
13972             linelen++;
13973         }
13974         fprintf(f, "%s", numtext);
13975         linelen += numlen;
13976
13977         /* Get move */
13978         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13979         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13980
13981         /* Print move */
13982         blank = linelen > 0 && movelen > 0;
13983         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13984             fprintf(f, "\n");
13985             linelen = 0;
13986             blank = 0;
13987         }
13988         if (blank) {
13989             fprintf(f, " ");
13990             linelen++;
13991         }
13992         fprintf(f, "%s", move_buffer);
13993         linelen += movelen;
13994
13995         /* [AS] Add PV info if present */
13996         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13997             /* [HGM] add time */
13998             char buf[MSG_SIZ]; int seconds;
13999
14000             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14001
14002             if( seconds <= 0)
14003               buf[0] = 0;
14004             else
14005               if( seconds < 30 )
14006                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14007               else
14008                 {
14009                   seconds = (seconds + 4)/10; // round to full seconds
14010                   if( seconds < 60 )
14011                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14012                   else
14013                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14014                 }
14015
14016             if(appData.cumulativeTimePGN) {
14017                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14018             }
14019
14020             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14021                       pvInfoList[i].score >= 0 ? "+" : "",
14022                       pvInfoList[i].score / 100.0,
14023                       pvInfoList[i].depth,
14024                       buf );
14025
14026             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14027
14028             /* Print score/depth */
14029             blank = linelen > 0 && movelen > 0;
14030             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14031                 fprintf(f, "\n");
14032                 linelen = 0;
14033                 blank = 0;
14034             }
14035             if (blank) {
14036                 fprintf(f, " ");
14037                 linelen++;
14038             }
14039             fprintf(f, "%s", move_buffer);
14040             linelen += movelen;
14041         }
14042
14043         i++;
14044     }
14045
14046     /* Start a new line */
14047     if (linelen > 0) fprintf(f, "\n");
14048
14049     /* Print comments after last move */
14050     if (commentList[i] != NULL) {
14051         fprintf(f, "%s\n", commentList[i]);
14052     }
14053
14054     /* Print result */
14055     if (gameInfo.resultDetails != NULL &&
14056         gameInfo.resultDetails[0] != NULLCHAR) {
14057         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14058         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14059            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14060             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14061         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14062     } else {
14063         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14064     }
14065 }
14066
14067 /* Save game in PGN style and close the file */
14068 int
14069 SaveGamePGN (FILE *f)
14070 {
14071     SaveGamePGN2(f);
14072     fclose(f);
14073     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14074     return TRUE;
14075 }
14076
14077 /* Save game in old style and close the file */
14078 int
14079 SaveGameOldStyle (FILE *f)
14080 {
14081     int i, offset;
14082     time_t tm;
14083
14084     tm = time((time_t *) NULL);
14085
14086     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14087     PrintOpponents(f);
14088
14089     if (backwardMostMove > 0 || startedFromSetupPosition) {
14090         fprintf(f, "\n[--------------\n");
14091         PrintPosition(f, backwardMostMove);
14092         fprintf(f, "--------------]\n");
14093     } else {
14094         fprintf(f, "\n");
14095     }
14096
14097     i = backwardMostMove;
14098     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14099
14100     while (i < forwardMostMove) {
14101         if (commentList[i] != NULL) {
14102             fprintf(f, "[%s]\n", commentList[i]);
14103         }
14104
14105         if ((i % 2) == 1) {
14106             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14107             i++;
14108         } else {
14109             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14110             i++;
14111             if (commentList[i] != NULL) {
14112                 fprintf(f, "\n");
14113                 continue;
14114             }
14115             if (i >= forwardMostMove) {
14116                 fprintf(f, "\n");
14117                 break;
14118             }
14119             fprintf(f, "%s\n", parseList[i]);
14120             i++;
14121         }
14122     }
14123
14124     if (commentList[i] != NULL) {
14125         fprintf(f, "[%s]\n", commentList[i]);
14126     }
14127
14128     /* This isn't really the old style, but it's close enough */
14129     if (gameInfo.resultDetails != NULL &&
14130         gameInfo.resultDetails[0] != NULLCHAR) {
14131         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14132                 gameInfo.resultDetails);
14133     } else {
14134         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14135     }
14136
14137     fclose(f);
14138     return TRUE;
14139 }
14140
14141 /* Save the current game to open file f and close the file */
14142 int
14143 SaveGame (FILE *f, int dummy, char *dummy2)
14144 {
14145     if (gameMode == EditPosition) EditPositionDone(TRUE);
14146     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14147     if (appData.oldSaveStyle)
14148       return SaveGameOldStyle(f);
14149     else
14150       return SaveGamePGN(f);
14151 }
14152
14153 /* Save the current position to the given file */
14154 int
14155 SavePositionToFile (char *filename)
14156 {
14157     FILE *f;
14158     char buf[MSG_SIZ];
14159
14160     if (strcmp(filename, "-") == 0) {
14161         return SavePosition(stdout, 0, NULL);
14162     } else {
14163         f = fopen(filename, "a");
14164         if (f == NULL) {
14165             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14166             DisplayError(buf, errno);
14167             return FALSE;
14168         } else {
14169             safeStrCpy(buf, lastMsg, MSG_SIZ);
14170             DisplayMessage(_("Waiting for access to save file"), "");
14171             flock(fileno(f), LOCK_EX); // [HGM] lock
14172             DisplayMessage(_("Saving position"), "");
14173             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14174             SavePosition(f, 0, NULL);
14175             DisplayMessage(buf, "");
14176             return TRUE;
14177         }
14178     }
14179 }
14180
14181 /* Save the current position to the given open file and close the file */
14182 int
14183 SavePosition (FILE *f, int dummy, char *dummy2)
14184 {
14185     time_t tm;
14186     char *fen;
14187
14188     if (gameMode == EditPosition) EditPositionDone(TRUE);
14189     if (appData.oldSaveStyle) {
14190         tm = time((time_t *) NULL);
14191
14192         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14193         PrintOpponents(f);
14194         fprintf(f, "[--------------\n");
14195         PrintPosition(f, currentMove);
14196         fprintf(f, "--------------]\n");
14197     } else {
14198         fen = PositionToFEN(currentMove, NULL, 1);
14199         fprintf(f, "%s\n", fen);
14200         free(fen);
14201     }
14202     fclose(f);
14203     return TRUE;
14204 }
14205
14206 void
14207 ReloadCmailMsgEvent (int unregister)
14208 {
14209 #if !WIN32
14210     static char *inFilename = NULL;
14211     static char *outFilename;
14212     int i;
14213     struct stat inbuf, outbuf;
14214     int status;
14215
14216     /* Any registered moves are unregistered if unregister is set, */
14217     /* i.e. invoked by the signal handler */
14218     if (unregister) {
14219         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14220             cmailMoveRegistered[i] = FALSE;
14221             if (cmailCommentList[i] != NULL) {
14222                 free(cmailCommentList[i]);
14223                 cmailCommentList[i] = NULL;
14224             }
14225         }
14226         nCmailMovesRegistered = 0;
14227     }
14228
14229     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14230         cmailResult[i] = CMAIL_NOT_RESULT;
14231     }
14232     nCmailResults = 0;
14233
14234     if (inFilename == NULL) {
14235         /* Because the filenames are static they only get malloced once  */
14236         /* and they never get freed                                      */
14237         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14238         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14239
14240         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14241         sprintf(outFilename, "%s.out", appData.cmailGameName);
14242     }
14243
14244     status = stat(outFilename, &outbuf);
14245     if (status < 0) {
14246         cmailMailedMove = FALSE;
14247     } else {
14248         status = stat(inFilename, &inbuf);
14249         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14250     }
14251
14252     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14253        counts the games, notes how each one terminated, etc.
14254
14255        It would be nice to remove this kludge and instead gather all
14256        the information while building the game list.  (And to keep it
14257        in the game list nodes instead of having a bunch of fixed-size
14258        parallel arrays.)  Note this will require getting each game's
14259        termination from the PGN tags, as the game list builder does
14260        not process the game moves.  --mann
14261        */
14262     cmailMsgLoaded = TRUE;
14263     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14264
14265     /* Load first game in the file or popup game menu */
14266     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14267
14268 #endif /* !WIN32 */
14269     return;
14270 }
14271
14272 int
14273 RegisterMove ()
14274 {
14275     FILE *f;
14276     char string[MSG_SIZ];
14277
14278     if (   cmailMailedMove
14279         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14280         return TRUE;            /* Allow free viewing  */
14281     }
14282
14283     /* Unregister move to ensure that we don't leave RegisterMove        */
14284     /* with the move registered when the conditions for registering no   */
14285     /* longer hold                                                       */
14286     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14287         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14288         nCmailMovesRegistered --;
14289
14290         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14291           {
14292               free(cmailCommentList[lastLoadGameNumber - 1]);
14293               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14294           }
14295     }
14296
14297     if (cmailOldMove == -1) {
14298         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14299         return FALSE;
14300     }
14301
14302     if (currentMove > cmailOldMove + 1) {
14303         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14304         return FALSE;
14305     }
14306
14307     if (currentMove < cmailOldMove) {
14308         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14309         return FALSE;
14310     }
14311
14312     if (forwardMostMove > currentMove) {
14313         /* Silently truncate extra moves */
14314         TruncateGame();
14315     }
14316
14317     if (   (currentMove == cmailOldMove + 1)
14318         || (   (currentMove == cmailOldMove)
14319             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14320                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14321         if (gameInfo.result != GameUnfinished) {
14322             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14323         }
14324
14325         if (commentList[currentMove] != NULL) {
14326             cmailCommentList[lastLoadGameNumber - 1]
14327               = StrSave(commentList[currentMove]);
14328         }
14329         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14330
14331         if (appData.debugMode)
14332           fprintf(debugFP, "Saving %s for game %d\n",
14333                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14334
14335         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14336
14337         f = fopen(string, "w");
14338         if (appData.oldSaveStyle) {
14339             SaveGameOldStyle(f); /* also closes the file */
14340
14341             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14342             f = fopen(string, "w");
14343             SavePosition(f, 0, NULL); /* also closes the file */
14344         } else {
14345             fprintf(f, "{--------------\n");
14346             PrintPosition(f, currentMove);
14347             fprintf(f, "--------------}\n\n");
14348
14349             SaveGame(f, 0, NULL); /* also closes the file*/
14350         }
14351
14352         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14353         nCmailMovesRegistered ++;
14354     } else if (nCmailGames == 1) {
14355         DisplayError(_("You have not made a move yet"), 0);
14356         return FALSE;
14357     }
14358
14359     return TRUE;
14360 }
14361
14362 void
14363 MailMoveEvent ()
14364 {
14365 #if !WIN32
14366     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14367     FILE *commandOutput;
14368     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14369     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14370     int nBuffers;
14371     int i;
14372     int archived;
14373     char *arcDir;
14374
14375     if (! cmailMsgLoaded) {
14376         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14377         return;
14378     }
14379
14380     if (nCmailGames == nCmailResults) {
14381         DisplayError(_("No unfinished games"), 0);
14382         return;
14383     }
14384
14385 #if CMAIL_PROHIBIT_REMAIL
14386     if (cmailMailedMove) {
14387       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);
14388         DisplayError(msg, 0);
14389         return;
14390     }
14391 #endif
14392
14393     if (! (cmailMailedMove || RegisterMove())) return;
14394
14395     if (   cmailMailedMove
14396         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14397       snprintf(string, MSG_SIZ, partCommandString,
14398                appData.debugMode ? " -v" : "", appData.cmailGameName);
14399         commandOutput = popen(string, "r");
14400
14401         if (commandOutput == NULL) {
14402             DisplayError(_("Failed to invoke cmail"), 0);
14403         } else {
14404             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14405                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14406             }
14407             if (nBuffers > 1) {
14408                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14409                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14410                 nBytes = MSG_SIZ - 1;
14411             } else {
14412                 (void) memcpy(msg, buffer, nBytes);
14413             }
14414             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14415
14416             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14417                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14418
14419                 archived = TRUE;
14420                 for (i = 0; i < nCmailGames; i ++) {
14421                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14422                         archived = FALSE;
14423                     }
14424                 }
14425                 if (   archived
14426                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14427                         != NULL)) {
14428                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14429                            arcDir,
14430                            appData.cmailGameName,
14431                            gameInfo.date);
14432                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14433                     cmailMsgLoaded = FALSE;
14434                 }
14435             }
14436
14437             DisplayInformation(msg);
14438             pclose(commandOutput);
14439         }
14440     } else {
14441         if ((*cmailMsg) != '\0') {
14442             DisplayInformation(cmailMsg);
14443         }
14444     }
14445
14446     return;
14447 #endif /* !WIN32 */
14448 }
14449
14450 char *
14451 CmailMsg ()
14452 {
14453 #if WIN32
14454     return NULL;
14455 #else
14456     int  prependComma = 0;
14457     char number[5];
14458     char string[MSG_SIZ];       /* Space for game-list */
14459     int  i;
14460
14461     if (!cmailMsgLoaded) return "";
14462
14463     if (cmailMailedMove) {
14464       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14465     } else {
14466         /* Create a list of games left */
14467       snprintf(string, MSG_SIZ, "[");
14468         for (i = 0; i < nCmailGames; i ++) {
14469             if (! (   cmailMoveRegistered[i]
14470                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14471                 if (prependComma) {
14472                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14473                 } else {
14474                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14475                     prependComma = 1;
14476                 }
14477
14478                 strcat(string, number);
14479             }
14480         }
14481         strcat(string, "]");
14482
14483         if (nCmailMovesRegistered + nCmailResults == 0) {
14484             switch (nCmailGames) {
14485               case 1:
14486                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14487                 break;
14488
14489               case 2:
14490                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14491                 break;
14492
14493               default:
14494                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14495                          nCmailGames);
14496                 break;
14497             }
14498         } else {
14499             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14500               case 1:
14501                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14502                          string);
14503                 break;
14504
14505               case 0:
14506                 if (nCmailResults == nCmailGames) {
14507                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14508                 } else {
14509                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14510                 }
14511                 break;
14512
14513               default:
14514                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14515                          string);
14516             }
14517         }
14518     }
14519     return cmailMsg;
14520 #endif /* WIN32 */
14521 }
14522
14523 void
14524 ResetGameEvent ()
14525 {
14526     if (gameMode == Training)
14527       SetTrainingModeOff();
14528
14529     Reset(TRUE, TRUE);
14530     cmailMsgLoaded = FALSE;
14531     if (appData.icsActive) {
14532       SendToICS(ics_prefix);
14533       SendToICS("refresh\n");
14534     }
14535 }
14536
14537 void
14538 ExitEvent (int status)
14539 {
14540     exiting++;
14541     if (exiting > 2) {
14542       /* Give up on clean exit */
14543       exit(status);
14544     }
14545     if (exiting > 1) {
14546       /* Keep trying for clean exit */
14547       return;
14548     }
14549
14550     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14551     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14552
14553     if (telnetISR != NULL) {
14554       RemoveInputSource(telnetISR);
14555     }
14556     if (icsPR != NoProc) {
14557       DestroyChildProcess(icsPR, TRUE);
14558     }
14559
14560     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14561     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14562
14563     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14564     /* make sure this other one finishes before killing it!                  */
14565     if(endingGame) { int count = 0;
14566         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14567         while(endingGame && count++ < 10) DoSleep(1);
14568         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14569     }
14570
14571     /* Kill off chess programs */
14572     if (first.pr != NoProc) {
14573         ExitAnalyzeMode();
14574
14575         DoSleep( appData.delayBeforeQuit );
14576         SendToProgram("quit\n", &first);
14577         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14578     }
14579     if (second.pr != NoProc) {
14580         DoSleep( appData.delayBeforeQuit );
14581         SendToProgram("quit\n", &second);
14582         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14583     }
14584     if (first.isr != NULL) {
14585         RemoveInputSource(first.isr);
14586     }
14587     if (second.isr != NULL) {
14588         RemoveInputSource(second.isr);
14589     }
14590
14591     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14592     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14593
14594     ShutDownFrontEnd();
14595     exit(status);
14596 }
14597
14598 void
14599 PauseEngine (ChessProgramState *cps)
14600 {
14601     SendToProgram("pause\n", cps);
14602     cps->pause = 2;
14603 }
14604
14605 void
14606 UnPauseEngine (ChessProgramState *cps)
14607 {
14608     SendToProgram("resume\n", cps);
14609     cps->pause = 1;
14610 }
14611
14612 void
14613 PauseEvent ()
14614 {
14615     if (appData.debugMode)
14616         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14617     if (pausing) {
14618         pausing = FALSE;
14619         ModeHighlight();
14620         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14621             StartClocks();
14622             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14623                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14624                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14625             }
14626             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14627             HandleMachineMove(stashedInputMove, stalledEngine);
14628             stalledEngine = NULL;
14629             return;
14630         }
14631         if (gameMode == MachinePlaysWhite ||
14632             gameMode == TwoMachinesPlay   ||
14633             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14634             if(first.pause)  UnPauseEngine(&first);
14635             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14636             if(second.pause) UnPauseEngine(&second);
14637             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14638             StartClocks();
14639         } else {
14640             DisplayBothClocks();
14641         }
14642         if (gameMode == PlayFromGameFile) {
14643             if (appData.timeDelay >= 0)
14644                 AutoPlayGameLoop();
14645         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14646             Reset(FALSE, TRUE);
14647             SendToICS(ics_prefix);
14648             SendToICS("refresh\n");
14649         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14650             ForwardInner(forwardMostMove);
14651         }
14652         pauseExamInvalid = FALSE;
14653     } else {
14654         switch (gameMode) {
14655           default:
14656             return;
14657           case IcsExamining:
14658             pauseExamForwardMostMove = forwardMostMove;
14659             pauseExamInvalid = FALSE;
14660             /* fall through */
14661           case IcsObserving:
14662           case IcsPlayingWhite:
14663           case IcsPlayingBlack:
14664             pausing = TRUE;
14665             ModeHighlight();
14666             return;
14667           case PlayFromGameFile:
14668             (void) StopLoadGameTimer();
14669             pausing = TRUE;
14670             ModeHighlight();
14671             break;
14672           case BeginningOfGame:
14673             if (appData.icsActive) return;
14674             /* else fall through */
14675           case MachinePlaysWhite:
14676           case MachinePlaysBlack:
14677           case TwoMachinesPlay:
14678             if (forwardMostMove == 0)
14679               return;           /* don't pause if no one has moved */
14680             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14681                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14682                 if(onMove->pause) {           // thinking engine can be paused
14683                     PauseEngine(onMove);      // do it
14684                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14685                         PauseEngine(onMove->other);
14686                     else
14687                         SendToProgram("easy\n", onMove->other);
14688                     StopClocks();
14689                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14690             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14691                 if(first.pause) {
14692                     PauseEngine(&first);
14693                     StopClocks();
14694                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14695             } else { // human on move, pause pondering by either method
14696                 if(first.pause)
14697                     PauseEngine(&first);
14698                 else if(appData.ponderNextMove)
14699                     SendToProgram("easy\n", &first);
14700                 StopClocks();
14701             }
14702             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14703           case AnalyzeMode:
14704             pausing = TRUE;
14705             ModeHighlight();
14706             break;
14707         }
14708     }
14709 }
14710
14711 void
14712 EditCommentEvent ()
14713 {
14714     char title[MSG_SIZ];
14715
14716     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14717       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14718     } else {
14719       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14720                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14721                parseList[currentMove - 1]);
14722     }
14723
14724     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14725 }
14726
14727
14728 void
14729 EditTagsEvent ()
14730 {
14731     char *tags = PGNTags(&gameInfo);
14732     bookUp = FALSE;
14733     EditTagsPopUp(tags, NULL);
14734     free(tags);
14735 }
14736
14737 void
14738 ToggleSecond ()
14739 {
14740   if(second.analyzing) {
14741     SendToProgram("exit\n", &second);
14742     second.analyzing = FALSE;
14743   } else {
14744     if (second.pr == NoProc) StartChessProgram(&second);
14745     InitChessProgram(&second, FALSE);
14746     FeedMovesToProgram(&second, currentMove);
14747
14748     SendToProgram("analyze\n", &second);
14749     second.analyzing = TRUE;
14750   }
14751 }
14752
14753 /* Toggle ShowThinking */
14754 void
14755 ToggleShowThinking()
14756 {
14757   appData.showThinking = !appData.showThinking;
14758   ShowThinkingEvent();
14759 }
14760
14761 int
14762 AnalyzeModeEvent ()
14763 {
14764     char buf[MSG_SIZ];
14765
14766     if (!first.analysisSupport) {
14767       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14768       DisplayError(buf, 0);
14769       return 0;
14770     }
14771     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14772     if (appData.icsActive) {
14773         if (gameMode != IcsObserving) {
14774           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14775             DisplayError(buf, 0);
14776             /* secure check */
14777             if (appData.icsEngineAnalyze) {
14778                 if (appData.debugMode)
14779                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14780                 ExitAnalyzeMode();
14781                 ModeHighlight();
14782             }
14783             return 0;
14784         }
14785         /* if enable, user wants to disable icsEngineAnalyze */
14786         if (appData.icsEngineAnalyze) {
14787                 ExitAnalyzeMode();
14788                 ModeHighlight();
14789                 return 0;
14790         }
14791         appData.icsEngineAnalyze = TRUE;
14792         if (appData.debugMode)
14793             fprintf(debugFP, "ICS engine analyze starting... \n");
14794     }
14795
14796     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14797     if (appData.noChessProgram || gameMode == AnalyzeMode)
14798       return 0;
14799
14800     if (gameMode != AnalyzeFile) {
14801         if (!appData.icsEngineAnalyze) {
14802                EditGameEvent();
14803                if (gameMode != EditGame) return 0;
14804         }
14805         if (!appData.showThinking) ToggleShowThinking();
14806         ResurrectChessProgram();
14807         SendToProgram("analyze\n", &first);
14808         first.analyzing = TRUE;
14809         /*first.maybeThinking = TRUE;*/
14810         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14811         EngineOutputPopUp();
14812     }
14813     if (!appData.icsEngineAnalyze) {
14814         gameMode = AnalyzeMode;
14815         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14816     }
14817     pausing = FALSE;
14818     ModeHighlight();
14819     SetGameInfo();
14820
14821     StartAnalysisClock();
14822     GetTimeMark(&lastNodeCountTime);
14823     lastNodeCount = 0;
14824     return 1;
14825 }
14826
14827 void
14828 AnalyzeFileEvent ()
14829 {
14830     if (appData.noChessProgram || gameMode == AnalyzeFile)
14831       return;
14832
14833     if (!first.analysisSupport) {
14834       char buf[MSG_SIZ];
14835       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14836       DisplayError(buf, 0);
14837       return;
14838     }
14839
14840     if (gameMode != AnalyzeMode) {
14841         keepInfo = 1; // mere annotating should not alter PGN tags
14842         EditGameEvent();
14843         keepInfo = 0;
14844         if (gameMode != EditGame) return;
14845         if (!appData.showThinking) ToggleShowThinking();
14846         ResurrectChessProgram();
14847         SendToProgram("analyze\n", &first);
14848         first.analyzing = TRUE;
14849         /*first.maybeThinking = TRUE;*/
14850         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14851         EngineOutputPopUp();
14852     }
14853     gameMode = AnalyzeFile;
14854     pausing = FALSE;
14855     ModeHighlight();
14856
14857     StartAnalysisClock();
14858     GetTimeMark(&lastNodeCountTime);
14859     lastNodeCount = 0;
14860     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14861     AnalysisPeriodicEvent(1);
14862 }
14863
14864 void
14865 MachineWhiteEvent ()
14866 {
14867     char buf[MSG_SIZ];
14868     char *bookHit = NULL;
14869
14870     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14871       return;
14872
14873
14874     if (gameMode == PlayFromGameFile ||
14875         gameMode == TwoMachinesPlay  ||
14876         gameMode == Training         ||
14877         gameMode == AnalyzeMode      ||
14878         gameMode == EndOfGame)
14879         EditGameEvent();
14880
14881     if (gameMode == EditPosition)
14882         EditPositionDone(TRUE);
14883
14884     if (!WhiteOnMove(currentMove)) {
14885         DisplayError(_("It is not White's turn"), 0);
14886         return;
14887     }
14888
14889     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14890       ExitAnalyzeMode();
14891
14892     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14893         gameMode == AnalyzeFile)
14894         TruncateGame();
14895
14896     ResurrectChessProgram();    /* in case it isn't running */
14897     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14898         gameMode = MachinePlaysWhite;
14899         ResetClocks();
14900     } else
14901     gameMode = MachinePlaysWhite;
14902     pausing = FALSE;
14903     ModeHighlight();
14904     SetGameInfo();
14905     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14906     DisplayTitle(buf);
14907     if (first.sendName) {
14908       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14909       SendToProgram(buf, &first);
14910     }
14911     if (first.sendTime) {
14912       if (first.useColors) {
14913         SendToProgram("black\n", &first); /*gnu kludge*/
14914       }
14915       SendTimeRemaining(&first, TRUE);
14916     }
14917     if (first.useColors) {
14918       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14919     }
14920     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14921     SetMachineThinkingEnables();
14922     first.maybeThinking = TRUE;
14923     StartClocks();
14924     firstMove = FALSE;
14925
14926     if (appData.autoFlipView && !flipView) {
14927       flipView = !flipView;
14928       DrawPosition(FALSE, NULL);
14929       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14930     }
14931
14932     if(bookHit) { // [HGM] book: simulate book reply
14933         static char bookMove[MSG_SIZ]; // a bit generous?
14934
14935         programStats.nodes = programStats.depth = programStats.time =
14936         programStats.score = programStats.got_only_move = 0;
14937         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14938
14939         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14940         strcat(bookMove, bookHit);
14941         savedMessage = bookMove; // args for deferred call
14942         savedState = &first;
14943         ScheduleDelayedEvent(DeferredBookMove, 1);
14944     }
14945 }
14946
14947 void
14948 MachineBlackEvent ()
14949 {
14950   char buf[MSG_SIZ];
14951   char *bookHit = NULL;
14952
14953     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14954         return;
14955
14956
14957     if (gameMode == PlayFromGameFile ||
14958         gameMode == TwoMachinesPlay  ||
14959         gameMode == Training         ||
14960         gameMode == AnalyzeMode      ||
14961         gameMode == EndOfGame)
14962         EditGameEvent();
14963
14964     if (gameMode == EditPosition)
14965         EditPositionDone(TRUE);
14966
14967     if (WhiteOnMove(currentMove)) {
14968         DisplayError(_("It is not Black's turn"), 0);
14969         return;
14970     }
14971
14972     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14973       ExitAnalyzeMode();
14974
14975     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14976         gameMode == AnalyzeFile)
14977         TruncateGame();
14978
14979     ResurrectChessProgram();    /* in case it isn't running */
14980     gameMode = MachinePlaysBlack;
14981     pausing = FALSE;
14982     ModeHighlight();
14983     SetGameInfo();
14984     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14985     DisplayTitle(buf);
14986     if (first.sendName) {
14987       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14988       SendToProgram(buf, &first);
14989     }
14990     if (first.sendTime) {
14991       if (first.useColors) {
14992         SendToProgram("white\n", &first); /*gnu kludge*/
14993       }
14994       SendTimeRemaining(&first, FALSE);
14995     }
14996     if (first.useColors) {
14997       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14998     }
14999     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15000     SetMachineThinkingEnables();
15001     first.maybeThinking = TRUE;
15002     StartClocks();
15003
15004     if (appData.autoFlipView && flipView) {
15005       flipView = !flipView;
15006       DrawPosition(FALSE, NULL);
15007       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15008     }
15009     if(bookHit) { // [HGM] book: simulate book reply
15010         static char bookMove[MSG_SIZ]; // a bit generous?
15011
15012         programStats.nodes = programStats.depth = programStats.time =
15013         programStats.score = programStats.got_only_move = 0;
15014         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15015
15016         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15017         strcat(bookMove, bookHit);
15018         savedMessage = bookMove; // args for deferred call
15019         savedState = &first;
15020         ScheduleDelayedEvent(DeferredBookMove, 1);
15021     }
15022 }
15023
15024
15025 void
15026 DisplayTwoMachinesTitle ()
15027 {
15028     char buf[MSG_SIZ];
15029     if (appData.matchGames > 0) {
15030         if(appData.tourneyFile[0]) {
15031           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15032                    gameInfo.white, _("vs."), gameInfo.black,
15033                    nextGame+1, appData.matchGames+1,
15034                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15035         } else
15036         if (first.twoMachinesColor[0] == 'w') {
15037           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15038                    gameInfo.white, _("vs."),  gameInfo.black,
15039                    first.matchWins, second.matchWins,
15040                    matchGame - 1 - (first.matchWins + second.matchWins));
15041         } else {
15042           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15043                    gameInfo.white, _("vs."), gameInfo.black,
15044                    second.matchWins, first.matchWins,
15045                    matchGame - 1 - (first.matchWins + second.matchWins));
15046         }
15047     } else {
15048       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15049     }
15050     DisplayTitle(buf);
15051 }
15052
15053 void
15054 SettingsMenuIfReady ()
15055 {
15056   if (second.lastPing != second.lastPong) {
15057     DisplayMessage("", _("Waiting for second chess program"));
15058     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15059     return;
15060   }
15061   ThawUI();
15062   DisplayMessage("", "");
15063   SettingsPopUp(&second);
15064 }
15065
15066 int
15067 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15068 {
15069     char buf[MSG_SIZ];
15070     if (cps->pr == NoProc) {
15071         StartChessProgram(cps);
15072         if (cps->protocolVersion == 1) {
15073           retry();
15074           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15075         } else {
15076           /* kludge: allow timeout for initial "feature" command */
15077           if(retry != TwoMachinesEventIfReady) FreezeUI();
15078           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15079           DisplayMessage("", buf);
15080           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15081         }
15082         return 1;
15083     }
15084     return 0;
15085 }
15086
15087 void
15088 TwoMachinesEvent P((void))
15089 {
15090     int i, move = forwardMostMove;
15091     char buf[MSG_SIZ];
15092     ChessProgramState *onmove;
15093     char *bookHit = NULL;
15094     static int stalling = 0;
15095     TimeMark now;
15096     long wait;
15097
15098     if (appData.noChessProgram) return;
15099
15100     switch (gameMode) {
15101       case TwoMachinesPlay:
15102         return;
15103       case MachinePlaysWhite:
15104       case MachinePlaysBlack:
15105         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15106             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15107             return;
15108         }
15109         /* fall through */
15110       case BeginningOfGame:
15111       case PlayFromGameFile:
15112       case EndOfGame:
15113         EditGameEvent();
15114         if (gameMode != EditGame) return;
15115         break;
15116       case EditPosition:
15117         EditPositionDone(TRUE);
15118         break;
15119       case AnalyzeMode:
15120       case AnalyzeFile:
15121         ExitAnalyzeMode();
15122         break;
15123       case EditGame:
15124       default:
15125         break;
15126     }
15127
15128 //    forwardMostMove = currentMove;
15129     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15130     startingEngine = TRUE;
15131
15132     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15133
15134     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15135     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15136       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15137       return;
15138     }
15139   if(!appData.epd) {
15140     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15141
15142     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15143                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15144         startingEngine = matchMode = FALSE;
15145         DisplayError("second engine does not play this", 0);
15146         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15147         EditGameEvent(); // switch back to EditGame mode
15148         return;
15149     }
15150
15151     if(!stalling) {
15152       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15153       SendToProgram("force\n", &second);
15154       stalling = 1;
15155       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15156       return;
15157     }
15158   }
15159     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15160     if(appData.matchPause>10000 || appData.matchPause<10)
15161                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15162     wait = SubtractTimeMarks(&now, &pauseStart);
15163     if(wait < appData.matchPause) {
15164         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15165         return;
15166     }
15167     // we are now committed to starting the game
15168     stalling = 0;
15169     DisplayMessage("", "");
15170   if(!appData.epd) {
15171     if (startedFromSetupPosition) {
15172         SendBoard(&second, backwardMostMove);
15173     if (appData.debugMode) {
15174         fprintf(debugFP, "Two Machines\n");
15175     }
15176     }
15177     for (i = backwardMostMove; i < forwardMostMove; i++) {
15178         SendMoveToProgram(i, &second);
15179     }
15180   }
15181
15182     gameMode = TwoMachinesPlay;
15183     pausing = startingEngine = FALSE;
15184     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15185     SetGameInfo();
15186     DisplayTwoMachinesTitle();
15187     firstMove = TRUE;
15188     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15189         onmove = &first;
15190     } else {
15191         onmove = &second;
15192     }
15193     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15194     SendToProgram(first.computerString, &first);
15195     if (first.sendName) {
15196       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15197       SendToProgram(buf, &first);
15198     }
15199   if(!appData.epd) {
15200     SendToProgram(second.computerString, &second);
15201     if (second.sendName) {
15202       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15203       SendToProgram(buf, &second);
15204     }
15205   }
15206
15207     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15208         ResetClocks();
15209         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15210         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15211     }
15212     if (onmove->sendTime) {
15213       if (onmove->useColors) {
15214         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15215       }
15216       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15217     }
15218     if (onmove->useColors) {
15219       SendToProgram(onmove->twoMachinesColor, onmove);
15220     }
15221     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15222 //    SendToProgram("go\n", onmove);
15223     onmove->maybeThinking = TRUE;
15224     SetMachineThinkingEnables();
15225
15226     StartClocks();
15227
15228     if(bookHit) { // [HGM] book: simulate book reply
15229         static char bookMove[MSG_SIZ]; // a bit generous?
15230
15231         programStats.nodes = programStats.depth = programStats.time =
15232         programStats.score = programStats.got_only_move = 0;
15233         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15234
15235         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15236         strcat(bookMove, bookHit);
15237         savedMessage = bookMove; // args for deferred call
15238         savedState = onmove;
15239         ScheduleDelayedEvent(DeferredBookMove, 1);
15240     }
15241 }
15242
15243 void
15244 TrainingEvent ()
15245 {
15246     if (gameMode == Training) {
15247       SetTrainingModeOff();
15248       gameMode = PlayFromGameFile;
15249       DisplayMessage("", _("Training mode off"));
15250     } else {
15251       gameMode = Training;
15252       animateTraining = appData.animate;
15253
15254       /* make sure we are not already at the end of the game */
15255       if (currentMove < forwardMostMove) {
15256         SetTrainingModeOn();
15257         DisplayMessage("", _("Training mode on"));
15258       } else {
15259         gameMode = PlayFromGameFile;
15260         DisplayError(_("Already at end of game"), 0);
15261       }
15262     }
15263     ModeHighlight();
15264 }
15265
15266 void
15267 IcsClientEvent ()
15268 {
15269     if (!appData.icsActive) return;
15270     switch (gameMode) {
15271       case IcsPlayingWhite:
15272       case IcsPlayingBlack:
15273       case IcsObserving:
15274       case IcsIdle:
15275       case BeginningOfGame:
15276       case IcsExamining:
15277         return;
15278
15279       case EditGame:
15280         break;
15281
15282       case EditPosition:
15283         EditPositionDone(TRUE);
15284         break;
15285
15286       case AnalyzeMode:
15287       case AnalyzeFile:
15288         ExitAnalyzeMode();
15289         break;
15290
15291       default:
15292         EditGameEvent();
15293         break;
15294     }
15295
15296     gameMode = IcsIdle;
15297     ModeHighlight();
15298     return;
15299 }
15300
15301 void
15302 EditGameEvent ()
15303 {
15304     int i;
15305
15306     switch (gameMode) {
15307       case Training:
15308         SetTrainingModeOff();
15309         break;
15310       case MachinePlaysWhite:
15311       case MachinePlaysBlack:
15312       case BeginningOfGame:
15313         SendToProgram("force\n", &first);
15314         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15315             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15316                 char buf[MSG_SIZ];
15317                 abortEngineThink = TRUE;
15318                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15319                 SendToProgram(buf, &first);
15320                 DisplayMessage("Aborting engine think", "");
15321                 FreezeUI();
15322             }
15323         }
15324         SetUserThinkingEnables();
15325         break;
15326       case PlayFromGameFile:
15327         (void) StopLoadGameTimer();
15328         if (gameFileFP != NULL) {
15329             gameFileFP = NULL;
15330         }
15331         break;
15332       case EditPosition:
15333         EditPositionDone(TRUE);
15334         break;
15335       case AnalyzeMode:
15336       case AnalyzeFile:
15337         ExitAnalyzeMode();
15338         SendToProgram("force\n", &first);
15339         break;
15340       case TwoMachinesPlay:
15341         GameEnds(EndOfFile, NULL, GE_PLAYER);
15342         ResurrectChessProgram();
15343         SetUserThinkingEnables();
15344         break;
15345       case EndOfGame:
15346         ResurrectChessProgram();
15347         break;
15348       case IcsPlayingBlack:
15349       case IcsPlayingWhite:
15350         DisplayError(_("Warning: You are still playing a game"), 0);
15351         break;
15352       case IcsObserving:
15353         DisplayError(_("Warning: You are still observing a game"), 0);
15354         break;
15355       case IcsExamining:
15356         DisplayError(_("Warning: You are still examining a game"), 0);
15357         break;
15358       case IcsIdle:
15359         break;
15360       case EditGame:
15361       default:
15362         return;
15363     }
15364
15365     pausing = FALSE;
15366     StopClocks();
15367     first.offeredDraw = second.offeredDraw = 0;
15368
15369     if (gameMode == PlayFromGameFile) {
15370         whiteTimeRemaining = timeRemaining[0][currentMove];
15371         blackTimeRemaining = timeRemaining[1][currentMove];
15372         DisplayTitle("");
15373     }
15374
15375     if (gameMode == MachinePlaysWhite ||
15376         gameMode == MachinePlaysBlack ||
15377         gameMode == TwoMachinesPlay ||
15378         gameMode == EndOfGame) {
15379         i = forwardMostMove;
15380         while (i > currentMove) {
15381             SendToProgram("undo\n", &first);
15382             i--;
15383         }
15384         if(!adjustedClock) {
15385         whiteTimeRemaining = timeRemaining[0][currentMove];
15386         blackTimeRemaining = timeRemaining[1][currentMove];
15387         DisplayBothClocks();
15388         }
15389         if (whiteFlag || blackFlag) {
15390             whiteFlag = blackFlag = 0;
15391         }
15392         DisplayTitle("");
15393     }
15394
15395     gameMode = EditGame;
15396     ModeHighlight();
15397     SetGameInfo();
15398 }
15399
15400 void
15401 EditPositionEvent ()
15402 {
15403     int i;
15404     if (gameMode == EditPosition) {
15405         EditGameEvent();
15406         return;
15407     }
15408
15409     EditGameEvent();
15410     if (gameMode != EditGame) return;
15411
15412     gameMode = EditPosition;
15413     ModeHighlight();
15414     SetGameInfo();
15415     CopyBoard(rightsBoard, nullBoard);
15416     if (currentMove > 0)
15417       CopyBoard(boards[0], boards[currentMove]);
15418     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15419       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15420
15421     blackPlaysFirst = !WhiteOnMove(currentMove);
15422     ResetClocks();
15423     currentMove = forwardMostMove = backwardMostMove = 0;
15424     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15425     DisplayMove(-1);
15426     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15427 }
15428
15429 void
15430 ExitAnalyzeMode ()
15431 {
15432     /* [DM] icsEngineAnalyze - possible call from other functions */
15433     if (appData.icsEngineAnalyze) {
15434         appData.icsEngineAnalyze = FALSE;
15435
15436         DisplayMessage("",_("Close ICS engine analyze..."));
15437     }
15438     if (first.analysisSupport && first.analyzing) {
15439       SendToBoth("exit\n");
15440       first.analyzing = second.analyzing = FALSE;
15441     }
15442     thinkOutput[0] = NULLCHAR;
15443 }
15444
15445 void
15446 EditPositionDone (Boolean fakeRights)
15447 {
15448     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15449
15450     startedFromSetupPosition = TRUE;
15451     InitChessProgram(&first, FALSE);
15452     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15453       int r, f;
15454       boards[0][EP_STATUS] = EP_NONE;
15455       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15456       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15457         if(rightsBoard[r][f]) {
15458           ChessSquare p = boards[0][r][f];
15459           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15460           else if(p == king) boards[0][CASTLING][2] = f;
15461           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15462           else rightsBoard[r][f] = 2; // mark for second pass
15463         }
15464       }
15465       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15466         if(rightsBoard[r][f] == 2) {
15467           ChessSquare p = boards[0][r][f];
15468           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15469           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15470         }
15471       }
15472     }
15473     SendToProgram("force\n", &first);
15474     if (blackPlaysFirst) {
15475         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15476         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15477         currentMove = forwardMostMove = backwardMostMove = 1;
15478         CopyBoard(boards[1], boards[0]);
15479     } else {
15480         currentMove = forwardMostMove = backwardMostMove = 0;
15481     }
15482     SendBoard(&first, forwardMostMove);
15483     if (appData.debugMode) {
15484         fprintf(debugFP, "EditPosDone\n");
15485     }
15486     DisplayTitle("");
15487     DisplayMessage("", "");
15488     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15489     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15490     gameMode = EditGame;
15491     ModeHighlight();
15492     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15493     ClearHighlights(); /* [AS] */
15494 }
15495
15496 /* Pause for `ms' milliseconds */
15497 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15498 void
15499 TimeDelay (long ms)
15500 {
15501     TimeMark m1, m2;
15502
15503     GetTimeMark(&m1);
15504     do {
15505         GetTimeMark(&m2);
15506     } while (SubtractTimeMarks(&m2, &m1) < ms);
15507 }
15508
15509 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15510 void
15511 SendMultiLineToICS (char *buf)
15512 {
15513     char temp[MSG_SIZ+1], *p;
15514     int len;
15515
15516     len = strlen(buf);
15517     if (len > MSG_SIZ)
15518       len = MSG_SIZ;
15519
15520     strncpy(temp, buf, len);
15521     temp[len] = 0;
15522
15523     p = temp;
15524     while (*p) {
15525         if (*p == '\n' || *p == '\r')
15526           *p = ' ';
15527         ++p;
15528     }
15529
15530     strcat(temp, "\n");
15531     SendToICS(temp);
15532     SendToPlayer(temp, strlen(temp));
15533 }
15534
15535 void
15536 SetWhiteToPlayEvent ()
15537 {
15538     if (gameMode == EditPosition) {
15539         blackPlaysFirst = FALSE;
15540         DisplayBothClocks();    /* works because currentMove is 0 */
15541     } else if (gameMode == IcsExamining) {
15542         SendToICS(ics_prefix);
15543         SendToICS("tomove white\n");
15544     }
15545 }
15546
15547 void
15548 SetBlackToPlayEvent ()
15549 {
15550     if (gameMode == EditPosition) {
15551         blackPlaysFirst = TRUE;
15552         currentMove = 1;        /* kludge */
15553         DisplayBothClocks();
15554         currentMove = 0;
15555     } else if (gameMode == IcsExamining) {
15556         SendToICS(ics_prefix);
15557         SendToICS("tomove black\n");
15558     }
15559 }
15560
15561 void
15562 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15563 {
15564     char buf[MSG_SIZ];
15565     ChessSquare piece = boards[0][y][x];
15566     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15567     static int lastVariant;
15568     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15569
15570     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15571
15572     switch (selection) {
15573       case ClearBoard:
15574         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15575         MarkTargetSquares(1);
15576         CopyBoard(currentBoard, boards[0]);
15577         CopyBoard(menuBoard, initialPosition);
15578         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15579             SendToICS(ics_prefix);
15580             SendToICS("bsetup clear\n");
15581         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15582             SendToICS(ics_prefix);
15583             SendToICS("clearboard\n");
15584         } else {
15585             int nonEmpty = 0;
15586             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15587                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15588                 for (y = 0; y < BOARD_HEIGHT; y++) {
15589                     if (gameMode == IcsExamining) {
15590                         if (boards[currentMove][y][x] != EmptySquare) {
15591                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15592                                     AAA + x, ONE + y);
15593                             SendToICS(buf);
15594                         }
15595                     } else if(boards[0][y][x] != DarkSquare) {
15596                         if(boards[0][y][x] != p) nonEmpty++;
15597                         boards[0][y][x] = p;
15598                     }
15599                 }
15600             }
15601             CopyBoard(rightsBoard, nullBoard);
15602             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15603                 int r, i;
15604                 for(r = 0; r < BOARD_HEIGHT; r++) {
15605                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15606                     ChessSquare p = menuBoard[r][x];
15607                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15608                   }
15609                 }
15610                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15611                 DisplayMessage("Clicking clock again restores position", "");
15612                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15613                 if(!nonEmpty) { // asked to clear an empty board
15614                     CopyBoard(boards[0], menuBoard);
15615                 } else
15616                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15617                     CopyBoard(boards[0], initialPosition);
15618                 } else
15619                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15620                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15621                     CopyBoard(boards[0], erasedBoard);
15622                 } else
15623                     CopyBoard(erasedBoard, currentBoard);
15624
15625                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15626                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15627             }
15628         }
15629         if (gameMode == EditPosition) {
15630             DrawPosition(FALSE, boards[0]);
15631         }
15632         break;
15633
15634       case WhitePlay:
15635         SetWhiteToPlayEvent();
15636         break;
15637
15638       case BlackPlay:
15639         SetBlackToPlayEvent();
15640         break;
15641
15642       case EmptySquare:
15643         if (gameMode == IcsExamining) {
15644             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15645             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15646             SendToICS(buf);
15647         } else {
15648             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15649                 if(x == BOARD_LEFT-2) {
15650                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15651                     boards[0][y][1] = 0;
15652                 } else
15653                 if(x == BOARD_RGHT+1) {
15654                     if(y >= gameInfo.holdingsSize) break;
15655                     boards[0][y][BOARD_WIDTH-2] = 0;
15656                 } else break;
15657             }
15658             boards[0][y][x] = EmptySquare;
15659             DrawPosition(FALSE, boards[0]);
15660         }
15661         break;
15662
15663       case PromotePiece:
15664         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15665            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15666             selection = (ChessSquare) (PROMOTED(piece));
15667         } else if(piece == EmptySquare) selection = WhiteSilver;
15668         else selection = (ChessSquare)((int)piece - 1);
15669         goto defaultlabel;
15670
15671       case DemotePiece:
15672         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15673            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15674             selection = (ChessSquare) (DEMOTED(piece));
15675         } else if(piece == EmptySquare) selection = BlackSilver;
15676         else selection = (ChessSquare)((int)piece + 1);
15677         goto defaultlabel;
15678
15679       case WhiteQueen:
15680       case BlackQueen:
15681         if(gameInfo.variant == VariantShatranj ||
15682            gameInfo.variant == VariantXiangqi  ||
15683            gameInfo.variant == VariantCourier  ||
15684            gameInfo.variant == VariantASEAN    ||
15685            gameInfo.variant == VariantMakruk     )
15686             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15687         goto defaultlabel;
15688
15689       case WhiteRook:
15690         baseRank = 0;
15691       case BlackRook:
15692         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15693         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15694         goto defaultlabel;
15695
15696       case WhiteKing:
15697         baseRank = 0;
15698       case BlackKing:
15699         if(gameInfo.variant == VariantXiangqi)
15700             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15701         if(gameInfo.variant == VariantKnightmate)
15702             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15703         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15704       default:
15705         defaultlabel:
15706         if (gameMode == IcsExamining) {
15707             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15708             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15709                      PieceToChar(selection), AAA + x, ONE + y);
15710             SendToICS(buf);
15711         } else {
15712             rightsBoard[y][x] = hasRights;
15713             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15714                 int n;
15715                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15716                     n = PieceToNumber(selection - BlackPawn);
15717                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15718                     boards[0][handSize-1-n][0] = selection;
15719                     boards[0][handSize-1-n][1]++;
15720                 } else
15721                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15722                     n = PieceToNumber(selection);
15723                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15724                     boards[0][n][BOARD_WIDTH-1] = selection;
15725                     boards[0][n][BOARD_WIDTH-2]++;
15726                 }
15727             } else
15728             boards[0][y][x] = selection;
15729             DrawPosition(TRUE, boards[0]);
15730             ClearHighlights();
15731             fromX = fromY = -1;
15732         }
15733         break;
15734     }
15735 }
15736
15737
15738 void
15739 DropMenuEvent (ChessSquare selection, int x, int y)
15740 {
15741     ChessMove moveType;
15742
15743     switch (gameMode) {
15744       case IcsPlayingWhite:
15745       case MachinePlaysBlack:
15746         if (!WhiteOnMove(currentMove)) {
15747             DisplayMoveError(_("It is Black's turn"));
15748             return;
15749         }
15750         moveType = WhiteDrop;
15751         break;
15752       case IcsPlayingBlack:
15753       case MachinePlaysWhite:
15754         if (WhiteOnMove(currentMove)) {
15755             DisplayMoveError(_("It is White's turn"));
15756             return;
15757         }
15758         moveType = BlackDrop;
15759         break;
15760       case EditGame:
15761         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15762         break;
15763       default:
15764         return;
15765     }
15766
15767     if (moveType == BlackDrop && selection < BlackPawn) {
15768       selection = (ChessSquare) ((int) selection
15769                                  + (int) BlackPawn - (int) WhitePawn);
15770     }
15771     if (boards[currentMove][y][x] != EmptySquare) {
15772         DisplayMoveError(_("That square is occupied"));
15773         return;
15774     }
15775
15776     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15777 }
15778
15779 void
15780 AcceptEvent ()
15781 {
15782     /* Accept a pending offer of any kind from opponent */
15783
15784     if (appData.icsActive) {
15785         SendToICS(ics_prefix);
15786         SendToICS("accept\n");
15787     } else if (cmailMsgLoaded) {
15788         if (currentMove == cmailOldMove &&
15789             commentList[cmailOldMove] != NULL &&
15790             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15791                    "Black offers a draw" : "White offers a draw")) {
15792             TruncateGame();
15793             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15794             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15795         } else {
15796             DisplayError(_("There is no pending offer on this move"), 0);
15797             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15798         }
15799     } else {
15800         /* Not used for offers from chess program */
15801     }
15802 }
15803
15804 void
15805 DeclineEvent ()
15806 {
15807     /* Decline a pending offer of any kind from opponent */
15808
15809     if (appData.icsActive) {
15810         SendToICS(ics_prefix);
15811         SendToICS("decline\n");
15812     } else if (cmailMsgLoaded) {
15813         if (currentMove == cmailOldMove &&
15814             commentList[cmailOldMove] != NULL &&
15815             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15816                    "Black offers a draw" : "White offers a draw")) {
15817 #ifdef NOTDEF
15818             AppendComment(cmailOldMove, "Draw declined", TRUE);
15819             DisplayComment(cmailOldMove - 1, "Draw declined");
15820 #endif /*NOTDEF*/
15821         } else {
15822             DisplayError(_("There is no pending offer on this move"), 0);
15823         }
15824     } else {
15825         /* Not used for offers from chess program */
15826     }
15827 }
15828
15829 void
15830 RematchEvent ()
15831 {
15832     /* Issue ICS rematch command */
15833     if (appData.icsActive) {
15834         SendToICS(ics_prefix);
15835         SendToICS("rematch\n");
15836     }
15837 }
15838
15839 void
15840 CallFlagEvent ()
15841 {
15842     /* Call your opponent's flag (claim a win on time) */
15843     if (appData.icsActive) {
15844         SendToICS(ics_prefix);
15845         SendToICS("flag\n");
15846     } else {
15847         switch (gameMode) {
15848           default:
15849             return;
15850           case MachinePlaysWhite:
15851             if (whiteFlag) {
15852                 if (blackFlag)
15853                   GameEnds(GameIsDrawn, "Both players ran out of time",
15854                            GE_PLAYER);
15855                 else
15856                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15857             } else {
15858                 DisplayError(_("Your opponent is not out of time"), 0);
15859             }
15860             break;
15861           case MachinePlaysBlack:
15862             if (blackFlag) {
15863                 if (whiteFlag)
15864                   GameEnds(GameIsDrawn, "Both players ran out of time",
15865                            GE_PLAYER);
15866                 else
15867                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15868             } else {
15869                 DisplayError(_("Your opponent is not out of time"), 0);
15870             }
15871             break;
15872         }
15873     }
15874 }
15875
15876 void
15877 ClockClick (int which)
15878 {       // [HGM] code moved to back-end from winboard.c
15879         if(which) { // black clock
15880           if (gameMode == EditPosition || gameMode == IcsExamining) {
15881             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15882             SetBlackToPlayEvent();
15883           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15884                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15885           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15886           } else if (shiftKey) {
15887             AdjustClock(which, -1);
15888           } else if (gameMode == IcsPlayingWhite ||
15889                      gameMode == MachinePlaysBlack) {
15890             CallFlagEvent();
15891           }
15892         } else { // white clock
15893           if (gameMode == EditPosition || gameMode == IcsExamining) {
15894             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15895             SetWhiteToPlayEvent();
15896           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15897                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15898           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15899           } else if (shiftKey) {
15900             AdjustClock(which, -1);
15901           } else if (gameMode == IcsPlayingBlack ||
15902                    gameMode == MachinePlaysWhite) {
15903             CallFlagEvent();
15904           }
15905         }
15906 }
15907
15908 void
15909 DrawEvent ()
15910 {
15911     /* Offer draw or accept pending draw offer from opponent */
15912
15913     if (appData.icsActive) {
15914         /* Note: tournament rules require draw offers to be
15915            made after you make your move but before you punch
15916            your clock.  Currently ICS doesn't let you do that;
15917            instead, you immediately punch your clock after making
15918            a move, but you can offer a draw at any time. */
15919
15920         SendToICS(ics_prefix);
15921         SendToICS("draw\n");
15922         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15923     } else if (cmailMsgLoaded) {
15924         if (currentMove == cmailOldMove &&
15925             commentList[cmailOldMove] != NULL &&
15926             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15927                    "Black offers a draw" : "White offers a draw")) {
15928             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15929             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15930         } else if (currentMove == cmailOldMove + 1) {
15931             char *offer = WhiteOnMove(cmailOldMove) ?
15932               "White offers a draw" : "Black offers a draw";
15933             AppendComment(currentMove, offer, TRUE);
15934             DisplayComment(currentMove - 1, offer);
15935             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15936         } else {
15937             DisplayError(_("You must make your move before offering a draw"), 0);
15938             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15939         }
15940     } else if (first.offeredDraw) {
15941         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15942     } else {
15943         if (first.sendDrawOffers) {
15944             SendToProgram("draw\n", &first);
15945             userOfferedDraw = TRUE;
15946         }
15947     }
15948 }
15949
15950 void
15951 AdjournEvent ()
15952 {
15953     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15954
15955     if (appData.icsActive) {
15956         SendToICS(ics_prefix);
15957         SendToICS("adjourn\n");
15958     } else {
15959         /* Currently GNU Chess doesn't offer or accept Adjourns */
15960     }
15961 }
15962
15963
15964 void
15965 AbortEvent ()
15966 {
15967     /* Offer Abort or accept pending Abort offer from opponent */
15968
15969     if (appData.icsActive) {
15970         SendToICS(ics_prefix);
15971         SendToICS("abort\n");
15972     } else {
15973         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15974     }
15975 }
15976
15977 void
15978 ResignEvent ()
15979 {
15980     /* Resign.  You can do this even if it's not your turn. */
15981
15982     if (appData.icsActive) {
15983         SendToICS(ics_prefix);
15984         SendToICS("resign\n");
15985     } else {
15986         switch (gameMode) {
15987           case MachinePlaysWhite:
15988             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15989             break;
15990           case MachinePlaysBlack:
15991             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15992             break;
15993           case EditGame:
15994             if (cmailMsgLoaded) {
15995                 TruncateGame();
15996                 if (WhiteOnMove(cmailOldMove)) {
15997                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15998                 } else {
15999                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16000                 }
16001                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16002             }
16003             break;
16004           default:
16005             break;
16006         }
16007     }
16008 }
16009
16010
16011 void
16012 StopObservingEvent ()
16013 {
16014     /* Stop observing current games */
16015     SendToICS(ics_prefix);
16016     SendToICS("unobserve\n");
16017 }
16018
16019 void
16020 StopExaminingEvent ()
16021 {
16022     /* Stop observing current game */
16023     SendToICS(ics_prefix);
16024     SendToICS("unexamine\n");
16025 }
16026
16027 void
16028 ForwardInner (int target)
16029 {
16030     int limit; int oldSeekGraphUp = seekGraphUp;
16031
16032     if (appData.debugMode)
16033         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16034                 target, currentMove, forwardMostMove);
16035
16036     if (gameMode == EditPosition)
16037       return;
16038
16039     seekGraphUp = FALSE;
16040     MarkTargetSquares(1);
16041     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16042
16043     if (gameMode == PlayFromGameFile && !pausing)
16044       PauseEvent();
16045
16046     if (gameMode == IcsExamining && pausing)
16047       limit = pauseExamForwardMostMove;
16048     else
16049       limit = forwardMostMove;
16050
16051     if (target > limit) target = limit;
16052
16053     if (target > 0 && moveList[target - 1][0]) {
16054         int fromX, fromY, toX, toY;
16055         toX = moveList[target - 1][2] - AAA;
16056         toY = moveList[target - 1][3] - ONE;
16057         if (moveList[target - 1][1] == '@') {
16058             if (appData.highlightLastMove) {
16059                 SetHighlights(-1, -1, toX, toY);
16060             }
16061         } else {
16062             fromX = moveList[target - 1][0] - AAA;
16063             fromY = moveList[target - 1][1] - ONE;
16064             if (target == currentMove + 1) {
16065                 if(moveList[target - 1][4] == ';') { // multi-leg
16066                     killX = moveList[target - 1][5] - AAA;
16067                     killY = moveList[target - 1][6] - ONE;
16068                 }
16069                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16070                 killX = killY = -1;
16071             }
16072             if (appData.highlightLastMove) {
16073                 SetHighlights(fromX, fromY, toX, toY);
16074             }
16075         }
16076     }
16077     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16078         gameMode == Training || gameMode == PlayFromGameFile ||
16079         gameMode == AnalyzeFile) {
16080         while (currentMove < target) {
16081             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16082             SendMoveToProgram(currentMove++, &first);
16083         }
16084     } else {
16085         currentMove = target;
16086     }
16087
16088     if (gameMode == EditGame || gameMode == EndOfGame) {
16089         whiteTimeRemaining = timeRemaining[0][currentMove];
16090         blackTimeRemaining = timeRemaining[1][currentMove];
16091     }
16092     DisplayBothClocks();
16093     DisplayMove(currentMove - 1);
16094     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16095     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16096     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16097         DisplayComment(currentMove - 1, commentList[currentMove]);
16098     }
16099     ClearMap(); // [HGM] exclude: invalidate map
16100 }
16101
16102
16103 void
16104 ForwardEvent ()
16105 {
16106     if (gameMode == IcsExamining && !pausing) {
16107         SendToICS(ics_prefix);
16108         SendToICS("forward\n");
16109     } else {
16110         ForwardInner(currentMove + 1);
16111     }
16112 }
16113
16114 void
16115 ToEndEvent ()
16116 {
16117     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16118         /* to optimze, we temporarily turn off analysis mode while we feed
16119          * the remaining moves to the engine. Otherwise we get analysis output
16120          * after each move.
16121          */
16122         if (first.analysisSupport) {
16123           SendToProgram("exit\nforce\n", &first);
16124           first.analyzing = FALSE;
16125         }
16126     }
16127
16128     if (gameMode == IcsExamining && !pausing) {
16129         SendToICS(ics_prefix);
16130         SendToICS("forward 999999\n");
16131     } else {
16132         ForwardInner(forwardMostMove);
16133     }
16134
16135     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16136         /* we have fed all the moves, so reactivate analysis mode */
16137         SendToProgram("analyze\n", &first);
16138         first.analyzing = TRUE;
16139         /*first.maybeThinking = TRUE;*/
16140         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16141     }
16142 }
16143
16144 void
16145 BackwardInner (int target)
16146 {
16147     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16148
16149     if (appData.debugMode)
16150         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16151                 target, currentMove, forwardMostMove);
16152
16153     if (gameMode == EditPosition) return;
16154     seekGraphUp = FALSE;
16155     MarkTargetSquares(1);
16156     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16157     if (currentMove <= backwardMostMove) {
16158         ClearHighlights();
16159         DrawPosition(full_redraw, boards[currentMove]);
16160         return;
16161     }
16162     if (gameMode == PlayFromGameFile && !pausing)
16163       PauseEvent();
16164
16165     if (moveList[target][0]) {
16166         int fromX, fromY, toX, toY;
16167         toX = moveList[target][2] - AAA;
16168         toY = moveList[target][3] - ONE;
16169         if (moveList[target][1] == '@') {
16170             if (appData.highlightLastMove) {
16171                 SetHighlights(-1, -1, toX, toY);
16172             }
16173         } else {
16174             fromX = moveList[target][0] - AAA;
16175             fromY = moveList[target][1] - ONE;
16176             if (target == currentMove - 1) {
16177                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16178             }
16179             if (appData.highlightLastMove) {
16180                 SetHighlights(fromX, fromY, toX, toY);
16181             }
16182         }
16183     }
16184     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16185         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16186         while (currentMove > target) {
16187             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16188                 // null move cannot be undone. Reload program with move history before it.
16189                 int i;
16190                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16191                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16192                 }
16193                 SendBoard(&first, i);
16194               if(second.analyzing) SendBoard(&second, i);
16195                 for(currentMove=i; currentMove<target; currentMove++) {
16196                     SendMoveToProgram(currentMove, &first);
16197                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16198                 }
16199                 break;
16200             }
16201             SendToBoth("undo\n");
16202             currentMove--;
16203         }
16204     } else {
16205         currentMove = target;
16206     }
16207
16208     if (gameMode == EditGame || gameMode == EndOfGame) {
16209         whiteTimeRemaining = timeRemaining[0][currentMove];
16210         blackTimeRemaining = timeRemaining[1][currentMove];
16211     }
16212     DisplayBothClocks();
16213     DisplayMove(currentMove - 1);
16214     DrawPosition(full_redraw, boards[currentMove]);
16215     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16216     // [HGM] PV info: routine tests if comment empty
16217     DisplayComment(currentMove - 1, commentList[currentMove]);
16218     ClearMap(); // [HGM] exclude: invalidate map
16219 }
16220
16221 void
16222 BackwardEvent ()
16223 {
16224     if (gameMode == IcsExamining && !pausing) {
16225         SendToICS(ics_prefix);
16226         SendToICS("backward\n");
16227     } else {
16228         BackwardInner(currentMove - 1);
16229     }
16230 }
16231
16232 void
16233 ToStartEvent ()
16234 {
16235     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16236         /* to optimize, we temporarily turn off analysis mode while we undo
16237          * all the moves. Otherwise we get analysis output after each undo.
16238          */
16239         if (first.analysisSupport) {
16240           SendToProgram("exit\nforce\n", &first);
16241           first.analyzing = FALSE;
16242         }
16243     }
16244
16245     if (gameMode == IcsExamining && !pausing) {
16246         SendToICS(ics_prefix);
16247         SendToICS("backward 999999\n");
16248     } else {
16249         BackwardInner(backwardMostMove);
16250     }
16251
16252     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16253         /* we have fed all the moves, so reactivate analysis mode */
16254         SendToProgram("analyze\n", &first);
16255         first.analyzing = TRUE;
16256         /*first.maybeThinking = TRUE;*/
16257         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16258     }
16259 }
16260
16261 void
16262 ToNrEvent (int to)
16263 {
16264   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16265   if (to >= forwardMostMove) to = forwardMostMove;
16266   if (to <= backwardMostMove) to = backwardMostMove;
16267   if (to < currentMove) {
16268     BackwardInner(to);
16269   } else {
16270     ForwardInner(to);
16271   }
16272 }
16273
16274 void
16275 RevertEvent (Boolean annotate)
16276 {
16277     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16278         return;
16279     }
16280     if (gameMode != IcsExamining) {
16281         DisplayError(_("You are not examining a game"), 0);
16282         return;
16283     }
16284     if (pausing) {
16285         DisplayError(_("You can't revert while pausing"), 0);
16286         return;
16287     }
16288     SendToICS(ics_prefix);
16289     SendToICS("revert\n");
16290 }
16291
16292 void
16293 RetractMoveEvent ()
16294 {
16295     switch (gameMode) {
16296       case MachinePlaysWhite:
16297       case MachinePlaysBlack:
16298         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16299             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16300             return;
16301         }
16302         if (forwardMostMove < 2) return;
16303         currentMove = forwardMostMove = forwardMostMove - 2;
16304         whiteTimeRemaining = timeRemaining[0][currentMove];
16305         blackTimeRemaining = timeRemaining[1][currentMove];
16306         DisplayBothClocks();
16307         DisplayMove(currentMove - 1);
16308         ClearHighlights();/*!! could figure this out*/
16309         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16310         SendToProgram("remove\n", &first);
16311         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16312         break;
16313
16314       case BeginningOfGame:
16315       default:
16316         break;
16317
16318       case IcsPlayingWhite:
16319       case IcsPlayingBlack:
16320         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16321             SendToICS(ics_prefix);
16322             SendToICS("takeback 2\n");
16323         } else {
16324             SendToICS(ics_prefix);
16325             SendToICS("takeback 1\n");
16326         }
16327         break;
16328     }
16329 }
16330
16331 void
16332 MoveNowEvent ()
16333 {
16334     ChessProgramState *cps;
16335
16336     switch (gameMode) {
16337       case MachinePlaysWhite:
16338         if (!WhiteOnMove(forwardMostMove)) {
16339             DisplayError(_("It is your turn"), 0);
16340             return;
16341         }
16342         cps = &first;
16343         break;
16344       case MachinePlaysBlack:
16345         if (WhiteOnMove(forwardMostMove)) {
16346             DisplayError(_("It is your turn"), 0);
16347             return;
16348         }
16349         cps = &first;
16350         break;
16351       case TwoMachinesPlay:
16352         if (WhiteOnMove(forwardMostMove) ==
16353             (first.twoMachinesColor[0] == 'w')) {
16354             cps = &first;
16355         } else {
16356             cps = &second;
16357         }
16358         break;
16359       case BeginningOfGame:
16360       default:
16361         return;
16362     }
16363     SendToProgram("?\n", cps);
16364 }
16365
16366 void
16367 TruncateGameEvent ()
16368 {
16369     EditGameEvent();
16370     if (gameMode != EditGame) return;
16371     TruncateGame();
16372 }
16373
16374 void
16375 TruncateGame ()
16376 {
16377     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16378     if (forwardMostMove > currentMove) {
16379         if (gameInfo.resultDetails != NULL) {
16380             free(gameInfo.resultDetails);
16381             gameInfo.resultDetails = NULL;
16382             gameInfo.result = GameUnfinished;
16383         }
16384         forwardMostMove = currentMove;
16385         HistorySet(parseList, backwardMostMove, forwardMostMove,
16386                    currentMove-1);
16387     }
16388 }
16389
16390 void
16391 HintEvent ()
16392 {
16393     if (appData.noChessProgram) return;
16394     switch (gameMode) {
16395       case MachinePlaysWhite:
16396         if (WhiteOnMove(forwardMostMove)) {
16397             DisplayError(_("Wait until your turn."), 0);
16398             return;
16399         }
16400         break;
16401       case BeginningOfGame:
16402       case MachinePlaysBlack:
16403         if (!WhiteOnMove(forwardMostMove)) {
16404             DisplayError(_("Wait until your turn."), 0);
16405             return;
16406         }
16407         break;
16408       default:
16409         DisplayError(_("No hint available"), 0);
16410         return;
16411     }
16412     SendToProgram("hint\n", &first);
16413     hintRequested = TRUE;
16414 }
16415
16416 int
16417 SaveSelected (FILE *g, int dummy, char *dummy2)
16418 {
16419     ListGame * lg = (ListGame *) gameList.head;
16420     int nItem, cnt=0;
16421     FILE *f;
16422
16423     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16424         DisplayError(_("Game list not loaded or empty"), 0);
16425         return 0;
16426     }
16427
16428     creatingBook = TRUE; // suppresses stuff during load game
16429
16430     /* Get list size */
16431     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16432         if(lg->position >= 0) { // selected?
16433             LoadGame(f, nItem, "", TRUE);
16434             SaveGamePGN2(g); // leaves g open
16435             cnt++; DoEvents();
16436         }
16437         lg = (ListGame *) lg->node.succ;
16438     }
16439
16440     fclose(g);
16441     creatingBook = FALSE;
16442
16443     return cnt;
16444 }
16445
16446 void
16447 CreateBookEvent ()
16448 {
16449     ListGame * lg = (ListGame *) gameList.head;
16450     FILE *f, *g;
16451     int nItem;
16452     static int secondTime = FALSE;
16453
16454     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16455         DisplayError(_("Game list not loaded or empty"), 0);
16456         return;
16457     }
16458
16459     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16460         fclose(g);
16461         secondTime++;
16462         DisplayNote(_("Book file exists! Try again for overwrite."));
16463         return;
16464     }
16465
16466     creatingBook = TRUE;
16467     secondTime = FALSE;
16468
16469     /* Get list size */
16470     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16471         if(lg->position >= 0) {
16472             LoadGame(f, nItem, "", TRUE);
16473             AddGameToBook(TRUE);
16474             DoEvents();
16475         }
16476         lg = (ListGame *) lg->node.succ;
16477     }
16478
16479     creatingBook = FALSE;
16480     FlushBook();
16481 }
16482
16483 void
16484 BookEvent ()
16485 {
16486     if (appData.noChessProgram) return;
16487     switch (gameMode) {
16488       case MachinePlaysWhite:
16489         if (WhiteOnMove(forwardMostMove)) {
16490             DisplayError(_("Wait until your turn."), 0);
16491             return;
16492         }
16493         break;
16494       case BeginningOfGame:
16495       case MachinePlaysBlack:
16496         if (!WhiteOnMove(forwardMostMove)) {
16497             DisplayError(_("Wait until your turn."), 0);
16498             return;
16499         }
16500         break;
16501       case EditPosition:
16502         EditPositionDone(TRUE);
16503         break;
16504       case TwoMachinesPlay:
16505         return;
16506       default:
16507         break;
16508     }
16509     SendToProgram("bk\n", &first);
16510     bookOutput[0] = NULLCHAR;
16511     bookRequested = TRUE;
16512 }
16513
16514 void
16515 AboutGameEvent ()
16516 {
16517     char *tags = PGNTags(&gameInfo);
16518     TagsPopUp(tags, CmailMsg());
16519     free(tags);
16520 }
16521
16522 /* end button procedures */
16523
16524 void
16525 PrintPosition (FILE *fp, int move)
16526 {
16527     int i, j;
16528
16529     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16530         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16531             char c = PieceToChar(boards[move][i][j]);
16532             fputc(c == '?' ? '.' : c, fp);
16533             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16534         }
16535     }
16536     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16537       fprintf(fp, "white to play\n");
16538     else
16539       fprintf(fp, "black to play\n");
16540 }
16541
16542 void
16543 PrintOpponents (FILE *fp)
16544 {
16545     if (gameInfo.white != NULL) {
16546         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16547     } else {
16548         fprintf(fp, "\n");
16549     }
16550 }
16551
16552 /* Find last component of program's own name, using some heuristics */
16553 void
16554 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16555 {
16556     char *p, *q, c;
16557     int local = (strcmp(host, "localhost") == 0);
16558     while (!local && (p = strchr(prog, ';')) != NULL) {
16559         p++;
16560         while (*p == ' ') p++;
16561         prog = p;
16562     }
16563     if (*prog == '"' || *prog == '\'') {
16564         q = strchr(prog + 1, *prog);
16565     } else {
16566         q = strchr(prog, ' ');
16567     }
16568     if (q == NULL) q = prog + strlen(prog);
16569     p = q;
16570     while (p >= prog && *p != '/' && *p != '\\') p--;
16571     p++;
16572     if(p == prog && *p == '"') p++;
16573     c = *q; *q = 0;
16574     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16575     memcpy(buf, p, q - p);
16576     buf[q - p] = NULLCHAR;
16577     if (!local) {
16578         strcat(buf, "@");
16579         strcat(buf, host);
16580     }
16581 }
16582
16583 char *
16584 TimeControlTagValue ()
16585 {
16586     char buf[MSG_SIZ];
16587     if (!appData.clockMode) {
16588       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16589     } else if (movesPerSession > 0) {
16590       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16591     } else if (timeIncrement == 0) {
16592       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16593     } else {
16594       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16595     }
16596     return StrSave(buf);
16597 }
16598
16599 void
16600 SetGameInfo ()
16601 {
16602     /* This routine is used only for certain modes */
16603     VariantClass v = gameInfo.variant;
16604     ChessMove r = GameUnfinished;
16605     char *p = NULL;
16606
16607     if(keepInfo) return;
16608
16609     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16610         r = gameInfo.result;
16611         p = gameInfo.resultDetails;
16612         gameInfo.resultDetails = NULL;
16613     }
16614     ClearGameInfo(&gameInfo);
16615     gameInfo.variant = v;
16616
16617     switch (gameMode) {
16618       case MachinePlaysWhite:
16619         gameInfo.event = StrSave( appData.pgnEventHeader );
16620         gameInfo.site = StrSave(HostName());
16621         gameInfo.date = PGNDate();
16622         gameInfo.round = StrSave("-");
16623         gameInfo.white = StrSave(first.tidy);
16624         gameInfo.black = StrSave(UserName());
16625         gameInfo.timeControl = TimeControlTagValue();
16626         break;
16627
16628       case MachinePlaysBlack:
16629         gameInfo.event = StrSave( appData.pgnEventHeader );
16630         gameInfo.site = StrSave(HostName());
16631         gameInfo.date = PGNDate();
16632         gameInfo.round = StrSave("-");
16633         gameInfo.white = StrSave(UserName());
16634         gameInfo.black = StrSave(first.tidy);
16635         gameInfo.timeControl = TimeControlTagValue();
16636         break;
16637
16638       case TwoMachinesPlay:
16639         gameInfo.event = StrSave( appData.pgnEventHeader );
16640         gameInfo.site = StrSave(HostName());
16641         gameInfo.date = PGNDate();
16642         if (roundNr > 0) {
16643             char buf[MSG_SIZ];
16644             snprintf(buf, MSG_SIZ, "%d", roundNr);
16645             gameInfo.round = StrSave(buf);
16646         } else {
16647             gameInfo.round = StrSave("-");
16648         }
16649         if (first.twoMachinesColor[0] == 'w') {
16650             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16651             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16652         } else {
16653             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16654             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16655         }
16656         gameInfo.timeControl = TimeControlTagValue();
16657         break;
16658
16659       case EditGame:
16660         gameInfo.event = StrSave("Edited game");
16661         gameInfo.site = StrSave(HostName());
16662         gameInfo.date = PGNDate();
16663         gameInfo.round = StrSave("-");
16664         gameInfo.white = StrSave("-");
16665         gameInfo.black = StrSave("-");
16666         gameInfo.result = r;
16667         gameInfo.resultDetails = p;
16668         break;
16669
16670       case EditPosition:
16671         gameInfo.event = StrSave("Edited position");
16672         gameInfo.site = StrSave(HostName());
16673         gameInfo.date = PGNDate();
16674         gameInfo.round = StrSave("-");
16675         gameInfo.white = StrSave("-");
16676         gameInfo.black = StrSave("-");
16677         break;
16678
16679       case IcsPlayingWhite:
16680       case IcsPlayingBlack:
16681       case IcsObserving:
16682       case IcsExamining:
16683         break;
16684
16685       case PlayFromGameFile:
16686         gameInfo.event = StrSave("Game from non-PGN file");
16687         gameInfo.site = StrSave(HostName());
16688         gameInfo.date = PGNDate();
16689         gameInfo.round = StrSave("-");
16690         gameInfo.white = StrSave("?");
16691         gameInfo.black = StrSave("?");
16692         break;
16693
16694       default:
16695         break;
16696     }
16697 }
16698
16699 void
16700 ReplaceComment (int index, char *text)
16701 {
16702     int len;
16703     char *p;
16704     float score;
16705
16706     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16707        pvInfoList[index-1].depth == len &&
16708        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16709        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16710     while (*text == '\n') text++;
16711     len = strlen(text);
16712     while (len > 0 && text[len - 1] == '\n') len--;
16713
16714     if (commentList[index] != NULL)
16715       free(commentList[index]);
16716
16717     if (len == 0) {
16718         commentList[index] = NULL;
16719         return;
16720     }
16721   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16722       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16723       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16724     commentList[index] = (char *) malloc(len + 2);
16725     strncpy(commentList[index], text, len);
16726     commentList[index][len] = '\n';
16727     commentList[index][len + 1] = NULLCHAR;
16728   } else {
16729     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16730     char *p;
16731     commentList[index] = (char *) malloc(len + 7);
16732     safeStrCpy(commentList[index], "{\n", 3);
16733     safeStrCpy(commentList[index]+2, text, len+1);
16734     commentList[index][len+2] = NULLCHAR;
16735     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16736     strcat(commentList[index], "\n}\n");
16737   }
16738 }
16739
16740 void
16741 CrushCRs (char *text)
16742 {
16743   char *p = text;
16744   char *q = text;
16745   char ch;
16746
16747   do {
16748     ch = *p++;
16749     if (ch == '\r') continue;
16750     *q++ = ch;
16751   } while (ch != '\0');
16752 }
16753
16754 void
16755 AppendComment (int index, char *text, Boolean addBraces)
16756 /* addBraces  tells if we should add {} */
16757 {
16758     int oldlen, len;
16759     char *old;
16760
16761 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16762     if(addBraces == 3) addBraces = 0; else // force appending literally
16763     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16764
16765     CrushCRs(text);
16766     while (*text == '\n') text++;
16767     len = strlen(text);
16768     while (len > 0 && text[len - 1] == '\n') len--;
16769     text[len] = NULLCHAR;
16770
16771     if (len == 0) return;
16772
16773     if (commentList[index] != NULL) {
16774       Boolean addClosingBrace = addBraces;
16775         old = commentList[index];
16776         oldlen = strlen(old);
16777         while(commentList[index][oldlen-1] ==  '\n')
16778           commentList[index][--oldlen] = NULLCHAR;
16779         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16780         safeStrCpy(commentList[index], old, oldlen + len + 6);
16781         free(old);
16782         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16783         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16784           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16785           while (*text == '\n') { text++; len--; }
16786           commentList[index][--oldlen] = NULLCHAR;
16787       }
16788         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16789         else          strcat(commentList[index], "\n");
16790         strcat(commentList[index], text);
16791         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16792         else          strcat(commentList[index], "\n");
16793     } else {
16794         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16795         if(addBraces)
16796           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16797         else commentList[index][0] = NULLCHAR;
16798         strcat(commentList[index], text);
16799         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16800         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16801     }
16802 }
16803
16804 static char *
16805 FindStr (char * text, char * sub_text)
16806 {
16807     char * result = strstr( text, sub_text );
16808
16809     if( result != NULL ) {
16810         result += strlen( sub_text );
16811     }
16812
16813     return result;
16814 }
16815
16816 /* [AS] Try to extract PV info from PGN comment */
16817 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16818 char *
16819 GetInfoFromComment (int index, char * text)
16820 {
16821     char * sep = text, *p;
16822
16823     if( text != NULL && index > 0 ) {
16824         int score = 0;
16825         int depth = 0;
16826         int time = -1, sec = 0, deci;
16827         char * s_eval = FindStr( text, "[%eval " );
16828         char * s_emt = FindStr( text, "[%emt " );
16829 #if 0
16830         if( s_eval != NULL || s_emt != NULL ) {
16831 #else
16832         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16833 #endif
16834             /* New style */
16835             char delim;
16836
16837             if( s_eval != NULL ) {
16838                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16839                     return text;
16840                 }
16841
16842                 if( delim != ']' ) {
16843                     return text;
16844                 }
16845             }
16846
16847             if( s_emt != NULL ) {
16848             }
16849                 return text;
16850         }
16851         else {
16852             /* We expect something like: [+|-]nnn.nn/dd */
16853             int score_lo = 0;
16854
16855             if(*text != '{') return text; // [HGM] braces: must be normal comment
16856
16857             sep = strchr( text, '/' );
16858             if( sep == NULL || sep < (text+4) ) {
16859                 return text;
16860             }
16861
16862             p = text;
16863             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16864             if(p[1] == '(') { // comment starts with PV
16865                p = strchr(p, ')'); // locate end of PV
16866                if(p == NULL || sep < p+5) return text;
16867                // at this point we have something like "{(.*) +0.23/6 ..."
16868                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16869                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16870                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16871             }
16872             time = -1; sec = -1; deci = -1;
16873             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16874                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16875                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16876                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16877                 return text;
16878             }
16879
16880             if( score_lo < 0 || score_lo >= 100 ) {
16881                 return text;
16882             }
16883
16884             if(sec >= 0) time = 600*time + 10*sec; else
16885             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16886
16887             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16888
16889             /* [HGM] PV time: now locate end of PV info */
16890             while( *++sep >= '0' && *sep <= '9'); // strip depth
16891             if(time >= 0)
16892             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16893             if(sec >= 0)
16894             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16895             if(deci >= 0)
16896             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16897             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16898         }
16899
16900         if( depth <= 0 ) {
16901             return text;
16902         }
16903
16904         if( time < 0 ) {
16905             time = -1;
16906         }
16907
16908         pvInfoList[index-1].depth = depth;
16909         pvInfoList[index-1].score = score;
16910         pvInfoList[index-1].time  = 10*time; // centi-sec
16911         if(*sep == '}') *sep = 0; else *--sep = '{';
16912         if(p != text) {
16913             while(*p++ = *sep++)
16914                                 ;
16915             sep = text;
16916         } // squeeze out space between PV and comment, and return both
16917     }
16918     return sep;
16919 }
16920
16921 void
16922 SendToProgram (char *message, ChessProgramState *cps)
16923 {
16924     int count, outCount, error;
16925     char buf[MSG_SIZ];
16926
16927     if (cps->pr == NoProc) return;
16928     Attention(cps);
16929
16930     if (appData.debugMode) {
16931         TimeMark now;
16932         GetTimeMark(&now);
16933         fprintf(debugFP, "%ld >%-6s: %s",
16934                 SubtractTimeMarks(&now, &programStartTime),
16935                 cps->which, message);
16936         if(serverFP)
16937             fprintf(serverFP, "%ld >%-6s: %s",
16938                 SubtractTimeMarks(&now, &programStartTime),
16939                 cps->which, message), fflush(serverFP);
16940     }
16941
16942     count = strlen(message);
16943     outCount = OutputToProcess(cps->pr, message, count, &error);
16944     if (outCount < count && !exiting
16945                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16946       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16947       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16948         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16949             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16950                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16951                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16952                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16953             } else {
16954                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16955                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16956                 gameInfo.result = res;
16957             }
16958             gameInfo.resultDetails = StrSave(buf);
16959         }
16960         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16961         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16962     }
16963 }
16964
16965 void
16966 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16967 {
16968     char *end_str;
16969     char buf[MSG_SIZ];
16970     ChessProgramState *cps = (ChessProgramState *)closure;
16971
16972     if (isr != cps->isr) return; /* Killed intentionally */
16973     if (count <= 0) {
16974         if (count == 0) {
16975             RemoveInputSource(cps->isr);
16976             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16977                     _(cps->which), cps->program);
16978             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16979             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16980                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16981                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16982                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16983                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16984                 } else {
16985                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16986                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16987                     gameInfo.result = res;
16988                 }
16989                 gameInfo.resultDetails = StrSave(buf);
16990             }
16991             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16992             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16993         } else {
16994             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16995                     _(cps->which), cps->program);
16996             RemoveInputSource(cps->isr);
16997
16998             /* [AS] Program is misbehaving badly... kill it */
16999             if( count == -2 ) {
17000                 DestroyChildProcess( cps->pr, 9 );
17001                 cps->pr = NoProc;
17002             }
17003
17004             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17005         }
17006         return;
17007     }
17008
17009     if ((end_str = strchr(message, '\r')) != NULL)
17010       *end_str = NULLCHAR;
17011     if ((end_str = strchr(message, '\n')) != NULL)
17012       *end_str = NULLCHAR;
17013
17014     if (appData.debugMode) {
17015         TimeMark now; int print = 1;
17016         char *quote = ""; char c; int i;
17017
17018         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17019                 char start = message[0];
17020                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17021                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17022                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17023                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17024                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17025                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17026                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17027                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17028                    sscanf(message, "hint: %c", &c)!=1 &&
17029                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17030                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17031                     print = (appData.engineComments >= 2);
17032                 }
17033                 message[0] = start; // restore original message
17034         }
17035         if(print) {
17036                 GetTimeMark(&now);
17037                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17038                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17039                         quote,
17040                         message);
17041                 if(serverFP)
17042                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17043                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17044                         quote,
17045                         message), fflush(serverFP);
17046         }
17047     }
17048
17049     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17050     if (appData.icsEngineAnalyze) {
17051         if (strstr(message, "whisper") != NULL ||
17052              strstr(message, "kibitz") != NULL ||
17053             strstr(message, "tellics") != NULL) return;
17054     }
17055
17056     HandleMachineMove(message, cps);
17057 }
17058
17059
17060 void
17061 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17062 {
17063     char buf[MSG_SIZ];
17064     int seconds;
17065
17066     if( timeControl_2 > 0 ) {
17067         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17068             tc = timeControl_2;
17069         }
17070     }
17071     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17072     inc /= cps->timeOdds;
17073     st  /= cps->timeOdds;
17074
17075     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17076
17077     if (st > 0) {
17078       /* Set exact time per move, normally using st command */
17079       if (cps->stKludge) {
17080         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17081         seconds = st % 60;
17082         if (seconds == 0) {
17083           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17084         } else {
17085           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17086         }
17087       } else {
17088         snprintf(buf, MSG_SIZ, "st %d\n", st);
17089       }
17090     } else {
17091       /* Set conventional or incremental time control, using level command */
17092       if (seconds == 0) {
17093         /* Note old gnuchess bug -- minutes:seconds used to not work.
17094            Fixed in later versions, but still avoid :seconds
17095            when seconds is 0. */
17096         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17097       } else {
17098         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17099                  seconds, inc/1000.);
17100       }
17101     }
17102     SendToProgram(buf, cps);
17103
17104     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17105     /* Orthogonally, limit search to given depth */
17106     if (sd > 0) {
17107       if (cps->sdKludge) {
17108         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17109       } else {
17110         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17111       }
17112       SendToProgram(buf, cps);
17113     }
17114
17115     if(cps->nps >= 0) { /* [HGM] nps */
17116         if(cps->supportsNPS == FALSE)
17117           cps->nps = -1; // don't use if engine explicitly says not supported!
17118         else {
17119           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17120           SendToProgram(buf, cps);
17121         }
17122     }
17123 }
17124
17125 ChessProgramState *
17126 WhitePlayer ()
17127 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17128 {
17129     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17130        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17131         return &second;
17132     return &first;
17133 }
17134
17135 void
17136 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17137 {
17138     char message[MSG_SIZ];
17139     long time, otime;
17140
17141     /* Note: this routine must be called when the clocks are stopped
17142        or when they have *just* been set or switched; otherwise
17143        it will be off by the time since the current tick started.
17144     */
17145     if (machineWhite) {
17146         time = whiteTimeRemaining / 10;
17147         otime = blackTimeRemaining / 10;
17148     } else {
17149         time = blackTimeRemaining / 10;
17150         otime = whiteTimeRemaining / 10;
17151     }
17152     /* [HGM] translate opponent's time by time-odds factor */
17153     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17154
17155     if (time <= 0) time = 1;
17156     if (otime <= 0) otime = 1;
17157
17158     snprintf(message, MSG_SIZ, "time %ld\n", time);
17159     SendToProgram(message, cps);
17160
17161     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17162     SendToProgram(message, cps);
17163 }
17164
17165 char *
17166 EngineDefinedVariant (ChessProgramState *cps, int n)
17167 {   // return name of n-th unknown variant that engine supports
17168     static char buf[MSG_SIZ];
17169     char *p, *s = cps->variants;
17170     if(!s) return NULL;
17171     do { // parse string from variants feature
17172       VariantClass v;
17173         p = strchr(s, ',');
17174         if(p) *p = NULLCHAR;
17175       v = StringToVariant(s);
17176       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17177         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17178             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17179                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17180                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17181                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17182             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17183         }
17184         if(p) *p++ = ',';
17185         if(n < 0) return buf;
17186     } while(s = p);
17187     return NULL;
17188 }
17189
17190 int
17191 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17192 {
17193   char buf[MSG_SIZ];
17194   int len = strlen(name);
17195   int val;
17196
17197   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17198     (*p) += len + 1;
17199     sscanf(*p, "%d", &val);
17200     *loc = (val != 0);
17201     while (**p && **p != ' ')
17202       (*p)++;
17203     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17204     SendToProgram(buf, cps);
17205     return TRUE;
17206   }
17207   return FALSE;
17208 }
17209
17210 int
17211 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17212 {
17213   char buf[MSG_SIZ];
17214   int len = strlen(name);
17215   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17216     (*p) += len + 1;
17217     sscanf(*p, "%d", loc);
17218     while (**p && **p != ' ') (*p)++;
17219     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17220     SendToProgram(buf, cps);
17221     return TRUE;
17222   }
17223   return FALSE;
17224 }
17225
17226 int
17227 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17228 {
17229   char buf[MSG_SIZ];
17230   int len = strlen(name);
17231   if (strncmp((*p), name, len) == 0
17232       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17233     (*p) += len + 2;
17234     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
17235     FREE(*loc); *loc = malloc(len);
17236     strncpy(*loc, *p, len);
17237     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17238     while (**p && **p != '\"') (*p)++;
17239     if (**p == '\"') (*p)++;
17240     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17241     SendToProgram(buf, cps);
17242     return TRUE;
17243   }
17244   return FALSE;
17245 }
17246
17247 int
17248 ParseOption (Option *opt, ChessProgramState *cps)
17249 // [HGM] options: process the string that defines an engine option, and determine
17250 // name, type, default value, and allowed value range
17251 {
17252         char *p, *q, buf[MSG_SIZ];
17253         int n, min = (-1)<<31, max = 1<<31, def;
17254
17255         opt->target = &opt->value;   // OK for spin/slider and checkbox
17256         if(p = strstr(opt->name, " -spin ")) {
17257             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17258             if(max < min) max = min; // enforce consistency
17259             if(def < min) def = min;
17260             if(def > max) def = max;
17261             opt->value = def;
17262             opt->min = min;
17263             opt->max = max;
17264             opt->type = Spin;
17265         } else if((p = strstr(opt->name, " -slider "))) {
17266             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17267             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17268             if(max < min) max = min; // enforce consistency
17269             if(def < min) def = min;
17270             if(def > max) def = max;
17271             opt->value = def;
17272             opt->min = min;
17273             opt->max = max;
17274             opt->type = Spin; // Slider;
17275         } else if((p = strstr(opt->name, " -string "))) {
17276             opt->textValue = p+9;
17277             opt->type = TextBox;
17278             opt->target = &opt->textValue;
17279         } else if((p = strstr(opt->name, " -file "))) {
17280             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17281             opt->target = opt->textValue = p+7;
17282             opt->type = FileName; // FileName;
17283             opt->target = &opt->textValue;
17284         } else if((p = strstr(opt->name, " -path "))) {
17285             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17286             opt->target = opt->textValue = p+7;
17287             opt->type = PathName; // PathName;
17288             opt->target = &opt->textValue;
17289         } else if(p = strstr(opt->name, " -check ")) {
17290             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17291             opt->value = (def != 0);
17292             opt->type = CheckBox;
17293         } else if(p = strstr(opt->name, " -combo ")) {
17294             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17295             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17296             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17297             opt->value = n = 0;
17298             while(q = StrStr(q, " /// ")) {
17299                 n++; *q = 0;    // count choices, and null-terminate each of them
17300                 q += 5;
17301                 if(*q == '*') { // remember default, which is marked with * prefix
17302                     q++;
17303                     opt->value = n;
17304                 }
17305                 cps->comboList[cps->comboCnt++] = q;
17306             }
17307             cps->comboList[cps->comboCnt++] = NULL;
17308             opt->max = n + 1;
17309             opt->type = ComboBox;
17310         } else if(p = strstr(opt->name, " -button")) {
17311             opt->type = Button;
17312         } else if(p = strstr(opt->name, " -save")) {
17313             opt->type = SaveButton;
17314         } else return FALSE;
17315         *p = 0; // terminate option name
17316         // now look if the command-line options define a setting for this engine option.
17317         if(cps->optionSettings && cps->optionSettings[0])
17318             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17319         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17320           snprintf(buf, MSG_SIZ, "option %s", p);
17321                 if(p = strstr(buf, ",")) *p = 0;
17322                 if(q = strchr(buf, '=')) switch(opt->type) {
17323                     case ComboBox:
17324                         for(n=0; n<opt->max; n++)
17325                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17326                         break;
17327                     case TextBox:
17328                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17329                         break;
17330                     case Spin:
17331                     case CheckBox:
17332                         opt->value = atoi(q+1);
17333                     default:
17334                         break;
17335                 }
17336                 strcat(buf, "\n");
17337                 SendToProgram(buf, cps);
17338         }
17339         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17340         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17341         return TRUE;
17342 }
17343
17344 void
17345 FeatureDone (ChessProgramState *cps, int val)
17346 {
17347   DelayedEventCallback cb = GetDelayedEvent();
17348   if ((cb == InitBackEnd3 && cps == &first) ||
17349       (cb == SettingsMenuIfReady && cps == &second) ||
17350       (cb == LoadEngine) ||
17351       (cb == TwoMachinesEventIfReady)) {
17352     CancelDelayedEvent();
17353     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17354   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17355   cps->initDone = val;
17356   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17357 }
17358
17359 /* Parse feature command from engine */
17360 void
17361 ParseFeatures (char *args, ChessProgramState *cps)
17362 {
17363   char *p = args;
17364   char *q = NULL;
17365   int val;
17366   char buf[MSG_SIZ];
17367
17368   for (;;) {
17369     while (*p == ' ') p++;
17370     if (*p == NULLCHAR) return;
17371
17372     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17373     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17374     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17375     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17376     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17377     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17378     if (BoolFeature(&p, "reuse", &val, cps)) {
17379       /* Engine can disable reuse, but can't enable it if user said no */
17380       if (!val) cps->reuse = FALSE;
17381       continue;
17382     }
17383     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17384     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17385       if (gameMode == TwoMachinesPlay) {
17386         DisplayTwoMachinesTitle();
17387       } else {
17388         DisplayTitle("");
17389       }
17390       continue;
17391     }
17392     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17393     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17394     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17395     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17396     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17397     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17398     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17399     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17400     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17401     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17402     if (IntFeature(&p, "done", &val, cps)) {
17403       FeatureDone(cps, val);
17404       continue;
17405     }
17406     /* Added by Tord: */
17407     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17408     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17409     /* End of additions by Tord */
17410
17411     /* [HGM] added features: */
17412     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17413     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17414     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17415     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17416     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17417     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17418     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17419     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17420         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17421         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17422         FREE(cps->option[cps->nrOptions].name);
17423         cps->option[cps->nrOptions].name = q; q = NULL;
17424         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17425           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17426             SendToProgram(buf, cps);
17427             continue;
17428         }
17429         if(cps->nrOptions >= MAX_OPTIONS) {
17430             cps->nrOptions--;
17431             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17432             DisplayError(buf, 0);
17433         }
17434         continue;
17435     }
17436     /* End of additions by HGM */
17437
17438     /* unknown feature: complain and skip */
17439     q = p;
17440     while (*q && *q != '=') q++;
17441     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17442     SendToProgram(buf, cps);
17443     p = q;
17444     if (*p == '=') {
17445       p++;
17446       if (*p == '\"') {
17447         p++;
17448         while (*p && *p != '\"') p++;
17449         if (*p == '\"') p++;
17450       } else {
17451         while (*p && *p != ' ') p++;
17452       }
17453     }
17454   }
17455
17456 }
17457
17458 void
17459 PeriodicUpdatesEvent (int newState)
17460 {
17461     if (newState == appData.periodicUpdates)
17462       return;
17463
17464     appData.periodicUpdates=newState;
17465
17466     /* Display type changes, so update it now */
17467 //    DisplayAnalysis();
17468
17469     /* Get the ball rolling again... */
17470     if (newState) {
17471         AnalysisPeriodicEvent(1);
17472         StartAnalysisClock();
17473     }
17474 }
17475
17476 void
17477 PonderNextMoveEvent (int newState)
17478 {
17479     if (newState == appData.ponderNextMove) return;
17480     if (gameMode == EditPosition) EditPositionDone(TRUE);
17481     if (newState) {
17482         SendToProgram("hard\n", &first);
17483         if (gameMode == TwoMachinesPlay) {
17484             SendToProgram("hard\n", &second);
17485         }
17486     } else {
17487         SendToProgram("easy\n", &first);
17488         thinkOutput[0] = NULLCHAR;
17489         if (gameMode == TwoMachinesPlay) {
17490             SendToProgram("easy\n", &second);
17491         }
17492     }
17493     appData.ponderNextMove = newState;
17494 }
17495
17496 void
17497 NewSettingEvent (int option, int *feature, char *command, int value)
17498 {
17499     char buf[MSG_SIZ];
17500
17501     if (gameMode == EditPosition) EditPositionDone(TRUE);
17502     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17503     if(feature == NULL || *feature) SendToProgram(buf, &first);
17504     if (gameMode == TwoMachinesPlay) {
17505         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17506     }
17507 }
17508
17509 void
17510 ShowThinkingEvent ()
17511 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17512 {
17513     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17514     int newState = appData.showThinking
17515         // [HGM] thinking: other features now need thinking output as well
17516         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17517
17518     if (oldState == newState) return;
17519     oldState = newState;
17520     if (gameMode == EditPosition) EditPositionDone(TRUE);
17521     if (oldState) {
17522         SendToProgram("post\n", &first);
17523         if (gameMode == TwoMachinesPlay) {
17524             SendToProgram("post\n", &second);
17525         }
17526     } else {
17527         SendToProgram("nopost\n", &first);
17528         thinkOutput[0] = NULLCHAR;
17529         if (gameMode == TwoMachinesPlay) {
17530             SendToProgram("nopost\n", &second);
17531         }
17532     }
17533 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17534 }
17535
17536 void
17537 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17538 {
17539   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17540   if (pr == NoProc) return;
17541   AskQuestion(title, question, replyPrefix, pr);
17542 }
17543
17544 void
17545 TypeInEvent (char firstChar)
17546 {
17547     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17548         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17549         gameMode == AnalyzeMode || gameMode == EditGame ||
17550         gameMode == EditPosition || gameMode == IcsExamining ||
17551         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17552         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17553                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17554                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17555         gameMode == Training) PopUpMoveDialog(firstChar);
17556 }
17557
17558 void
17559 TypeInDoneEvent (char *move)
17560 {
17561         Board board;
17562         int n, fromX, fromY, toX, toY;
17563         char promoChar;
17564         ChessMove moveType;
17565
17566         // [HGM] FENedit
17567         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17568                 EditPositionPasteFEN(move);
17569                 return;
17570         }
17571         // [HGM] movenum: allow move number to be typed in any mode
17572         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17573           ToNrEvent(2*n-1);
17574           return;
17575         }
17576         // undocumented kludge: allow command-line option to be typed in!
17577         // (potentially fatal, and does not implement the effect of the option.)
17578         // should only be used for options that are values on which future decisions will be made,
17579         // and definitely not on options that would be used during initialization.
17580         if(strstr(move, "!!! -") == move) {
17581             ParseArgsFromString(move+4);
17582             return;
17583         }
17584
17585       if (gameMode != EditGame && currentMove != forwardMostMove &&
17586         gameMode != Training) {
17587         DisplayMoveError(_("Displayed move is not current"));
17588       } else {
17589         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17590           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17591         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17592         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17593           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17594           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17595         } else {
17596           DisplayMoveError(_("Could not parse move"));
17597         }
17598       }
17599 }
17600
17601 void
17602 DisplayMove (int moveNumber)
17603 {
17604     char message[MSG_SIZ];
17605     char res[MSG_SIZ];
17606     char cpThinkOutput[MSG_SIZ];
17607
17608     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17609
17610     if (moveNumber == forwardMostMove - 1 ||
17611         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17612
17613         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17614
17615         if (strchr(cpThinkOutput, '\n')) {
17616             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17617         }
17618     } else {
17619         *cpThinkOutput = NULLCHAR;
17620     }
17621
17622     /* [AS] Hide thinking from human user */
17623     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17624         *cpThinkOutput = NULLCHAR;
17625         if( thinkOutput[0] != NULLCHAR ) {
17626             int i;
17627
17628             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17629                 cpThinkOutput[i] = '.';
17630             }
17631             cpThinkOutput[i] = NULLCHAR;
17632             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17633         }
17634     }
17635
17636     if (moveNumber == forwardMostMove - 1 &&
17637         gameInfo.resultDetails != NULL) {
17638         if (gameInfo.resultDetails[0] == NULLCHAR) {
17639           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17640         } else {
17641           snprintf(res, MSG_SIZ, " {%s} %s",
17642                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17643         }
17644     } else {
17645         res[0] = NULLCHAR;
17646     }
17647
17648     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17649         DisplayMessage(res, cpThinkOutput);
17650     } else {
17651       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17652                 WhiteOnMove(moveNumber) ? " " : ".. ",
17653                 parseList[moveNumber], res);
17654         DisplayMessage(message, cpThinkOutput);
17655     }
17656 }
17657
17658 void
17659 DisplayComment (int moveNumber, char *text)
17660 {
17661     char title[MSG_SIZ];
17662
17663     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17664       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17665     } else {
17666       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17667               WhiteOnMove(moveNumber) ? " " : ".. ",
17668               parseList[moveNumber]);
17669     }
17670     if (text != NULL && (appData.autoDisplayComment || commentUp))
17671         CommentPopUp(title, text);
17672 }
17673
17674 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17675  * might be busy thinking or pondering.  It can be omitted if your
17676  * gnuchess is configured to stop thinking immediately on any user
17677  * input.  However, that gnuchess feature depends on the FIONREAD
17678  * ioctl, which does not work properly on some flavors of Unix.
17679  */
17680 void
17681 Attention (ChessProgramState *cps)
17682 {
17683 #if ATTENTION
17684     if (!cps->useSigint) return;
17685     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17686     switch (gameMode) {
17687       case MachinePlaysWhite:
17688       case MachinePlaysBlack:
17689       case TwoMachinesPlay:
17690       case IcsPlayingWhite:
17691       case IcsPlayingBlack:
17692       case AnalyzeMode:
17693       case AnalyzeFile:
17694         /* Skip if we know it isn't thinking */
17695         if (!cps->maybeThinking) return;
17696         if (appData.debugMode)
17697           fprintf(debugFP, "Interrupting %s\n", cps->which);
17698         InterruptChildProcess(cps->pr);
17699         cps->maybeThinking = FALSE;
17700         break;
17701       default:
17702         break;
17703     }
17704 #endif /*ATTENTION*/
17705 }
17706
17707 int
17708 CheckFlags ()
17709 {
17710     if (whiteTimeRemaining <= 0) {
17711         if (!whiteFlag) {
17712             whiteFlag = TRUE;
17713             if (appData.icsActive) {
17714                 if (appData.autoCallFlag &&
17715                     gameMode == IcsPlayingBlack && !blackFlag) {
17716                   SendToICS(ics_prefix);
17717                   SendToICS("flag\n");
17718                 }
17719             } else {
17720                 if (blackFlag) {
17721                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17722                 } else {
17723                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17724                     if (appData.autoCallFlag) {
17725                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17726                         return TRUE;
17727                     }
17728                 }
17729             }
17730         }
17731     }
17732     if (blackTimeRemaining <= 0) {
17733         if (!blackFlag) {
17734             blackFlag = TRUE;
17735             if (appData.icsActive) {
17736                 if (appData.autoCallFlag &&
17737                     gameMode == IcsPlayingWhite && !whiteFlag) {
17738                   SendToICS(ics_prefix);
17739                   SendToICS("flag\n");
17740                 }
17741             } else {
17742                 if (whiteFlag) {
17743                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17744                 } else {
17745                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17746                     if (appData.autoCallFlag) {
17747                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17748                         return TRUE;
17749                     }
17750                 }
17751             }
17752         }
17753     }
17754     return FALSE;
17755 }
17756
17757 void
17758 CheckTimeControl ()
17759 {
17760     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17761         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17762
17763     /*
17764      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17765      */
17766     if ( !WhiteOnMove(forwardMostMove) ) {
17767         /* White made time control */
17768         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17769         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17770         /* [HGM] time odds: correct new time quota for time odds! */
17771                                             / WhitePlayer()->timeOdds;
17772         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17773     } else {
17774         lastBlack -= blackTimeRemaining;
17775         /* Black made time control */
17776         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17777                                             / WhitePlayer()->other->timeOdds;
17778         lastWhite = whiteTimeRemaining;
17779     }
17780 }
17781
17782 void
17783 DisplayBothClocks ()
17784 {
17785     int wom = gameMode == EditPosition ?
17786       !blackPlaysFirst : WhiteOnMove(currentMove);
17787     DisplayWhiteClock(whiteTimeRemaining, wom);
17788     DisplayBlackClock(blackTimeRemaining, !wom);
17789 }
17790
17791
17792 /* Timekeeping seems to be a portability nightmare.  I think everyone
17793    has ftime(), but I'm really not sure, so I'm including some ifdefs
17794    to use other calls if you don't.  Clocks will be less accurate if
17795    you have neither ftime nor gettimeofday.
17796 */
17797
17798 /* VS 2008 requires the #include outside of the function */
17799 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17800 #include <sys/timeb.h>
17801 #endif
17802
17803 /* Get the current time as a TimeMark */
17804 void
17805 GetTimeMark (TimeMark *tm)
17806 {
17807 #if HAVE_GETTIMEOFDAY
17808
17809     struct timeval timeVal;
17810     struct timezone timeZone;
17811
17812     gettimeofday(&timeVal, &timeZone);
17813     tm->sec = (long) timeVal.tv_sec;
17814     tm->ms = (int) (timeVal.tv_usec / 1000L);
17815
17816 #else /*!HAVE_GETTIMEOFDAY*/
17817 #if HAVE_FTIME
17818
17819 // include <sys/timeb.h> / moved to just above start of function
17820     struct timeb timeB;
17821
17822     ftime(&timeB);
17823     tm->sec = (long) timeB.time;
17824     tm->ms = (int) timeB.millitm;
17825
17826 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17827     tm->sec = (long) time(NULL);
17828     tm->ms = 0;
17829 #endif
17830 #endif
17831 }
17832
17833 /* Return the difference in milliseconds between two
17834    time marks.  We assume the difference will fit in a long!
17835 */
17836 long
17837 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17838 {
17839     return 1000L*(tm2->sec - tm1->sec) +
17840            (long) (tm2->ms - tm1->ms);
17841 }
17842
17843
17844 /*
17845  * Code to manage the game clocks.
17846  *
17847  * In tournament play, black starts the clock and then white makes a move.
17848  * We give the human user a slight advantage if he is playing white---the
17849  * clocks don't run until he makes his first move, so it takes zero time.
17850  * Also, we don't account for network lag, so we could get out of sync
17851  * with GNU Chess's clock -- but then, referees are always right.
17852  */
17853
17854 static TimeMark tickStartTM;
17855 static long intendedTickLength;
17856
17857 long
17858 NextTickLength (long timeRemaining)
17859 {
17860     long nominalTickLength, nextTickLength;
17861
17862     if (timeRemaining > 0L && timeRemaining <= 10000L)
17863       nominalTickLength = 100L;
17864     else
17865       nominalTickLength = 1000L;
17866     nextTickLength = timeRemaining % nominalTickLength;
17867     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17868
17869     return nextTickLength;
17870 }
17871
17872 /* Adjust clock one minute up or down */
17873 void
17874 AdjustClock (Boolean which, int dir)
17875 {
17876     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17877     if(which) blackTimeRemaining += 60000*dir;
17878     else      whiteTimeRemaining += 60000*dir;
17879     DisplayBothClocks();
17880     adjustedClock = TRUE;
17881 }
17882
17883 /* Stop clocks and reset to a fresh time control */
17884 void
17885 ResetClocks ()
17886 {
17887     (void) StopClockTimer();
17888     if (appData.icsActive) {
17889         whiteTimeRemaining = blackTimeRemaining = 0;
17890     } else if (searchTime) {
17891         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17892         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17893     } else { /* [HGM] correct new time quote for time odds */
17894         whiteTC = blackTC = fullTimeControlString;
17895         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17896         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17897     }
17898     if (whiteFlag || blackFlag) {
17899         DisplayTitle("");
17900         whiteFlag = blackFlag = FALSE;
17901     }
17902     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17903     DisplayBothClocks();
17904     adjustedClock = FALSE;
17905 }
17906
17907 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17908
17909 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17910
17911 /* Decrement running clock by amount of time that has passed */
17912 void
17913 DecrementClocks ()
17914 {
17915     long tRemaining;
17916     long lastTickLength, fudge;
17917     TimeMark now;
17918
17919     if (!appData.clockMode) return;
17920     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17921
17922     GetTimeMark(&now);
17923
17924     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17925
17926     /* Fudge if we woke up a little too soon */
17927     fudge = intendedTickLength - lastTickLength;
17928     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17929
17930     if (WhiteOnMove(forwardMostMove)) {
17931         if(whiteNPS >= 0) lastTickLength = 0;
17932          tRemaining = whiteTimeRemaining -= lastTickLength;
17933         if( tRemaining < 0 && !appData.icsActive) {
17934             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17935             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17936                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17937                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17938             }
17939         }
17940         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17941         DisplayWhiteClock(whiteTimeRemaining - fudge,
17942                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17943         timeSuffix = 0;
17944     } else {
17945         if(blackNPS >= 0) lastTickLength = 0;
17946          tRemaining = blackTimeRemaining -= lastTickLength;
17947         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17948             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17949             if(suddenDeath) {
17950                 blackStartMove = forwardMostMove;
17951                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17952             }
17953         }
17954         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17955         DisplayBlackClock(blackTimeRemaining - fudge,
17956                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17957         timeSuffix = 0;
17958     }
17959     if (CheckFlags()) return;
17960
17961     if(twoBoards) { // count down secondary board's clocks as well
17962         activePartnerTime -= lastTickLength;
17963         partnerUp = 1;
17964         if(activePartner == 'W')
17965             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17966         else
17967             DisplayBlackClock(activePartnerTime, TRUE);
17968         partnerUp = 0;
17969     }
17970
17971     tickStartTM = now;
17972     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17973     StartClockTimer(intendedTickLength);
17974
17975     /* if the time remaining has fallen below the alarm threshold, sound the
17976      * alarm. if the alarm has sounded and (due to a takeback or time control
17977      * with increment) the time remaining has increased to a level above the
17978      * threshold, reset the alarm so it can sound again.
17979      */
17980
17981     if (appData.icsActive && appData.icsAlarm) {
17982
17983         /* make sure we are dealing with the user's clock */
17984         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17985                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17986            )) return;
17987
17988         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17989             alarmSounded = FALSE;
17990         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17991             PlayAlarmSound();
17992             alarmSounded = TRUE;
17993         }
17994     }
17995 }
17996
17997
17998 /* A player has just moved, so stop the previously running
17999    clock and (if in clock mode) start the other one.
18000    We redisplay both clocks in case we're in ICS mode, because
18001    ICS gives us an update to both clocks after every move.
18002    Note that this routine is called *after* forwardMostMove
18003    is updated, so the last fractional tick must be subtracted
18004    from the color that is *not* on move now.
18005 */
18006 void
18007 SwitchClocks (int newMoveNr)
18008 {
18009     long lastTickLength;
18010     TimeMark now;
18011     int flagged = FALSE;
18012
18013     GetTimeMark(&now);
18014
18015     if (StopClockTimer() && appData.clockMode) {
18016         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18017         if (!WhiteOnMove(forwardMostMove)) {
18018             if(blackNPS >= 0) lastTickLength = 0;
18019             blackTimeRemaining -= lastTickLength;
18020            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18021 //         if(pvInfoList[forwardMostMove].time == -1)
18022                  pvInfoList[forwardMostMove].time =               // use GUI time
18023                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18024         } else {
18025            if(whiteNPS >= 0) lastTickLength = 0;
18026            whiteTimeRemaining -= lastTickLength;
18027            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18028 //         if(pvInfoList[forwardMostMove].time == -1)
18029                  pvInfoList[forwardMostMove].time =
18030                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18031         }
18032         flagged = CheckFlags();
18033     }
18034     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18035     CheckTimeControl();
18036
18037     if (flagged || !appData.clockMode) return;
18038
18039     switch (gameMode) {
18040       case MachinePlaysBlack:
18041       case MachinePlaysWhite:
18042       case BeginningOfGame:
18043         if (pausing) return;
18044         break;
18045
18046       case EditGame:
18047       case PlayFromGameFile:
18048       case IcsExamining:
18049         return;
18050
18051       default:
18052         break;
18053     }
18054
18055     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18056         if(WhiteOnMove(forwardMostMove))
18057              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18058         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18059     }
18060
18061     tickStartTM = now;
18062     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18063       whiteTimeRemaining : blackTimeRemaining);
18064     StartClockTimer(intendedTickLength);
18065 }
18066
18067
18068 /* Stop both clocks */
18069 void
18070 StopClocks ()
18071 {
18072     long lastTickLength;
18073     TimeMark now;
18074
18075     if (!StopClockTimer()) return;
18076     if (!appData.clockMode) return;
18077
18078     GetTimeMark(&now);
18079
18080     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18081     if (WhiteOnMove(forwardMostMove)) {
18082         if(whiteNPS >= 0) lastTickLength = 0;
18083         whiteTimeRemaining -= lastTickLength;
18084         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18085     } else {
18086         if(blackNPS >= 0) lastTickLength = 0;
18087         blackTimeRemaining -= lastTickLength;
18088         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18089     }
18090     CheckFlags();
18091 }
18092
18093 /* Start clock of player on move.  Time may have been reset, so
18094    if clock is already running, stop and restart it. */
18095 void
18096 StartClocks ()
18097 {
18098     (void) StopClockTimer(); /* in case it was running already */
18099     DisplayBothClocks();
18100     if (CheckFlags()) return;
18101
18102     if (!appData.clockMode) return;
18103     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18104
18105     GetTimeMark(&tickStartTM);
18106     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18107       whiteTimeRemaining : blackTimeRemaining);
18108
18109    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18110     whiteNPS = blackNPS = -1;
18111     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18112        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18113         whiteNPS = first.nps;
18114     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18115        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18116         blackNPS = first.nps;
18117     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18118         whiteNPS = second.nps;
18119     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18120         blackNPS = second.nps;
18121     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18122
18123     StartClockTimer(intendedTickLength);
18124 }
18125
18126 char *
18127 TimeString (long ms)
18128 {
18129     long second, minute, hour, day;
18130     char *sign = "";
18131     static char buf[40], moveTime[8];
18132
18133     if (ms > 0 && ms <= 9900) {
18134       /* convert milliseconds to tenths, rounding up */
18135       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18136
18137       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18138       return buf;
18139     }
18140
18141     /* convert milliseconds to seconds, rounding up */
18142     /* use floating point to avoid strangeness of integer division
18143        with negative dividends on many machines */
18144     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18145
18146     if (second < 0) {
18147         sign = "-";
18148         second = -second;
18149     }
18150
18151     day = second / (60 * 60 * 24);
18152     second = second % (60 * 60 * 24);
18153     hour = second / (60 * 60);
18154     second = second % (60 * 60);
18155     minute = second / 60;
18156     second = second % 60;
18157
18158     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18159     else *moveTime = NULLCHAR;
18160
18161     if (day > 0)
18162       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18163               sign, day, hour, minute, second, moveTime);
18164     else if (hour > 0)
18165       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18166     else
18167       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18168
18169     return buf;
18170 }
18171
18172
18173 /*
18174  * This is necessary because some C libraries aren't ANSI C compliant yet.
18175  */
18176 char *
18177 StrStr (char *string, char *match)
18178 {
18179     int i, length;
18180
18181     length = strlen(match);
18182
18183     for (i = strlen(string) - length; i >= 0; i--, string++)
18184       if (!strncmp(match, string, length))
18185         return string;
18186
18187     return NULL;
18188 }
18189
18190 char *
18191 StrCaseStr (char *string, char *match)
18192 {
18193     int i, j, length;
18194
18195     length = strlen(match);
18196
18197     for (i = strlen(string) - length; i >= 0; i--, string++) {
18198         for (j = 0; j < length; j++) {
18199             if (ToLower(match[j]) != ToLower(string[j]))
18200               break;
18201         }
18202         if (j == length) return string;
18203     }
18204
18205     return NULL;
18206 }
18207
18208 #ifndef _amigados
18209 int
18210 StrCaseCmp (char *s1, char *s2)
18211 {
18212     char c1, c2;
18213
18214     for (;;) {
18215         c1 = ToLower(*s1++);
18216         c2 = ToLower(*s2++);
18217         if (c1 > c2) return 1;
18218         if (c1 < c2) return -1;
18219         if (c1 == NULLCHAR) return 0;
18220     }
18221 }
18222
18223
18224 int
18225 ToLower (int c)
18226 {
18227     return isupper(c) ? tolower(c) : c;
18228 }
18229
18230
18231 int
18232 ToUpper (int c)
18233 {
18234     return islower(c) ? toupper(c) : c;
18235 }
18236 #endif /* !_amigados    */
18237
18238 char *
18239 StrSave (char *s)
18240 {
18241   char *ret;
18242
18243   if ((ret = (char *) malloc(strlen(s) + 1)))
18244     {
18245       safeStrCpy(ret, s, strlen(s)+1);
18246     }
18247   return ret;
18248 }
18249
18250 char *
18251 StrSavePtr (char *s, char **savePtr)
18252 {
18253     if (*savePtr) {
18254         free(*savePtr);
18255     }
18256     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18257       safeStrCpy(*savePtr, s, strlen(s)+1);
18258     }
18259     return(*savePtr);
18260 }
18261
18262 char *
18263 PGNDate ()
18264 {
18265     time_t clock;
18266     struct tm *tm;
18267     char buf[MSG_SIZ];
18268
18269     clock = time((time_t *)NULL);
18270     tm = localtime(&clock);
18271     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18272             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18273     return StrSave(buf);
18274 }
18275
18276
18277 char *
18278 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18279 {
18280     int i, j, fromX, fromY, toX, toY;
18281     int whiteToPlay, haveRights = nrCastlingRights;
18282     char buf[MSG_SIZ];
18283     char *p, *q;
18284     int emptycount;
18285     ChessSquare piece;
18286
18287     whiteToPlay = (gameMode == EditPosition) ?
18288       !blackPlaysFirst : (move % 2 == 0);
18289     p = buf;
18290
18291     /* Piece placement data */
18292     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18293         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18294         emptycount = 0;
18295         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18296             if (boards[move][i][j] == EmptySquare) {
18297                 emptycount++;
18298             } else { ChessSquare piece = boards[move][i][j];
18299                 if (emptycount > 0) {
18300                     if(emptycount<10) /* [HGM] can be >= 10 */
18301                         *p++ = '0' + emptycount;
18302                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18303                     emptycount = 0;
18304                 }
18305                 if(PieceToChar(piece) == '+') {
18306                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18307                     *p++ = '+';
18308                     piece = (ChessSquare)(CHUDEMOTED(piece));
18309                 }
18310                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18311                 if(*p = PieceSuffix(piece)) p++;
18312                 if(p[-1] == '~') {
18313                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18314                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18315                     *p++ = '~';
18316                 }
18317             }
18318         }
18319         if (emptycount > 0) {
18320             if(emptycount<10) /* [HGM] can be >= 10 */
18321                 *p++ = '0' + emptycount;
18322             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18323             emptycount = 0;
18324         }
18325         *p++ = '/';
18326     }
18327     *(p - 1) = ' ';
18328
18329     /* [HGM] print Crazyhouse or Shogi holdings */
18330     if( gameInfo.holdingsWidth ) {
18331         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18332         q = p;
18333         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18334             piece = boards[move][i][BOARD_WIDTH-1];
18335             if( piece != EmptySquare )
18336               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18337                   *p++ = PieceToChar(piece);
18338         }
18339         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18340             piece = boards[move][BOARD_HEIGHT-i-1][0];
18341             if( piece != EmptySquare )
18342               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18343                   *p++ = PieceToChar(piece);
18344         }
18345
18346         if( q == p ) *p++ = '-';
18347         *p++ = ']';
18348         *p++ = ' ';
18349     }
18350
18351     /* Active color */
18352     *p++ = whiteToPlay ? 'w' : 'b';
18353     *p++ = ' ';
18354
18355   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18356     haveRights = 0; q = p;
18357     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18358       piece = boards[move][0][i];
18359       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18360         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18361       }
18362     }
18363     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18364       piece = boards[move][BOARD_HEIGHT-1][i];
18365       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18366         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18367       }
18368     }
18369     if(p == q) *p++ = '-';
18370     *p++ = ' ';
18371   }
18372
18373   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18374     while(*p++ = *q++)
18375                       ;
18376     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18377   } else {
18378   if(haveRights) {
18379      int handW=0, handB=0;
18380      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18381         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18382         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18383      }
18384      q = p;
18385      if(appData.fischerCastling) {
18386         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18387            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18388                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18389         } else {
18390        /* [HGM] write directly from rights */
18391            if(boards[move][CASTLING][2] != NoRights &&
18392               boards[move][CASTLING][0] != NoRights   )
18393                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18394            if(boards[move][CASTLING][2] != NoRights &&
18395               boards[move][CASTLING][1] != NoRights   )
18396                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18397         }
18398         if(handB) {
18399            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18400                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18401         } else {
18402            if(boards[move][CASTLING][5] != NoRights &&
18403               boards[move][CASTLING][3] != NoRights   )
18404                 *p++ = boards[move][CASTLING][3] + AAA;
18405            if(boards[move][CASTLING][5] != NoRights &&
18406               boards[move][CASTLING][4] != NoRights   )
18407                 *p++ = boards[move][CASTLING][4] + AAA;
18408         }
18409      } else {
18410
18411         /* [HGM] write true castling rights */
18412         if( nrCastlingRights == 6 ) {
18413             int q, k=0;
18414             if(boards[move][CASTLING][0] != NoRights &&
18415                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18416             q = (boards[move][CASTLING][1] != NoRights &&
18417                  boards[move][CASTLING][2] != NoRights  );
18418             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18419                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18420                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18421                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18422             }
18423             if(q) *p++ = 'Q';
18424             k = 0;
18425             if(boards[move][CASTLING][3] != NoRights &&
18426                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18427             q = (boards[move][CASTLING][4] != NoRights &&
18428                  boards[move][CASTLING][5] != NoRights  );
18429             if(handB) {
18430                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18431                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18432                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18433             }
18434             if(q) *p++ = 'q';
18435         }
18436      }
18437      if (q == p) *p++ = '-'; /* No castling rights */
18438      *p++ = ' ';
18439   }
18440
18441   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18442      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18443      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18444     /* En passant target square */
18445     if (move > backwardMostMove) {
18446         fromX = moveList[move - 1][0] - AAA;
18447         fromY = moveList[move - 1][1] - ONE;
18448         toX = moveList[move - 1][2] - AAA;
18449         toY = moveList[move - 1][3] - ONE;
18450         if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18451             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18452             /* 2-square pawn move just happened */
18453             *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18454             *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18455             if(gameInfo.variant == VariantBerolina) {
18456                 *p++ = toX + AAA;
18457                 *p++ = toY + ONE;
18458             }
18459         } else {
18460             *p++ = '-';
18461         }
18462     } else if(move == backwardMostMove) {
18463         // [HGM] perhaps we should always do it like this, and forget the above?
18464         if((signed char)boards[move][EP_STATUS] >= 0) {
18465             *p++ = boards[move][EP_STATUS] + AAA;
18466             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18467         } else {
18468             *p++ = '-';
18469         }
18470     } else {
18471         *p++ = '-';
18472     }
18473     *p++ = ' ';
18474   }
18475   }
18476
18477     i = boards[move][CHECK_COUNT];
18478     if(i) {
18479         sprintf(p, "%d+%d ", i&255, i>>8);
18480         while(*p) p++;
18481     }
18482
18483     if(moveCounts)
18484     {   int i = 0, j=move;
18485
18486         /* [HGM] find reversible plies */
18487         if (appData.debugMode) { int k;
18488             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18489             for(k=backwardMostMove; k<=forwardMostMove; k++)
18490                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18491
18492         }
18493
18494         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18495         if( j == backwardMostMove ) i += initialRulePlies;
18496         sprintf(p, "%d ", i);
18497         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18498
18499         /* Fullmove number */
18500         sprintf(p, "%d", (move / 2) + 1);
18501     } else *--p = NULLCHAR;
18502
18503     return StrSave(buf);
18504 }
18505
18506 Boolean
18507 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18508 {
18509     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18510     char *p, c;
18511     int emptycount, virgin[BOARD_FILES];
18512     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18513
18514     p = fen;
18515
18516     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18517
18518     /* Piece placement data */
18519     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18520         j = 0;
18521         for (;;) {
18522             if (*p == '/' || *p == ' ' || *p == '[' ) {
18523                 if(j > w) w = j;
18524                 emptycount = gameInfo.boardWidth - j;
18525                 while (emptycount--)
18526                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18527                 if (*p == '/') p++;
18528                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18529                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18530                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18531                     }
18532                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18533                 }
18534                 break;
18535 #if(BOARD_FILES >= 10)*0
18536             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18537                 p++; emptycount=10;
18538                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18539                 while (emptycount--)
18540                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18541 #endif
18542             } else if (*p == '*') {
18543                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18544             } else if (isdigit(*p)) {
18545                 emptycount = *p++ - '0';
18546                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18547                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18548                 while (emptycount--)
18549                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18550             } else if (*p == '<') {
18551                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18552                 else if (i != 0 || !shuffle) return FALSE;
18553                 p++;
18554             } else if (shuffle && *p == '>') {
18555                 p++; // for now ignore closing shuffle range, and assume rank-end
18556             } else if (*p == '?') {
18557                 if (j >= gameInfo.boardWidth) return FALSE;
18558                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18559                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18560             } else if (*p == '+' || isalpha(*p)) {
18561                 char *q, *s = SUFFIXES;
18562                 if (j >= gameInfo.boardWidth) return FALSE;
18563                 if(*p=='+') {
18564                     char c = *++p;
18565                     if(q = strchr(s, p[1])) p++;
18566                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18567                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18568                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18569                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18570                 } else {
18571                     char c = *p++;
18572                     if(q = strchr(s, *p)) p++;
18573                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18574                 }
18575
18576                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18577                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18578                     piece = (ChessSquare) (PROMOTED(piece));
18579                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18580                     p++;
18581                 }
18582                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18583                 if(piece == king) wKingRank = i;
18584                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18585             } else {
18586                 return FALSE;
18587             }
18588         }
18589     }
18590     while (*p == '/' || *p == ' ') p++;
18591
18592     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18593
18594     /* [HGM] by default clear Crazyhouse holdings, if present */
18595     if(gameInfo.holdingsWidth) {
18596        for(i=0; i<BOARD_HEIGHT; i++) {
18597            board[i][0]             = EmptySquare; /* black holdings */
18598            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18599            board[i][1]             = (ChessSquare) 0; /* black counts */
18600            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18601        }
18602     }
18603
18604     /* [HGM] look for Crazyhouse holdings here */
18605     while(*p==' ') p++;
18606     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18607         int swap=0, wcnt=0, bcnt=0;
18608         if(*p == '[') p++;
18609         if(*p == '<') swap++, p++;
18610         if(*p == '-' ) p++; /* empty holdings */ else {
18611             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18612             /* if we would allow FEN reading to set board size, we would   */
18613             /* have to add holdings and shift the board read so far here   */
18614             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18615                 p++;
18616                 if((int) piece >= (int) BlackPawn ) {
18617                     i = (int)piece - (int)BlackPawn;
18618                     i = PieceToNumber((ChessSquare)i);
18619                     if( i >= gameInfo.holdingsSize ) return FALSE;
18620                     board[handSize-1-i][0] = piece; /* black holdings */
18621                     board[handSize-1-i][1]++;       /* black counts   */
18622                     bcnt++;
18623                 } else {
18624                     i = (int)piece - (int)WhitePawn;
18625                     i = PieceToNumber((ChessSquare)i);
18626                     if( i >= gameInfo.holdingsSize ) return FALSE;
18627                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18628                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18629                     wcnt++;
18630                 }
18631             }
18632             if(subst) { // substitute back-rank question marks by holdings pieces
18633                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18634                     int k, m, n = bcnt + 1;
18635                     if(board[0][j] == ClearBoard) {
18636                         if(!wcnt) return FALSE;
18637                         n = rand() % wcnt;
18638                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18639                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18640                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18641                             break;
18642                         }
18643                     }
18644                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18645                         if(!bcnt) return FALSE;
18646                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18647                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18648                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18649                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18650                             break;
18651                         }
18652                     }
18653                 }
18654                 subst = 0;
18655             }
18656         }
18657         if(*p == ']') p++;
18658     }
18659
18660     if(subst) return FALSE; // substitution requested, but no holdings
18661
18662     while(*p == ' ') p++;
18663
18664     /* Active color */
18665     c = *p++;
18666     if(appData.colorNickNames) {
18667       if( c == appData.colorNickNames[0] ) c = 'w'; else
18668       if( c == appData.colorNickNames[1] ) c = 'b';
18669     }
18670     switch (c) {
18671       case 'w':
18672         *blackPlaysFirst = FALSE;
18673         break;
18674       case 'b':
18675         *blackPlaysFirst = TRUE;
18676         break;
18677       default:
18678         return FALSE;
18679     }
18680
18681     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18682     /* return the extra info in global variiables             */
18683
18684     while(*p==' ') p++;
18685
18686     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18687         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18688         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18689     }
18690
18691     /* set defaults in case FEN is incomplete */
18692     board[EP_STATUS] = EP_UNKNOWN;
18693     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18694     for(i=0; i<nrCastlingRights; i++ ) {
18695         board[CASTLING][i] =
18696             appData.fischerCastling ? NoRights : initialRights[i];
18697     }   /* assume possible unless obviously impossible */
18698     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18699     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18700     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18701                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18702     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18703     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18704     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18705                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18706     FENrulePlies = 0;
18707
18708     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18709       char *q = p;
18710       int w=0, b=0;
18711       while(isalpha(*p)) {
18712         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18713         if(islower(*p)) b |= 1 << (*p++ - 'a');
18714       }
18715       if(*p == '-') p++;
18716       if(p != q) {
18717         board[TOUCHED_W] = ~w;
18718         board[TOUCHED_B] = ~b;
18719         while(*p == ' ') p++;
18720       }
18721     } else
18722
18723     if(nrCastlingRights) {
18724       int fischer = 0;
18725       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18726       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18727           /* castling indicator present, so default becomes no castlings */
18728           for(i=0; i<nrCastlingRights; i++ ) {
18729                  board[CASTLING][i] = NoRights;
18730           }
18731       }
18732       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18733              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18734              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18735              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18736         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18737
18738         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18739             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18740             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18741         }
18742         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18743             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18744         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18745                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18746         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18747                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18748         switch(c) {
18749           case'K':
18750               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18751               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18752               board[CASTLING][2] = whiteKingFile;
18753               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18754               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18755               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18756               break;
18757           case'Q':
18758               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18759               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18760               board[CASTLING][2] = whiteKingFile;
18761               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18762               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18763               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18764               break;
18765           case'k':
18766               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18767               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18768               board[CASTLING][5] = blackKingFile;
18769               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18770               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18771               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18772               break;
18773           case'q':
18774               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18775               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18776               board[CASTLING][5] = blackKingFile;
18777               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18778               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18779               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18780           case '-':
18781               break;
18782           default: /* FRC castlings */
18783               if(c >= 'a') { /* black rights */
18784                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18785                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18786                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18787                   if(i == BOARD_RGHT) break;
18788                   board[CASTLING][5] = i;
18789                   c -= AAA;
18790                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18791                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18792                   if(c > i)
18793                       board[CASTLING][3] = c;
18794                   else
18795                       board[CASTLING][4] = c;
18796               } else { /* white rights */
18797                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18798                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18799                     if(board[0][i] == WhiteKing) break;
18800                   if(i == BOARD_RGHT) break;
18801                   board[CASTLING][2] = i;
18802                   c -= AAA - 'a' + 'A';
18803                   if(board[0][c] >= WhiteKing) break;
18804                   if(c > i)
18805                       board[CASTLING][0] = c;
18806                   else
18807                       board[CASTLING][1] = c;
18808               }
18809         }
18810       }
18811       for(i=0; i<nrCastlingRights; i++)
18812         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18813       if(gameInfo.variant == VariantSChess)
18814         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18815       if(fischer && shuffle) appData.fischerCastling = TRUE;
18816     if (appData.debugMode) {
18817         fprintf(debugFP, "FEN castling rights:");
18818         for(i=0; i<nrCastlingRights; i++)
18819         fprintf(debugFP, " %d", board[CASTLING][i]);
18820         fprintf(debugFP, "\n");
18821     }
18822
18823       while(*p==' ') p++;
18824     }
18825
18826     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18827
18828     /* read e.p. field in games that know e.p. capture */
18829     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18830        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18831        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18832       if(*p=='-') {
18833         p++; board[EP_STATUS] = EP_NONE;
18834       } else {
18835          int d, r, c = *p - AAA;
18836
18837          if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18838              p++;
18839              board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18840              if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18841              d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18842              if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18843              board[LAST_TO] = 256*(r + d) + c;
18844              c = *p++ - AAA;
18845              if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18846                  if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18847                  board[LAST_TO] = 256*r + c;
18848                  if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18849              }
18850          }
18851       }
18852     }
18853
18854     while(*p == ' ') p++;
18855
18856     board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18857     if(sscanf(p, "%d+%d", &i, &j) == 2) {
18858         board[CHECK_COUNT] = i + 256*j;
18859         while(*p && *p != ' ') p++;
18860     }
18861
18862     c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18863     if(c > 0) {
18864         FENrulePlies = i; /* 50-move ply counter */
18865         /* (The move number is still ignored)    */
18866         if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18867     }
18868
18869     return TRUE;
18870 }
18871
18872 void
18873 EditPositionPasteFEN (char *fen)
18874 {
18875   if (fen != NULL) {
18876     Board initial_position;
18877
18878     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18879       DisplayError(_("Bad FEN position in clipboard"), 0);
18880       return ;
18881     } else {
18882       int savedBlackPlaysFirst = blackPlaysFirst;
18883       EditPositionEvent();
18884       blackPlaysFirst = savedBlackPlaysFirst;
18885       CopyBoard(boards[0], initial_position);
18886       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18887       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18888       DisplayBothClocks();
18889       DrawPosition(FALSE, boards[currentMove]);
18890     }
18891   }
18892 }
18893
18894 static char cseq[12] = "\\   ";
18895
18896 Boolean
18897 set_cont_sequence (char *new_seq)
18898 {
18899     int len;
18900     Boolean ret;
18901
18902     // handle bad attempts to set the sequence
18903         if (!new_seq)
18904                 return 0; // acceptable error - no debug
18905
18906     len = strlen(new_seq);
18907     ret = (len > 0) && (len < sizeof(cseq));
18908     if (ret)
18909       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18910     else if (appData.debugMode)
18911       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18912     return ret;
18913 }
18914
18915 /*
18916     reformat a source message so words don't cross the width boundary.  internal
18917     newlines are not removed.  returns the wrapped size (no null character unless
18918     included in source message).  If dest is NULL, only calculate the size required
18919     for the dest buffer.  lp argument indicats line position upon entry, and it's
18920     passed back upon exit.
18921 */
18922 int
18923 wrap (char *dest, char *src, int count, int width, int *lp)
18924 {
18925     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18926
18927     cseq_len = strlen(cseq);
18928     old_line = line = *lp;
18929     ansi = len = clen = 0;
18930
18931     for (i=0; i < count; i++)
18932     {
18933         if (src[i] == '\033')
18934             ansi = 1;
18935
18936         // if we hit the width, back up
18937         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18938         {
18939             // store i & len in case the word is too long
18940             old_i = i, old_len = len;
18941
18942             // find the end of the last word
18943             while (i && src[i] != ' ' && src[i] != '\n')
18944             {
18945                 i--;
18946                 len--;
18947             }
18948
18949             // word too long?  restore i & len before splitting it
18950             if ((old_i-i+clen) >= width)
18951             {
18952                 i = old_i;
18953                 len = old_len;
18954             }
18955
18956             // extra space?
18957             if (i && src[i-1] == ' ')
18958                 len--;
18959
18960             if (src[i] != ' ' && src[i] != '\n')
18961             {
18962                 i--;
18963                 if (len)
18964                     len--;
18965             }
18966
18967             // now append the newline and continuation sequence
18968             if (dest)
18969                 dest[len] = '\n';
18970             len++;
18971             if (dest)
18972                 strncpy(dest+len, cseq, cseq_len);
18973             len += cseq_len;
18974             line = cseq_len;
18975             clen = cseq_len;
18976             continue;
18977         }
18978
18979         if (dest)
18980             dest[len] = src[i];
18981         len++;
18982         if (!ansi)
18983             line++;
18984         if (src[i] == '\n')
18985             line = 0;
18986         if (src[i] == 'm')
18987             ansi = 0;
18988     }
18989     if (dest && appData.debugMode)
18990     {
18991         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18992             count, width, line, len, *lp);
18993         show_bytes(debugFP, src, count);
18994         fprintf(debugFP, "\ndest: ");
18995         show_bytes(debugFP, dest, len);
18996         fprintf(debugFP, "\n");
18997     }
18998     *lp = dest ? line : old_line;
18999
19000     return len;
19001 }
19002
19003 // [HGM] vari: routines for shelving variations
19004 Boolean modeRestore = FALSE;
19005
19006 void
19007 PushInner (int firstMove, int lastMove)
19008 {
19009         int i, j, nrMoves = lastMove - firstMove;
19010
19011         // push current tail of game on stack
19012         savedResult[storedGames] = gameInfo.result;
19013         savedDetails[storedGames] = gameInfo.resultDetails;
19014         gameInfo.resultDetails = NULL;
19015         savedFirst[storedGames] = firstMove;
19016         savedLast [storedGames] = lastMove;
19017         savedFramePtr[storedGames] = framePtr;
19018         framePtr -= nrMoves; // reserve space for the boards
19019         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19020             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19021             for(j=0; j<MOVE_LEN; j++)
19022                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19023             for(j=0; j<2*MOVE_LEN; j++)
19024                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19025             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19026             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19027             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19028             pvInfoList[firstMove+i-1].depth = 0;
19029             commentList[framePtr+i] = commentList[firstMove+i];
19030             commentList[firstMove+i] = NULL;
19031         }
19032
19033         storedGames++;
19034         forwardMostMove = firstMove; // truncate game so we can start variation
19035 }
19036
19037 void
19038 PushTail (int firstMove, int lastMove)
19039 {
19040         if(appData.icsActive) { // only in local mode
19041                 forwardMostMove = currentMove; // mimic old ICS behavior
19042                 return;
19043         }
19044         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19045
19046         PushInner(firstMove, lastMove);
19047         if(storedGames == 1) GreyRevert(FALSE);
19048         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19049 }
19050
19051 void
19052 PopInner (Boolean annotate)
19053 {
19054         int i, j, nrMoves;
19055         char buf[8000], moveBuf[20];
19056
19057         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19058         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19059         nrMoves = savedLast[storedGames] - currentMove;
19060         if(annotate) {
19061                 int cnt = 10;
19062                 if(!WhiteOnMove(currentMove))
19063                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19064                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19065                 for(i=currentMove; i<forwardMostMove; i++) {
19066                         if(WhiteOnMove(i))
19067                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19068                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19069                         strcat(buf, moveBuf);
19070                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19071                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19072                 }
19073                 strcat(buf, ")");
19074         }
19075         for(i=1; i<=nrMoves; i++) { // copy last variation back
19076             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19077             for(j=0; j<MOVE_LEN; j++)
19078                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19079             for(j=0; j<2*MOVE_LEN; j++)
19080                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19081             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19082             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19083             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19084             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19085             commentList[currentMove+i] = commentList[framePtr+i];
19086             commentList[framePtr+i] = NULL;
19087         }
19088         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19089         framePtr = savedFramePtr[storedGames];
19090         gameInfo.result = savedResult[storedGames];
19091         if(gameInfo.resultDetails != NULL) {
19092             free(gameInfo.resultDetails);
19093       }
19094         gameInfo.resultDetails = savedDetails[storedGames];
19095         forwardMostMove = currentMove + nrMoves;
19096 }
19097
19098 Boolean
19099 PopTail (Boolean annotate)
19100 {
19101         if(appData.icsActive) return FALSE; // only in local mode
19102         if(!storedGames) return FALSE; // sanity
19103         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19104
19105         PopInner(annotate);
19106         if(currentMove < forwardMostMove) ForwardEvent(); else
19107         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19108
19109         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19110         return TRUE;
19111 }
19112
19113 void
19114 CleanupTail ()
19115 {       // remove all shelved variations
19116         int i;
19117         for(i=0; i<storedGames; i++) {
19118             if(savedDetails[i])
19119                 free(savedDetails[i]);
19120             savedDetails[i] = NULL;
19121         }
19122         for(i=framePtr; i<MAX_MOVES; i++) {
19123                 if(commentList[i]) free(commentList[i]);
19124                 commentList[i] = NULL;
19125         }
19126         framePtr = MAX_MOVES-1;
19127         storedGames = 0;
19128 }
19129
19130 void
19131 LoadVariation (int index, char *text)
19132 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19133         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19134         int level = 0, move;
19135
19136         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19137         // first find outermost bracketing variation
19138         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19139             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19140                 if(*p == '{') wait = '}'; else
19141                 if(*p == '[') wait = ']'; else
19142                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19143                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19144             }
19145             if(*p == wait) wait = NULLCHAR; // closing ]} found
19146             p++;
19147         }
19148         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19149         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19150         end[1] = NULLCHAR; // clip off comment beyond variation
19151         ToNrEvent(currentMove-1);
19152         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19153         // kludge: use ParsePV() to append variation to game
19154         move = currentMove;
19155         ParsePV(start, TRUE, TRUE);
19156         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19157         ClearPremoveHighlights();
19158         CommentPopDown();
19159         ToNrEvent(currentMove+1);
19160 }
19161
19162 int transparency[2];
19163
19164 void
19165 LoadTheme ()
19166 {
19167 #define BUF_SIZ (2*MSG_SIZ)
19168     char *p, *q, buf[BUF_SIZ];
19169     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19170         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19171         ParseArgsFromString(buf);
19172         ActivateTheme(TRUE); // also redo colors
19173         return;
19174     }
19175     p = nickName;
19176     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19177     {
19178         int len;
19179         q = appData.themeNames;
19180         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19181       if(appData.useBitmaps) {
19182         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19183                 Shorten(appData.liteBackTextureFile));
19184         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19185                 Shorten(appData.darkBackTextureFile),
19186                 appData.liteBackTextureMode,
19187                 appData.darkBackTextureMode );
19188       } else {
19189         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19190       }
19191       if(!appData.useBitmaps || transparency[0]) {
19192         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19193       }
19194       if(!appData.useBitmaps || transparency[1]) {
19195         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19196       }
19197       if(appData.useBorder) {
19198         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19199                 appData.border);
19200       } else {
19201         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19202       }
19203       if(appData.useFont) {
19204         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19205                 appData.renderPiecesWithFont,
19206                 appData.fontToPieceTable,
19207                 Col2Text(9),    // appData.fontBackColorWhite
19208                 Col2Text(10) ); // appData.fontForeColorBlack
19209       } else {
19210         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19211         if(appData.pieceDirectory[0]) {
19212           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19213           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19214             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19215         }
19216         if(!appData.pieceDirectory[0] || !appData.trueColors)
19217           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19218                 Col2Text(0),   // whitePieceColor
19219                 Col2Text(1) ); // blackPieceColor
19220       }
19221       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19222                 Col2Text(4),   // highlightSquareColor
19223                 Col2Text(5) ); // premoveHighlightColor
19224         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19225         if(insert != q) insert[-1] = NULLCHAR;
19226         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19227         if(q)   free(q);
19228     }
19229     ActivateTheme(FALSE);
19230 }