Let repeated right-clicking upgrade placed piece in Edit Position mode
[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, handOffsets;
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(new == EmptySquare || 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) // 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     if(!handSize) 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) return -1;
6827   if(x == BOARD_RGHT+1 && handOffsets & 1) y += handSize - BOARD_HEIGHT;
6828   if(x == BOARD_LEFT-2 && !(handOffsets & 2)) y += handSize - BOARD_HEIGHT;
6829      return boards[currentMove][y][x];
6830 }
6831
6832 ChessSquare
6833 More (Board board, int col, int start, int end)
6834 {
6835     int k;
6836     for(k=start; k<end; k++) if(board[k][col]) return (col == 1 ? WhiteMonarch : BlackMonarch); // arrow image
6837     return EmptySquare;
6838 }
6839
6840 void
6841 DrawPosition (int repaint, Board board)
6842 {
6843     Board compactedBoard;
6844     if(handSize > BOARD_HEIGHT && board) {
6845         int k;
6846         CopyBoard(compactedBoard, board);
6847         if(handOffsets & 1) {
6848             for(k=0; k<BOARD_HEIGHT; k++) {
6849                 compactedBoard[k][BOARD_WIDTH-1] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-1];
6850                 compactedBoard[k][BOARD_WIDTH-2] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-2];
6851             }
6852             compactedBoard[0][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, 0, handSize-BOARD_HEIGHT+1);
6853         } else compactedBoard[BOARD_HEIGHT-1][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, BOARD_HEIGHT-1, handSize);
6854         if(!(handOffsets & 2)) {
6855             for(k=0; k<BOARD_HEIGHT; k++) {
6856                 compactedBoard[k][0] = board[k+handSize-BOARD_HEIGHT][0];
6857                 compactedBoard[k][1] = board[k+handSize-BOARD_HEIGHT][1];
6858             }
6859             compactedBoard[0][0] = More(board, 1, 0, handSize-BOARD_HEIGHT+1);
6860         } else compactedBoard[BOARD_HEIGHT-1][0] = More(board, 1, BOARD_HEIGHT-1, handSize);
6861         DrawPositionX(TRUE, compactedBoard);
6862     } else DrawPositionX(repaint, board);
6863 }
6864
6865 int
6866 OKToStartUserMove (int x, int y)
6867 {
6868     ChessSquare from_piece;
6869     int white_piece;
6870
6871     if (matchMode) return FALSE;
6872     if (gameMode == EditPosition) return TRUE;
6873
6874     if (x >= 0 && y >= 0)
6875       from_piece = boards[currentMove][y][x];
6876     else
6877       from_piece = EmptySquare;
6878
6879     if (from_piece == EmptySquare) return FALSE;
6880
6881     white_piece = (int)from_piece >= (int)WhitePawn &&
6882       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6883
6884     switch (gameMode) {
6885       case AnalyzeFile:
6886       case TwoMachinesPlay:
6887       case EndOfGame:
6888         return FALSE;
6889
6890       case IcsObserving:
6891       case IcsIdle:
6892         return FALSE;
6893
6894       case MachinePlaysWhite:
6895       case IcsPlayingBlack:
6896         if (appData.zippyPlay) return FALSE;
6897         if (white_piece) {
6898             DisplayMoveError(_("You are playing Black"));
6899             return FALSE;
6900         }
6901         break;
6902
6903       case MachinePlaysBlack:
6904       case IcsPlayingWhite:
6905         if (appData.zippyPlay) return FALSE;
6906         if (!white_piece) {
6907             DisplayMoveError(_("You are playing White"));
6908             return FALSE;
6909         }
6910         break;
6911
6912       case PlayFromGameFile:
6913             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6914       case EditGame:
6915       case AnalyzeMode:
6916         if (!white_piece && WhiteOnMove(currentMove)) {
6917             DisplayMoveError(_("It is White's turn"));
6918             return FALSE;
6919         }
6920         if (white_piece && !WhiteOnMove(currentMove)) {
6921             DisplayMoveError(_("It is Black's turn"));
6922             return FALSE;
6923         }
6924         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6925             /* Editing correspondence game history */
6926             /* Could disallow this or prompt for confirmation */
6927             cmailOldMove = -1;
6928         }
6929         break;
6930
6931       case BeginningOfGame:
6932         if (appData.icsActive) return FALSE;
6933         if (!appData.noChessProgram) {
6934             if (!white_piece) {
6935                 DisplayMoveError(_("You are playing White"));
6936                 return FALSE;
6937             }
6938         }
6939         break;
6940
6941       case Training:
6942         if (!white_piece && WhiteOnMove(currentMove)) {
6943             DisplayMoveError(_("It is White's turn"));
6944             return FALSE;
6945         }
6946         if (white_piece && !WhiteOnMove(currentMove)) {
6947             DisplayMoveError(_("It is Black's turn"));
6948             return FALSE;
6949         }
6950         break;
6951
6952       default:
6953       case IcsExamining:
6954         break;
6955     }
6956     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6957         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6958         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6959         && gameMode != AnalyzeFile && gameMode != Training) {
6960         DisplayMoveError(_("Displayed position is not current"));
6961         return FALSE;
6962     }
6963     return TRUE;
6964 }
6965
6966 Boolean
6967 OnlyMove (int *x, int *y, Boolean captures)
6968 {
6969     DisambiguateClosure cl;
6970     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6971     switch(gameMode) {
6972       case MachinePlaysBlack:
6973       case IcsPlayingWhite:
6974       case BeginningOfGame:
6975         if(!WhiteOnMove(currentMove)) return FALSE;
6976         break;
6977       case MachinePlaysWhite:
6978       case IcsPlayingBlack:
6979         if(WhiteOnMove(currentMove)) return FALSE;
6980         break;
6981       case EditGame:
6982         break;
6983       default:
6984         return FALSE;
6985     }
6986     cl.pieceIn = EmptySquare;
6987     cl.rfIn = *y;
6988     cl.ffIn = *x;
6989     cl.rtIn = -1;
6990     cl.ftIn = -1;
6991     cl.promoCharIn = NULLCHAR;
6992     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6993     if( cl.kind == NormalMove ||
6994         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6995         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6996         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6997       fromX = cl.ff;
6998       fromY = cl.rf;
6999       *x = cl.ft;
7000       *y = cl.rt;
7001       return TRUE;
7002     }
7003     if(cl.kind != ImpossibleMove) return FALSE;
7004     cl.pieceIn = EmptySquare;
7005     cl.rfIn = -1;
7006     cl.ffIn = -1;
7007     cl.rtIn = *y;
7008     cl.ftIn = *x;
7009     cl.promoCharIn = NULLCHAR;
7010     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7011     if( cl.kind == NormalMove ||
7012         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7013         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7014         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7015       fromX = cl.ff;
7016       fromY = cl.rf;
7017       *x = cl.ft;
7018       *y = cl.rt;
7019       autoQueen = TRUE; // act as if autoQueen on when we click to-square
7020       return TRUE;
7021     }
7022     return FALSE;
7023 }
7024
7025 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
7026 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
7027 int lastLoadGameUseList = FALSE;
7028 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
7029 ChessMove lastLoadGameStart = EndOfFile;
7030 int doubleClick;
7031 Boolean addToBookFlag;
7032 static Board rightsBoard, nullBoard;
7033
7034 void
7035 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7036 {
7037     ChessMove moveType;
7038     ChessSquare pup;
7039     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7040
7041     /* Check if the user is playing in turn.  This is complicated because we
7042        let the user "pick up" a piece before it is his turn.  So the piece he
7043        tried to pick up may have been captured by the time he puts it down!
7044        Therefore we use the color the user is supposed to be playing in this
7045        test, not the color of the piece that is currently on the starting
7046        square---except in EditGame mode, where the user is playing both
7047        sides; fortunately there the capture race can't happen.  (It can
7048        now happen in IcsExamining mode, but that's just too bad.  The user
7049        will get a somewhat confusing message in that case.)
7050        */
7051
7052     switch (gameMode) {
7053       case AnalyzeFile:
7054       case TwoMachinesPlay:
7055       case EndOfGame:
7056       case IcsObserving:
7057       case IcsIdle:
7058         /* We switched into a game mode where moves are not accepted,
7059            perhaps while the mouse button was down. */
7060         return;
7061
7062       case MachinePlaysWhite:
7063         /* User is moving for Black */
7064         if (WhiteOnMove(currentMove)) {
7065             DisplayMoveError(_("It is White's turn"));
7066             return;
7067         }
7068         break;
7069
7070       case MachinePlaysBlack:
7071         /* User is moving for White */
7072         if (!WhiteOnMove(currentMove)) {
7073             DisplayMoveError(_("It is Black's turn"));
7074             return;
7075         }
7076         break;
7077
7078       case PlayFromGameFile:
7079             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7080       case EditGame:
7081       case IcsExamining:
7082       case BeginningOfGame:
7083       case AnalyzeMode:
7084       case Training:
7085         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7086         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7087             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7088             /* User is moving for Black */
7089             if (WhiteOnMove(currentMove)) {
7090                 DisplayMoveError(_("It is White's turn"));
7091                 return;
7092             }
7093         } else {
7094             /* User is moving for White */
7095             if (!WhiteOnMove(currentMove)) {
7096                 DisplayMoveError(_("It is Black's turn"));
7097                 return;
7098             }
7099         }
7100         break;
7101
7102       case IcsPlayingBlack:
7103         /* User is moving for Black */
7104         if (WhiteOnMove(currentMove)) {
7105             if (!appData.premove) {
7106                 DisplayMoveError(_("It is White's turn"));
7107             } else if (toX >= 0 && toY >= 0) {
7108                 premoveToX = toX;
7109                 premoveToY = toY;
7110                 premoveFromX = fromX;
7111                 premoveFromY = fromY;
7112                 premovePromoChar = promoChar;
7113                 gotPremove = 1;
7114                 if (appData.debugMode)
7115                     fprintf(debugFP, "Got premove: fromX %d,"
7116                             "fromY %d, toX %d, toY %d\n",
7117                             fromX, fromY, toX, toY);
7118             }
7119             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7120             return;
7121         }
7122         break;
7123
7124       case IcsPlayingWhite:
7125         /* User is moving for White */
7126         if (!WhiteOnMove(currentMove)) {
7127             if (!appData.premove) {
7128                 DisplayMoveError(_("It is Black's turn"));
7129             } else if (toX >= 0 && toY >= 0) {
7130                 premoveToX = toX;
7131                 premoveToY = toY;
7132                 premoveFromX = fromX;
7133                 premoveFromY = fromY;
7134                 premovePromoChar = promoChar;
7135                 gotPremove = 1;
7136                 if (appData.debugMode)
7137                     fprintf(debugFP, "Got premove: fromX %d,"
7138                             "fromY %d, toX %d, toY %d\n",
7139                             fromX, fromY, toX, toY);
7140             }
7141             DrawPosition(TRUE, boards[currentMove]);
7142             return;
7143         }
7144         break;
7145
7146       default:
7147         break;
7148
7149       case EditPosition:
7150         /* EditPosition, empty square, or different color piece;
7151            click-click move is possible */
7152         if (toX == -2 || toY == -2) {
7153             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7154             DrawPosition(FALSE, boards[currentMove]);
7155             return;
7156         } else if (toX >= 0 && toY >= 0) {
7157             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7158                 ChessSquare p = boards[0][rf][ff];
7159                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7160                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7161                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7162                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7163                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7164                     gatingPiece = p;
7165                 }
7166             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7167             boards[0][toY][toX] = boards[0][fromY][fromX];
7168             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7169                 if(boards[0][fromY][0] != EmptySquare) {
7170                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7171                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7172                 }
7173             } else
7174             if(fromX == BOARD_RGHT+1) {
7175                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7176                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7177                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7178                 }
7179             } else
7180             boards[0][fromY][fromX] = gatingPiece;
7181             ClearHighlights();
7182             DrawPosition(FALSE, boards[currentMove]);
7183             return;
7184         }
7185         return;
7186     }
7187
7188     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7189     pup = boards[currentMove][toY][toX];
7190
7191     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7192     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7193          if( pup != EmptySquare ) return;
7194          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7195            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7196                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7197            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7198            if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7199            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7200            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7201          fromY = DROP_RANK;
7202     }
7203
7204     /* [HGM] always test for legality, to get promotion info */
7205     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7206                                          fromY, fromX, toY, toX, promoChar);
7207
7208     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7209
7210     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7211
7212     /* [HGM] but possibly ignore an IllegalMove result */
7213     if (appData.testLegality) {
7214         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7215             DisplayMoveError(_("Illegal move"));
7216             return;
7217         }
7218     }
7219
7220     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7221         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7222              ClearPremoveHighlights(); // was included
7223         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7224         DrawPosition(FALSE, NULL);
7225         return;
7226     }
7227
7228     if(addToBookFlag) { // adding moves to book
7229         char buf[MSG_SIZ], move[MSG_SIZ];
7230         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7231         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7232                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7233         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7234         AddBookMove(buf);
7235         addToBookFlag = FALSE;
7236         ClearHighlights();
7237         return;
7238     }
7239
7240     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7241 }
7242
7243 /* Common tail of UserMoveEvent and DropMenuEvent */
7244 int
7245 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7246 {
7247     char *bookHit = 0;
7248
7249     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7250         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7251         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7252         if(WhiteOnMove(currentMove)) {
7253             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7254         } else {
7255             if(!boards[currentMove][handSize-1-k][1]) return 0;
7256         }
7257     }
7258
7259     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7260        move type in caller when we know the move is a legal promotion */
7261     if(moveType == NormalMove && promoChar)
7262         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7263
7264     /* [HGM] <popupFix> The following if has been moved here from
7265        UserMoveEvent(). Because it seemed to belong here (why not allow
7266        piece drops in training games?), and because it can only be
7267        performed after it is known to what we promote. */
7268     if (gameMode == Training) {
7269       /* compare the move played on the board to the next move in the
7270        * game. If they match, display the move and the opponent's response.
7271        * If they don't match, display an error message.
7272        */
7273       int saveAnimate;
7274       Board testBoard;
7275       CopyBoard(testBoard, boards[currentMove]);
7276       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7277
7278       if (CompareBoards(testBoard, boards[currentMove+1])) {
7279         ForwardInner(currentMove+1);
7280
7281         /* Autoplay the opponent's response.
7282          * if appData.animate was TRUE when Training mode was entered,
7283          * the response will be animated.
7284          */
7285         saveAnimate = appData.animate;
7286         appData.animate = animateTraining;
7287         ForwardInner(currentMove+1);
7288         appData.animate = saveAnimate;
7289
7290         /* check for the end of the game */
7291         if (currentMove >= forwardMostMove) {
7292           gameMode = PlayFromGameFile;
7293           ModeHighlight();
7294           SetTrainingModeOff();
7295           DisplayInformation(_("End of game"));
7296         }
7297       } else {
7298         DisplayError(_("Incorrect move"), 0);
7299       }
7300       return 1;
7301     }
7302
7303   /* Ok, now we know that the move is good, so we can kill
7304      the previous line in Analysis Mode */
7305   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7306                                 && currentMove < forwardMostMove) {
7307     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7308     else forwardMostMove = currentMove;
7309   }
7310
7311   ClearMap();
7312
7313   /* If we need the chess program but it's dead, restart it */
7314   ResurrectChessProgram();
7315
7316   /* A user move restarts a paused game*/
7317   if (pausing)
7318     PauseEvent();
7319
7320   thinkOutput[0] = NULLCHAR;
7321
7322   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7323
7324   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7325     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7326     return 1;
7327   }
7328
7329   if (gameMode == BeginningOfGame) {
7330     if (appData.noChessProgram) {
7331       gameMode = EditGame;
7332       SetGameInfo();
7333     } else {
7334       char buf[MSG_SIZ];
7335       gameMode = MachinePlaysBlack;
7336       StartClocks();
7337       SetGameInfo();
7338       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7339       DisplayTitle(buf);
7340       if (first.sendName) {
7341         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7342         SendToProgram(buf, &first);
7343       }
7344       StartClocks();
7345     }
7346     ModeHighlight();
7347   }
7348
7349   /* Relay move to ICS or chess engine */
7350   if (appData.icsActive) {
7351     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7352         gameMode == IcsExamining) {
7353       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7354         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7355         SendToICS("draw ");
7356         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7357       }
7358       // also send plain move, in case ICS does not understand atomic claims
7359       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7360       ics_user_moved = 1;
7361     }
7362   } else {
7363     if (first.sendTime && (gameMode == BeginningOfGame ||
7364                            gameMode == MachinePlaysWhite ||
7365                            gameMode == MachinePlaysBlack)) {
7366       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7367     }
7368     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7369          // [HGM] book: if program might be playing, let it use book
7370         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7371         first.maybeThinking = TRUE;
7372     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7373         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7374         SendBoard(&first, currentMove+1);
7375         if(second.analyzing) {
7376             if(!second.useSetboard) SendToProgram("undo\n", &second);
7377             SendBoard(&second, currentMove+1);
7378         }
7379     } else {
7380         SendMoveToProgram(forwardMostMove-1, &first);
7381         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7382     }
7383     if (currentMove == cmailOldMove + 1) {
7384       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7385     }
7386   }
7387
7388   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7389
7390   switch (gameMode) {
7391   case EditGame:
7392     if(appData.testLegality)
7393     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7394     case MT_NONE:
7395     case MT_CHECK:
7396       break;
7397     case MT_CHECKMATE:
7398     case MT_STAINMATE:
7399       if (WhiteOnMove(currentMove)) {
7400         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7401       } else {
7402         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7403       }
7404       break;
7405     case MT_STALEMATE:
7406       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7407       break;
7408     }
7409     break;
7410
7411   case MachinePlaysBlack:
7412   case MachinePlaysWhite:
7413     /* disable certain menu options while machine is thinking */
7414     SetMachineThinkingEnables();
7415     break;
7416
7417   default:
7418     break;
7419   }
7420
7421   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7422   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7423
7424   if(bookHit) { // [HGM] book: simulate book reply
7425         static char bookMove[MSG_SIZ]; // a bit generous?
7426
7427         programStats.nodes = programStats.depth = programStats.time =
7428         programStats.score = programStats.got_only_move = 0;
7429         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7430
7431         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7432         strcat(bookMove, bookHit);
7433         HandleMachineMove(bookMove, &first);
7434   }
7435   return 1;
7436 }
7437
7438 void
7439 MarkByFEN(char *fen)
7440 {
7441         int r, f;
7442         if(!appData.markers || !appData.highlightDragging) return;
7443         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7444         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7445         while(*fen) {
7446             int s = 0;
7447             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7448             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7449             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7450             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7451             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7452             if(*fen == 'T') marker[r][f++] = 0; else
7453             if(*fen == 'Y') marker[r][f++] = 1; else
7454             if(*fen == 'G') marker[r][f++] = 3; else
7455             if(*fen == 'B') marker[r][f++] = 4; else
7456             if(*fen == 'C') marker[r][f++] = 5; else
7457             if(*fen == 'M') marker[r][f++] = 6; else
7458             if(*fen == 'W') marker[r][f++] = 7; else
7459             if(*fen == 'D') marker[r][f++] = 8; else
7460             if(*fen == 'R') marker[r][f++] = 2; else {
7461                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7462               f += s; fen -= s>0;
7463             }
7464             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7465             if(r < 0) break;
7466             fen++;
7467         }
7468         DrawPosition(TRUE, NULL);
7469 }
7470
7471 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7472
7473 void
7474 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7475 {
7476     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7477     Markers *m = (Markers *) closure;
7478     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7479                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7480         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7481                          || kind == WhiteCapturesEnPassant
7482                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7483     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7484 }
7485
7486 static int hoverSavedValid;
7487
7488 void
7489 MarkTargetSquares (int clear)
7490 {
7491   int x, y, sum=0;
7492   if(clear) { // no reason to ever suppress clearing
7493     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7494     hoverSavedValid = 0;
7495     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7496   } else {
7497     int capt = 0;
7498     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7499        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7500     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7501     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7502       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7503       if(capt)
7504       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;
7505     }
7506   }
7507   DrawPosition(FALSE, NULL);
7508 }
7509
7510 int
7511 Explode (Board board, int fromX, int fromY, int toX, int toY)
7512 {
7513     if(gameInfo.variant == VariantAtomic &&
7514        (board[toY][toX] != EmptySquare ||                     // capture?
7515         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7516                          board[fromY][fromX] == BlackPawn   )
7517       )) {
7518         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7519         return TRUE;
7520     }
7521     return FALSE;
7522 }
7523
7524 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7525
7526 int
7527 CanPromote (ChessSquare piece, int y)
7528 {
7529         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7530         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7531         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7532         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7533            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7534           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7535            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7536         return (piece == BlackPawn && y <= zone ||
7537                 piece == WhitePawn && y >= BOARD_HEIGHT-1-deadRanks-zone ||
7538                 piece == BlackLance && y <= zone ||
7539                 piece == WhiteLance && y >= BOARD_HEIGHT-1-deadRanks-zone );
7540 }
7541
7542 void
7543 HoverEvent (int xPix, int yPix, int x, int y)
7544 {
7545         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7546         int r, f;
7547         if(!first.highlight) return;
7548         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7549         if(x == oldX && y == oldY) return; // only do something if we enter new square
7550         oldFromX = fromX; oldFromY = fromY;
7551         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7552           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7553             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7554           hoverSavedValid = 1;
7555         } else if(oldX != x || oldY != y) {
7556           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7557           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7558           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7559             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7560           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7561             char buf[MSG_SIZ];
7562             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7563             SendToProgram(buf, &first);
7564           }
7565           oldX = x; oldY = y;
7566 //        SetHighlights(fromX, fromY, x, y);
7567         }
7568 }
7569
7570 void ReportClick(char *action, int x, int y)
7571 {
7572         char buf[MSG_SIZ]; // Inform engine of what user does
7573         int r, f;
7574         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7575           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7576             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7577         if(!first.highlight || gameMode == EditPosition) return;
7578         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7579         SendToProgram(buf, &first);
7580 }
7581
7582 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7583 Boolean deferChoice;
7584 int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode
7585
7586 void
7587 LeftClick (ClickType clickType, int xPix, int yPix)
7588 {
7589     int x, y;
7590     static Boolean saveAnimate;
7591     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7592     char promoChoice = NULLCHAR;
7593     ChessSquare piece;
7594     static TimeMark lastClickTime, prevClickTime;
7595
7596     if(flashing) return;
7597
7598   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7599     x = EventToSquare(xPix, BOARD_WIDTH);
7600     y = EventToSquare(yPix, BOARD_HEIGHT);
7601     if (!flipView && y >= 0) {
7602         y = BOARD_HEIGHT - 1 - y;
7603     }
7604     if (flipView && x >= 0) {
7605         x = BOARD_WIDTH - 1 - x;
7606     }
7607
7608     // map clicks in offsetted holdings back to true coords (or switch the offset)
7609     if(x == BOARD_RGHT+1) {
7610         if(handOffsets & 1) {
7611             if(y == 0) { handOffsets &= ~1; DrawPosition(TRUE, boards[currentMove]); return; }
7612             y += handSize - BOARD_HEIGHT;
7613         } else if(y == BOARD_HEIGHT-1) { handOffsets |= 1; DrawPosition(TRUE, boards[currentMove]); return; }
7614     }
7615     if(x == BOARD_LEFT-2) {
7616         if(!(handOffsets & 2)) {
7617             if(y == 0) { handOffsets |= 2; DrawPosition(TRUE, boards[currentMove]); return; }
7618             y += handSize - BOARD_HEIGHT;
7619         } else if(y == BOARD_HEIGHT-1) { handOffsets &= ~2; DrawPosition(TRUE, boards[currentMove]); return; }
7620     }
7621
7622     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && 
7623         (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY) ) {
7624         static int dummy;
7625         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7626         right = TRUE;
7627         return;
7628     }
7629
7630     createX = createY = -1;
7631
7632     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7633
7634     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7635
7636     if (clickType == Press) ErrorPopDown();
7637     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7638
7639     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7640         defaultPromoChoice = promoSweep;
7641         promoSweep = EmptySquare;   // terminate sweep
7642         promoDefaultAltered = TRUE;
7643         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7644     }
7645
7646     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7647         if(clickType == Release) return; // ignore upclick of click-click destination
7648         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7649         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7650         if(gameInfo.holdingsWidth &&
7651                 (WhiteOnMove(currentMove)
7652                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7653                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7654             // click in right holdings, for determining promotion piece
7655             ChessSquare p = boards[currentMove][y][x];
7656             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7657             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7658             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7659                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7660                 fromX = fromY = -1;
7661                 return;
7662             }
7663         }
7664         DrawPosition(FALSE, boards[currentMove]);
7665         return;
7666     }
7667
7668     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7669     if(clickType == Press
7670             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7671               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7672               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7673         return;
7674
7675     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7676         // could be static click on premove from-square: abort premove
7677         gotPremove = 0;
7678         ClearPremoveHighlights();
7679     }
7680
7681     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7682         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7683
7684     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7685         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7686                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7687         defaultPromoChoice = DefaultPromoChoice(side);
7688     }
7689
7690     autoQueen = appData.alwaysPromoteToQueen;
7691
7692     if (fromX == -1) {
7693       int originalY = y;
7694       gatingPiece = EmptySquare;
7695       if (clickType != Press) {
7696         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7697             DragPieceEnd(xPix, yPix); dragging = 0;
7698             DrawPosition(FALSE, NULL);
7699         }
7700         return;
7701       }
7702       doubleClick = FALSE;
7703       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7704         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7705       }
7706       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7707       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7708          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7709          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7710             /* First square */
7711             if (OKToStartUserMove(fromX, fromY)) {
7712                 second = 0;
7713                 ReportClick("lift", x, y);
7714                 MarkTargetSquares(0);
7715                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7716                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7717                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7718                     promoSweep = defaultPromoChoice;
7719                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7720                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7721                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7722                 }
7723                 if (appData.highlightDragging) {
7724                     SetHighlights(fromX, fromY, -1, -1);
7725                 } else {
7726                     ClearHighlights();
7727                 }
7728             } else fromX = fromY = -1;
7729             return;
7730         }
7731     }
7732
7733     /* fromX != -1 */
7734     if (clickType == Press && gameMode != EditPosition) {
7735         ChessSquare fromP;
7736         ChessSquare toP;
7737         int frc;
7738
7739         // ignore off-board to clicks
7740         if(y < 0 || x < 0) return;
7741
7742         /* Check if clicking again on the same color piece */
7743         fromP = boards[currentMove][fromY][fromX];
7744         toP = boards[currentMove][y][x];
7745         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7746         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7747             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7748            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7749              WhitePawn <= toP && toP <= WhiteKing &&
7750              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7751              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7752             (BlackPawn <= fromP && fromP <= BlackKing &&
7753              BlackPawn <= toP && toP <= BlackKing &&
7754              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7755              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7756             /* Clicked again on same color piece -- changed his mind */
7757             second = (x == fromX && y == fromY);
7758             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7759             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7760                 second = FALSE; // first double-click rather than scond click
7761                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7762             }
7763             promoDefaultAltered = FALSE;
7764            if(!second) MarkTargetSquares(1);
7765            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7766             if (appData.highlightDragging) {
7767                 SetHighlights(x, y, -1, -1);
7768             } else {
7769                 ClearHighlights();
7770             }
7771             if (OKToStartUserMove(x, y)) {
7772                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7773                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7774                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7775                  gatingPiece = boards[currentMove][fromY][fromX];
7776                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7777                 fromX = x;
7778                 fromY = y; dragging = 1;
7779                 if(!second) ReportClick("lift", x, y);
7780                 MarkTargetSquares(0);
7781                 DragPieceBegin(xPix, yPix, FALSE);
7782                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7783                     promoSweep = defaultPromoChoice;
7784                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7785                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7786                 }
7787             }
7788            }
7789            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7790            second = FALSE;
7791         }
7792         // ignore clicks on holdings
7793         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7794     }
7795
7796     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7797         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7798         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7799         return;
7800     }
7801
7802     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7803         DragPieceEnd(xPix, yPix); dragging = 0;
7804         if(clearFlag) {
7805             // a deferred attempt to click-click move an empty square on top of a piece
7806             boards[currentMove][y][x] = EmptySquare;
7807             ClearHighlights();
7808             DrawPosition(FALSE, boards[currentMove]);
7809             fromX = fromY = -1; clearFlag = 0;
7810             return;
7811         }
7812         if (appData.animateDragging) {
7813             /* Undo animation damage if any */
7814             DrawPosition(FALSE, NULL);
7815         }
7816         if (second) {
7817             /* Second up/down in same square; just abort move */
7818             second = 0;
7819             fromX = fromY = -1;
7820             gatingPiece = EmptySquare;
7821             ClearHighlights();
7822             gotPremove = 0;
7823             ClearPremoveHighlights();
7824             MarkTargetSquares(-1);
7825             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7826         } else {
7827             /* First upclick in same square; start click-click mode */
7828             SetHighlights(x, y, -1, -1);
7829         }
7830         return;
7831     }
7832
7833     clearFlag = 0;
7834
7835     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7836        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7837         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7838         DisplayMessage(_("only marked squares are legal"),"");
7839         DrawPosition(TRUE, NULL);
7840         return; // ignore to-click
7841     }
7842
7843     /* we now have a different from- and (possibly off-board) to-square */
7844     /* Completed move */
7845     if(!sweepSelecting) {
7846         toX = x;
7847         toY = y;
7848     }
7849
7850     piece = boards[currentMove][fromY][fromX];
7851
7852     saveAnimate = appData.animate;
7853     if (clickType == Press) {
7854         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7855         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7856             // must be Edit Position mode with empty-square selected
7857             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7858             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7859             return;
7860         }
7861         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7862             return;
7863         }
7864         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7865             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7866         } else
7867         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7868         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7869           if(appData.sweepSelect) {
7870             promoSweep = defaultPromoChoice;
7871             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7872             selectFlag = 0; lastX = xPix; lastY = yPix;
7873             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7874             saveFlash = appData.flashCount; appData.flashCount = 0;
7875             Sweep(0); // Pawn that is going to promote: preview promotion piece
7876             sweepSelecting = 1;
7877             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7878             MarkTargetSquares(1);
7879           }
7880           return; // promo popup appears on up-click
7881         }
7882         /* Finish clickclick move */
7883         if (appData.animate || appData.highlightLastMove) {
7884             SetHighlights(fromX, fromY, toX, toY);
7885         } else {
7886             ClearHighlights();
7887         }
7888         MarkTargetSquares(1);
7889     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7890         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7891         *promoRestrict = 0; appData.flashCount = saveFlash;
7892         if (appData.animate || appData.highlightLastMove) {
7893             SetHighlights(fromX, fromY, toX, toY);
7894         } else {
7895             ClearHighlights();
7896         }
7897         MarkTargetSquares(1);
7898     } else {
7899 #if 0
7900 // [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
7901         /* Finish drag move */
7902         if (appData.highlightLastMove) {
7903             SetHighlights(fromX, fromY, toX, toY);
7904         } else {
7905             ClearHighlights();
7906         }
7907 #endif
7908         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7909           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7910         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7911         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7912           dragging *= 2;            // flag button-less dragging if we are dragging
7913           MarkTargetSquares(1);
7914           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7915           else {
7916             kill2X = killX; kill2Y = killY;
7917             killX = x; killY = y;     // remember this square as intermediate
7918             ReportClick("put", x, y); // and inform engine
7919             ReportClick("lift", x, y);
7920             MarkTargetSquares(0);
7921             return;
7922           }
7923         }
7924         DragPieceEnd(xPix, yPix); dragging = 0;
7925         /* Don't animate move and drag both */
7926         appData.animate = FALSE;
7927         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7928     }
7929
7930     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7931     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7932         ChessSquare piece = boards[currentMove][fromY][fromX];
7933         if(gameMode == EditPosition && piece != EmptySquare &&
7934            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7935             int n;
7936
7937             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7938                 n = PieceToNumber(piece - (int)BlackPawn);
7939                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7940                 boards[currentMove][handSize-1 - n][0] = piece;
7941                 boards[currentMove][handSize-1 - n][1]++;
7942             } else
7943             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7944                 n = PieceToNumber(piece);
7945                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7946                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7947                 boards[currentMove][n][BOARD_WIDTH-2]++;
7948             }
7949             boards[currentMove][fromY][fromX] = EmptySquare;
7950         }
7951         ClearHighlights();
7952         fromX = fromY = -1;
7953         MarkTargetSquares(1);
7954         DrawPosition(TRUE, boards[currentMove]);
7955         return;
7956     }
7957
7958     // off-board moves should not be highlighted
7959     if(x < 0 || y < 0) {
7960         ClearHighlights();
7961         DrawPosition(FALSE, NULL);
7962     } else ReportClick("put", x, y);
7963
7964     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7965  }
7966
7967     if(legal[toY][toX] == 2) { // highlight-induced promotion
7968         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7969         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7970     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7971       if(!*promoRestrict) {           // but has not done that yet
7972         deferChoice = TRUE;           // set up retry for when it does
7973         return;                       // and wait for that
7974       }
7975       promoChoice = ToLower(*promoRestrict); // force engine's choice
7976       deferChoice = FALSE;
7977     }
7978
7979     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7980         SetHighlights(fromX, fromY, toX, toY);
7981         MarkTargetSquares(1);
7982         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7983             // [HGM] super: promotion to captured piece selected from holdings
7984             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7985             promotionChoice = TRUE;
7986             // kludge follows to temporarily execute move on display, without promoting yet
7987             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7988             boards[currentMove][toY][toX] = p;
7989             DrawPosition(FALSE, boards[currentMove]);
7990             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7991             boards[currentMove][toY][toX] = q;
7992             DisplayMessage("Click in holdings to choose piece", "");
7993             return;
7994         }
7995         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7996         PromotionPopUp(promoChoice);
7997     } else {
7998         int oldMove = currentMove;
7999         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
8000         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
8001         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
8002         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
8003         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
8004            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
8005             DrawPosition(TRUE, boards[currentMove]);
8006         else DrawPosition(FALSE, NULL);
8007         fromX = fromY = -1;
8008         flashing = 0;
8009     }
8010     appData.animate = saveAnimate;
8011     if (appData.animate || appData.animateDragging) {
8012         /* Undo animation damage if needed */
8013 //      DrawPosition(FALSE, NULL);
8014     }
8015 }
8016
8017 int
8018 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
8019 {   // front-end-free part taken out of PieceMenuPopup
8020     int whichMenu; int xSqr, ySqr;
8021
8022     if(seekGraphUp) { // [HGM] seekgraph
8023         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
8024         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
8025         return -2;
8026     }
8027
8028     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
8029          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
8030         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
8031         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
8032         if(action == Press)   {
8033             originalFlip = flipView;
8034             flipView = !flipView; // temporarily flip board to see game from partners perspective
8035             DrawPosition(TRUE, partnerBoard);
8036             DisplayMessage(partnerStatus, "");
8037             partnerUp = TRUE;
8038         } else if(action == Release) {
8039             flipView = originalFlip;
8040             DrawPosition(TRUE, boards[currentMove]);
8041             partnerUp = FALSE;
8042         }
8043         return -2;
8044     }
8045
8046     xSqr = EventToSquare(x, BOARD_WIDTH);
8047     ySqr = EventToSquare(y, BOARD_HEIGHT);
8048     if (action == Release) {
8049         if(pieceSweep != EmptySquare) {
8050             EditPositionMenuEvent(pieceSweep, toX, toY);
8051             pieceSweep = EmptySquare;
8052         } else UnLoadPV(); // [HGM] pv
8053     }
8054     if (action != Press) return -2; // return code to be ignored
8055     switch (gameMode) {
8056       case IcsExamining:
8057         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8058       case EditPosition:
8059         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8060         if (xSqr < 0 || ySqr < 0) return -1;
8061         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8062         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8063         if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) {
8064             ChessSquare p = boards[currentMove][ySqr][xSqr];
8065             do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.');
8066             boards[currentMove][ySqr][xSqr] = p; DrawPosition(FALSE, boards[currentMove]);
8067             return -2;
8068         }
8069         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8070         createX = toX = xSqr; createY = toY = ySqr; lastX = x, lastY = y;
8071         NextPiece(0);
8072         return 2; // grab
8073       case IcsObserving:
8074         if(!appData.icsEngineAnalyze) return -1;
8075       case IcsPlayingWhite:
8076       case IcsPlayingBlack:
8077         if(!appData.zippyPlay) goto noZip;
8078       case AnalyzeMode:
8079       case AnalyzeFile:
8080       case MachinePlaysWhite:
8081       case MachinePlaysBlack:
8082       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8083         if (!appData.dropMenu) {
8084           LoadPV(x, y);
8085           return 2; // flag front-end to grab mouse events
8086         }
8087         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8088            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8089       case EditGame:
8090       noZip:
8091         if (xSqr < 0 || ySqr < 0) return -1;
8092         if (!appData.dropMenu || appData.testLegality &&
8093             gameInfo.variant != VariantBughouse &&
8094             gameInfo.variant != VariantCrazyhouse) return -1;
8095         whichMenu = 1; // drop menu
8096         break;
8097       default:
8098         return -1;
8099     }
8100
8101     if (((*fromX = xSqr) < 0) ||
8102         ((*fromY = ySqr) < 0)) {
8103         *fromX = *fromY = -1;
8104         return -1;
8105     }
8106     if (flipView)
8107       *fromX = BOARD_WIDTH - 1 - *fromX;
8108     else
8109       *fromY = BOARD_HEIGHT - 1 - *fromY;
8110
8111     return whichMenu;
8112 }
8113
8114 void
8115 Wheel (int dir, int x, int y)
8116 {
8117     if(gameMode == EditPosition) {
8118         int xSqr = EventToSquare(x, BOARD_WIDTH);
8119         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8120         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8121         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8122         do {
8123             boards[currentMove][ySqr][xSqr] += dir;
8124             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8125             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8126         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8127         DrawPosition(FALSE, boards[currentMove]);
8128     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8129 }
8130
8131 void
8132 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8133 {
8134 //    char * hint = lastHint;
8135     FrontEndProgramStats stats;
8136
8137     stats.which = cps == &first ? 0 : 1;
8138     stats.depth = cpstats->depth;
8139     stats.nodes = cpstats->nodes;
8140     stats.score = cpstats->score;
8141     stats.time = cpstats->time;
8142     stats.pv = cpstats->movelist;
8143     stats.hint = lastHint;
8144     stats.an_move_index = 0;
8145     stats.an_move_count = 0;
8146
8147     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8148         stats.hint = cpstats->move_name;
8149         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8150         stats.an_move_count = cpstats->nr_moves;
8151     }
8152
8153     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
8154
8155     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8156         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8157
8158     SetProgramStats( &stats );
8159 }
8160
8161 void
8162 ClearEngineOutputPane (int which)
8163 {
8164     static FrontEndProgramStats dummyStats;
8165     dummyStats.which = which;
8166     dummyStats.pv = "#";
8167     SetProgramStats( &dummyStats );
8168 }
8169
8170 #define MAXPLAYERS 500
8171
8172 char *
8173 TourneyStandings (int display)
8174 {
8175     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8176     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8177     char result, *p, *names[MAXPLAYERS];
8178
8179     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8180         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8181     names[0] = p = strdup(appData.participants);
8182     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8183
8184     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8185
8186     while(result = appData.results[nr]) {
8187         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8188         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8189         wScore = bScore = 0;
8190         switch(result) {
8191           case '+': wScore = 2; break;
8192           case '-': bScore = 2; break;
8193           case '=': wScore = bScore = 1; break;
8194           case ' ':
8195           case '*': return strdup("busy"); // tourney not finished
8196         }
8197         score[w] += wScore;
8198         score[b] += bScore;
8199         games[w]++;
8200         games[b]++;
8201         nr++;
8202     }
8203     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8204     for(w=0; w<nPlayers; w++) {
8205         bScore = -1;
8206         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8207         ranking[w] = b; points[w] = bScore; score[b] = -2;
8208     }
8209     p = malloc(nPlayers*34+1);
8210     for(w=0; w<nPlayers && w<display; w++)
8211         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8212     free(names[0]);
8213     return p;
8214 }
8215
8216 void
8217 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8218 {       // count all piece types
8219         int p, f, r;
8220         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8221         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8222         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8223                 p = board[r][f];
8224                 pCnt[p]++;
8225                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8226                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8227                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8228                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8229                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8230                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8231         }
8232 }
8233
8234 int
8235 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8236 {
8237         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8238         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8239
8240         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8241         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8242         if(myPawns == 2 && nMine == 3) // KPP
8243             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8244         if(myPawns == 1 && nMine == 2) // KP
8245             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8246         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8247             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8248         if(myPawns) return FALSE;
8249         if(pCnt[WhiteRook+side])
8250             return pCnt[BlackRook-side] ||
8251                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8252                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8253                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8254         if(pCnt[WhiteCannon+side]) {
8255             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8256             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8257         }
8258         if(pCnt[WhiteKnight+side])
8259             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8260         return FALSE;
8261 }
8262
8263 int
8264 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8265 {
8266         VariantClass v = gameInfo.variant;
8267
8268         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8269         if(v == VariantShatranj) return TRUE; // always winnable through baring
8270         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8271         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8272
8273         if(v == VariantXiangqi) {
8274                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8275
8276                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8277                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8278                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8279                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8280                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8281                 if(stale) // we have at least one last-rank P plus perhaps C
8282                     return majors // KPKX
8283                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8284                 else // KCA*E*
8285                     return pCnt[WhiteFerz+side] // KCAK
8286                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8287                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8288                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8289
8290         } else if(v == VariantKnightmate) {
8291                 if(nMine == 1) return FALSE;
8292                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8293         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8294                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8295
8296                 if(nMine == 1) return FALSE; // bare King
8297                 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
8298                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8299                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8300                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8301                 if(pCnt[WhiteKnight+side])
8302                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8303                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8304                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8305                 if(nBishops)
8306                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8307                 if(pCnt[WhiteAlfil+side])
8308                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8309                 if(pCnt[WhiteWazir+side])
8310                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8311         }
8312
8313         return TRUE;
8314 }
8315
8316 int
8317 CompareWithRights (Board b1, Board b2)
8318 {
8319     int rights = 0;
8320     if(!CompareBoards(b1, b2)) return FALSE;
8321     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8322     /* compare castling rights */
8323     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8324            rights++; /* King lost rights, while rook still had them */
8325     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8326         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8327            rights++; /* but at least one rook lost them */
8328     }
8329     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8330            rights++;
8331     if( b1[CASTLING][5] != NoRights ) {
8332         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8333            rights++;
8334     }
8335     return rights == 0;
8336 }
8337
8338 int
8339 Adjudicate (ChessProgramState *cps)
8340 {       // [HGM] some adjudications useful with buggy engines
8341         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8342         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8343         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8344         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8345         int k, drop, count = 0; static int bare = 1;
8346         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8347         Boolean canAdjudicate = !appData.icsActive;
8348
8349         // most tests only when we understand the game, i.e. legality-checking on
8350             if( appData.testLegality )
8351             {   /* [HGM] Some more adjudications for obstinate engines */
8352                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8353                 static int moveCount = 6;
8354                 ChessMove result;
8355                 char *reason = NULL;
8356
8357                 /* Count what is on board. */
8358                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8359
8360                 /* Some material-based adjudications that have to be made before stalemate test */
8361                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8362                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8363                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8364                      if(canAdjudicate && appData.checkMates) {
8365                          if(engineOpponent)
8366                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8367                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8368                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8369                          return 1;
8370                      }
8371                 }
8372
8373                 /* Bare King in Shatranj (loses) or Losers (wins) */
8374                 if( nrW == 1 || nrB == 1) {
8375                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8376                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8377                      if(canAdjudicate && appData.checkMates) {
8378                          if(engineOpponent)
8379                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8380                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8381                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8382                          return 1;
8383                      }
8384                   } else
8385                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8386                   {    /* bare King */
8387                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8388                         if(canAdjudicate && appData.checkMates) {
8389                             /* but only adjudicate if adjudication enabled */
8390                             if(engineOpponent)
8391                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8392                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8393                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8394                             return 1;
8395                         }
8396                   }
8397                 } else bare = 1;
8398
8399
8400             // don't wait for engine to announce game end if we can judge ourselves
8401             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8402               case MT_CHECK:
8403                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8404                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8405                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8406                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8407                             checkCnt++;
8408                         if(checkCnt >= 2) {
8409                             reason = "Xboard adjudication: 3rd check";
8410                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8411                             break;
8412                         }
8413                     }
8414                 }
8415               case MT_NONE:
8416               default:
8417                 break;
8418               case MT_STEALMATE:
8419               case MT_STALEMATE:
8420               case MT_STAINMATE:
8421                 reason = "Xboard adjudication: Stalemate";
8422                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8423                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8424                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8425                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8426                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8427                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8428                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8429                                                                         EP_CHECKMATE : EP_WINS);
8430                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8431                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8432                 }
8433                 break;
8434               case MT_CHECKMATE:
8435                 reason = "Xboard adjudication: Checkmate";
8436                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8437                 if(gameInfo.variant == VariantShogi) {
8438                     if(forwardMostMove > backwardMostMove
8439                        && moveList[forwardMostMove-1][1] == '@'
8440                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8441                         reason = "XBoard adjudication: pawn-drop mate";
8442                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8443                     }
8444                 }
8445                 break;
8446             }
8447
8448                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8449                     case EP_STALEMATE:
8450                         result = GameIsDrawn; break;
8451                     case EP_CHECKMATE:
8452                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8453                     case EP_WINS:
8454                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8455                     default:
8456                         result = EndOfFile;
8457                 }
8458                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8459                     if(engineOpponent)
8460                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8461                     GameEnds( result, reason, GE_XBOARD );
8462                     return 1;
8463                 }
8464
8465                 /* Next absolutely insufficient mating material. */
8466                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8467                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8468                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8469
8470                      /* always flag draws, for judging claims */
8471                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8472
8473                      if(canAdjudicate && appData.materialDraws) {
8474                          /* but only adjudicate them if adjudication enabled */
8475                          if(engineOpponent) {
8476                            SendToProgram("force\n", engineOpponent); // suppress reply
8477                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8478                          }
8479                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8480                          return 1;
8481                      }
8482                 }
8483
8484                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8485                 if(gameInfo.variant == VariantXiangqi ?
8486                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8487                  : nrW + nrB == 4 &&
8488                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8489                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8490                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8491                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8492                    ) ) {
8493                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8494                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8495                           if(engineOpponent) {
8496                             SendToProgram("force\n", engineOpponent); // suppress reply
8497                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8498                           }
8499                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8500                           return 1;
8501                      }
8502                 } else moveCount = 6;
8503             }
8504
8505         // Repetition draws and 50-move rule can be applied independently of legality testing
8506
8507                 /* Check for rep-draws */
8508                 count = 0;
8509                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8510                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8511                 for(k = forwardMostMove-2;
8512                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8513                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8514                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8515                     k-=2)
8516                 {   int rights=0;
8517                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8518                         /* compare castling rights */
8519                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8520                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8521                                 rights++; /* King lost rights, while rook still had them */
8522                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8523                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8524                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8525                                    rights++; /* but at least one rook lost them */
8526                         }
8527                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8528                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8529                                 rights++;
8530                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8531                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8532                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8533                                    rights++;
8534                         }
8535                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8536                             && appData.drawRepeats > 1) {
8537                              /* adjudicate after user-specified nr of repeats */
8538                              int result = GameIsDrawn;
8539                              char *details = "XBoard adjudication: repetition draw";
8540                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8541                                 // [HGM] xiangqi: check for forbidden perpetuals
8542                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8543                                 for(m=forwardMostMove; m>k; m-=2) {
8544                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8545                                         ourPerpetual = 0; // the current mover did not always check
8546                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8547                                         hisPerpetual = 0; // the opponent did not always check
8548                                 }
8549                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8550                                                                         ourPerpetual, hisPerpetual);
8551                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8552                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8553                                     details = "Xboard adjudication: perpetual checking";
8554                                 } else
8555                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8556                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8557                                 } else
8558                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8559                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8560                                         result = BlackWins;
8561                                         details = "Xboard adjudication: repetition";
8562                                     }
8563                                 } else // it must be XQ
8564                                 // Now check for perpetual chases
8565                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8566                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8567                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8568                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8569                                         static char resdet[MSG_SIZ];
8570                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8571                                         details = resdet;
8572                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8573                                     } else
8574                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8575                                         break; // Abort repetition-checking loop.
8576                                 }
8577                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8578                              }
8579                              if(engineOpponent) {
8580                                SendToProgram("force\n", engineOpponent); // suppress reply
8581                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8582                              }
8583                              GameEnds( result, details, GE_XBOARD );
8584                              return 1;
8585                         }
8586                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8587                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8588                     }
8589                 }
8590
8591                 /* Now we test for 50-move draws. Determine ply count */
8592                 count = forwardMostMove;
8593                 /* look for last irreversble move */
8594                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8595                     count--;
8596                 /* if we hit starting position, add initial plies */
8597                 if( count == backwardMostMove )
8598                     count -= initialRulePlies;
8599                 count = forwardMostMove - count;
8600                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8601                         // adjust reversible move counter for checks in Xiangqi
8602                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8603                         if(i < backwardMostMove) i = backwardMostMove;
8604                         while(i <= forwardMostMove) {
8605                                 lastCheck = inCheck; // check evasion does not count
8606                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8607                                 if(inCheck || lastCheck) count--; // check does not count
8608                                 i++;
8609                         }
8610                 }
8611                 if( count >= 100)
8612                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8613                          /* this is used to judge if draw claims are legal */
8614                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8615                          if(engineOpponent) {
8616                            SendToProgram("force\n", engineOpponent); // suppress reply
8617                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8618                          }
8619                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8620                          return 1;
8621                 }
8622
8623                 /* if draw offer is pending, treat it as a draw claim
8624                  * when draw condition present, to allow engines a way to
8625                  * claim draws before making their move to avoid a race
8626                  * condition occurring after their move
8627                  */
8628                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8629                          char *p = NULL;
8630                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8631                              p = "Draw claim: 50-move rule";
8632                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8633                              p = "Draw claim: 3-fold repetition";
8634                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8635                              p = "Draw claim: insufficient mating material";
8636                          if( p != NULL && canAdjudicate) {
8637                              if(engineOpponent) {
8638                                SendToProgram("force\n", engineOpponent); // suppress reply
8639                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8640                              }
8641                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8642                              return 1;
8643                          }
8644                 }
8645
8646                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8647                     if(engineOpponent) {
8648                       SendToProgram("force\n", engineOpponent); // suppress reply
8649                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8650                     }
8651                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8652                     return 1;
8653                 }
8654         return 0;
8655 }
8656
8657 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8658 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8659 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8660
8661 static int
8662 BitbaseProbe ()
8663 {
8664     int pieces[10], squares[10], cnt=0, r, f, res;
8665     static int loaded;
8666     static PPROBE_EGBB probeBB;
8667     if(!appData.testLegality) return 10;
8668     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8669     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8670     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8671     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8672         ChessSquare piece = boards[forwardMostMove][r][f];
8673         int black = (piece >= BlackPawn);
8674         int type = piece - black*BlackPawn;
8675         if(piece == EmptySquare) continue;
8676         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8677         if(type == WhiteKing) type = WhiteQueen + 1;
8678         type = egbbCode[type];
8679         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8680         pieces[cnt] = type + black*6;
8681         if(++cnt > 5) return 11;
8682     }
8683     pieces[cnt] = squares[cnt] = 0;
8684     // probe EGBB
8685     if(loaded == 2) return 13; // loading failed before
8686     if(loaded == 0) {
8687         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8688         HMODULE lib;
8689         PLOAD_EGBB loadBB;
8690         loaded = 2; // prepare for failure
8691         if(!path) return 13; // no egbb installed
8692         strncpy(buf, path + 8, MSG_SIZ);
8693         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8694         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8695         lib = LoadLibrary(buf);
8696         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8697         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8698         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8699         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8700         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8701         loaded = 1; // success!
8702     }
8703     res = probeBB(forwardMostMove & 1, pieces, squares);
8704     return res > 0 ? 1 : res < 0 ? -1 : 0;
8705 }
8706
8707 char *
8708 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8709 {   // [HGM] book: this routine intercepts moves to simulate book replies
8710     char *bookHit = NULL;
8711
8712     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8713         char buf[MSG_SIZ];
8714         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8715         SendToProgram(buf, cps);
8716     }
8717     //first determine if the incoming move brings opponent into his book
8718     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8719         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8720     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8721     if(bookHit != NULL && !cps->bookSuspend) {
8722         // make sure opponent is not going to reply after receiving move to book position
8723         SendToProgram("force\n", cps);
8724         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8725     }
8726     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8727     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8728     // now arrange restart after book miss
8729     if(bookHit) {
8730         // after a book hit we never send 'go', and the code after the call to this routine
8731         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8732         char buf[MSG_SIZ], *move = bookHit;
8733         if(cps->useSAN) {
8734             int fromX, fromY, toX, toY;
8735             char promoChar;
8736             ChessMove moveType;
8737             move = buf + 30;
8738             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8739                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8740                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8741                                     PosFlags(forwardMostMove),
8742                                     fromY, fromX, toY, toX, promoChar, move);
8743             } else {
8744                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8745                 bookHit = NULL;
8746             }
8747         }
8748         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8749         SendToProgram(buf, cps);
8750         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8751     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8752         SendToProgram("go\n", cps);
8753         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8754     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8755         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8756             SendToProgram("go\n", cps);
8757         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8758     }
8759     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8760 }
8761
8762 int
8763 LoadError (char *errmess, ChessProgramState *cps)
8764 {   // unloads engine and switches back to -ncp mode if it was first
8765     if(cps->initDone) return FALSE;
8766     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8767     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8768     cps->pr = NoProc;
8769     if(cps == &first) {
8770         appData.noChessProgram = TRUE;
8771         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8772         gameMode = BeginningOfGame; ModeHighlight();
8773         SetNCPMode();
8774     }
8775     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8776     DisplayMessage("", ""); // erase waiting message
8777     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8778     return TRUE;
8779 }
8780
8781 char *savedMessage;
8782 ChessProgramState *savedState;
8783 void
8784 DeferredBookMove (void)
8785 {
8786         if(savedState->lastPing != savedState->lastPong)
8787                     ScheduleDelayedEvent(DeferredBookMove, 10);
8788         else
8789         HandleMachineMove(savedMessage, savedState);
8790 }
8791
8792 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8793 static ChessProgramState *stalledEngine;
8794 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8795
8796 void
8797 HandleMachineMove (char *message, ChessProgramState *cps)
8798 {
8799     static char firstLeg[20], legs;
8800     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8801     char realname[MSG_SIZ];
8802     int fromX, fromY, toX, toY;
8803     ChessMove moveType;
8804     char promoChar, roar;
8805     char *p, *pv=buf1;
8806     int oldError;
8807     char *bookHit;
8808
8809     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8810         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8811         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8812             DisplayError(_("Invalid pairing from pairing engine"), 0);
8813             return;
8814         }
8815         pairingReceived = 1;
8816         NextMatchGame();
8817         return; // Skim the pairing messages here.
8818     }
8819
8820     oldError = cps->userError; cps->userError = 0;
8821
8822 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8823     /*
8824      * Kludge to ignore BEL characters
8825      */
8826     while (*message == '\007') message++;
8827
8828     /*
8829      * [HGM] engine debug message: ignore lines starting with '#' character
8830      */
8831     if(cps->debug && *message == '#') return;
8832
8833     /*
8834      * Look for book output
8835      */
8836     if (cps == &first && bookRequested) {
8837         if (message[0] == '\t' || message[0] == ' ') {
8838             /* Part of the book output is here; append it */
8839             strcat(bookOutput, message);
8840             strcat(bookOutput, "  \n");
8841             return;
8842         } else if (bookOutput[0] != NULLCHAR) {
8843             /* All of book output has arrived; display it */
8844             char *p = bookOutput;
8845             while (*p != NULLCHAR) {
8846                 if (*p == '\t') *p = ' ';
8847                 p++;
8848             }
8849             DisplayInformation(bookOutput);
8850             bookRequested = FALSE;
8851             /* Fall through to parse the current output */
8852         }
8853     }
8854
8855     /*
8856      * Look for machine move.
8857      */
8858     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8859         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8860     {
8861         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8862             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8863             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8864             stalledEngine = cps;
8865             if(appData.ponderNextMove) { // bring opponent out of ponder
8866                 if(gameMode == TwoMachinesPlay) {
8867                     if(cps->other->pause)
8868                         PauseEngine(cps->other);
8869                     else
8870                         SendToProgram("easy\n", cps->other);
8871                 }
8872             }
8873             StopClocks();
8874             return;
8875         }
8876
8877       if(cps->usePing) {
8878
8879         /* This method is only useful on engines that support ping */
8880         if(abortEngineThink) {
8881             if (appData.debugMode) {
8882                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8883             }
8884             SendToProgram("undo\n", cps);
8885             return;
8886         }
8887
8888         if (cps->lastPing != cps->lastPong) {
8889             /* Extra move from before last new; ignore */
8890             if (appData.debugMode) {
8891                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8892             }
8893           return;
8894         }
8895
8896       } else {
8897
8898         int machineWhite = FALSE;
8899
8900         switch (gameMode) {
8901           case BeginningOfGame:
8902             /* Extra move from before last reset; ignore */
8903             if (appData.debugMode) {
8904                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8905             }
8906             return;
8907
8908           case EndOfGame:
8909           case IcsIdle:
8910           default:
8911             /* Extra move after we tried to stop.  The mode test is
8912                not a reliable way of detecting this problem, but it's
8913                the best we can do on engines that don't support ping.
8914             */
8915             if (appData.debugMode) {
8916                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8917                         cps->which, gameMode);
8918             }
8919             SendToProgram("undo\n", cps);
8920             return;
8921
8922           case MachinePlaysWhite:
8923           case IcsPlayingWhite:
8924             machineWhite = TRUE;
8925             break;
8926
8927           case MachinePlaysBlack:
8928           case IcsPlayingBlack:
8929             machineWhite = FALSE;
8930             break;
8931
8932           case TwoMachinesPlay:
8933             machineWhite = (cps->twoMachinesColor[0] == 'w');
8934             break;
8935         }
8936         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8937             if (appData.debugMode) {
8938                 fprintf(debugFP,
8939                         "Ignoring move out of turn by %s, gameMode %d"
8940                         ", forwardMost %d\n",
8941                         cps->which, gameMode, forwardMostMove);
8942             }
8943             return;
8944         }
8945       }
8946
8947         if(cps->alphaRank) AlphaRank(machineMove, 4);
8948
8949         // [HGM] lion: (some very limited) support for Alien protocol
8950         killX = killY = kill2X = kill2Y = -1;
8951         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8952             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8953             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8954             return;
8955         }
8956         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8957             char *q = strchr(p+1, ',');            // second comma?
8958             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8959             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8960             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8961         }
8962         if(firstLeg[0]) { // there was a previous leg;
8963             // only support case where same piece makes two step
8964             char buf[20], *p = machineMove+1, *q = buf+1, f;
8965             safeStrCpy(buf, machineMove, 20);
8966             while(isdigit(*q)) q++; // find start of to-square
8967             safeStrCpy(machineMove, firstLeg, 20);
8968             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8969             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
8970             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)
8971             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8972             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8973             firstLeg[0] = NULLCHAR; legs = 0;
8974         }
8975
8976         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8977                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8978             /* Machine move could not be parsed; ignore it. */
8979           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8980                     machineMove, _(cps->which));
8981             DisplayMoveError(buf1);
8982             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8983                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8984             if (gameMode == TwoMachinesPlay) {
8985               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8986                        buf1, GE_XBOARD);
8987             }
8988             return;
8989         }
8990
8991         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8992         /* So we have to redo legality test with true e.p. status here,  */
8993         /* to make sure an illegal e.p. capture does not slip through,   */
8994         /* to cause a forfeit on a justified illegal-move complaint      */
8995         /* of the opponent.                                              */
8996         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8997            ChessMove moveType;
8998            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8999                              fromY, fromX, toY, toX, promoChar);
9000             if(moveType == IllegalMove) {
9001               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
9002                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
9003                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9004                            buf1, GE_XBOARD);
9005                 return;
9006            } else if(!appData.fischerCastling)
9007            /* [HGM] Kludge to handle engines that send FRC-style castling
9008               when they shouldn't (like TSCP-Gothic) */
9009            switch(moveType) {
9010              case WhiteASideCastleFR:
9011              case BlackASideCastleFR:
9012                toX+=2;
9013                currentMoveString[2]++;
9014                break;
9015              case WhiteHSideCastleFR:
9016              case BlackHSideCastleFR:
9017                toX--;
9018                currentMoveString[2]--;
9019                break;
9020              default: ; // nothing to do, but suppresses warning of pedantic compilers
9021            }
9022         }
9023         hintRequested = FALSE;
9024         lastHint[0] = NULLCHAR;
9025         bookRequested = FALSE;
9026         /* Program may be pondering now */
9027         cps->maybeThinking = TRUE;
9028         if (cps->sendTime == 2) cps->sendTime = 1;
9029         if (cps->offeredDraw) cps->offeredDraw--;
9030
9031         /* [AS] Save move info*/
9032         pvInfoList[ forwardMostMove ].score = programStats.score;
9033         pvInfoList[ forwardMostMove ].depth = programStats.depth;
9034         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
9035
9036         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
9037
9038         /* Test suites abort the 'game' after one move */
9039         if(*appData.finger) {
9040            static FILE *f;
9041            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
9042            if(!f) f = fopen(appData.finger, "w");
9043            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
9044            else { DisplayFatalError("Bad output file", errno, 0); return; }
9045            free(fen);
9046            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9047         }
9048         if(appData.epd) {
9049            if(solvingTime >= 0) {
9050               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
9051               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
9052            } else {
9053               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
9054               if(solvingTime == -2) second.matchWins++;
9055            }
9056            OutputKibitz(2, buf1);
9057            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9058         }
9059
9060         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9061         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9062             int count = 0;
9063
9064             while( count < adjudicateLossPlies ) {
9065                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9066
9067                 if( count & 1 ) {
9068                     score = -score; /* Flip score for winning side */
9069                 }
9070
9071                 if( score > appData.adjudicateLossThreshold ) {
9072                     break;
9073                 }
9074
9075                 count++;
9076             }
9077
9078             if( count >= adjudicateLossPlies ) {
9079                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9080
9081                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9082                     "Xboard adjudication",
9083                     GE_XBOARD );
9084
9085                 return;
9086             }
9087         }
9088
9089         if(Adjudicate(cps)) {
9090             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9091             return; // [HGM] adjudicate: for all automatic game ends
9092         }
9093
9094 #if ZIPPY
9095         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9096             first.initDone) {
9097           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9098                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9099                 SendToICS("draw ");
9100                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9101           }
9102           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9103           ics_user_moved = 1;
9104           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9105                 char buf[3*MSG_SIZ];
9106
9107                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9108                         programStats.score / 100.,
9109                         programStats.depth,
9110                         programStats.time / 100.,
9111                         (unsigned int)programStats.nodes,
9112                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9113                         programStats.movelist);
9114                 SendToICS(buf);
9115           }
9116         }
9117 #endif
9118
9119         /* [AS] Clear stats for next move */
9120         ClearProgramStats();
9121         thinkOutput[0] = NULLCHAR;
9122         hiddenThinkOutputState = 0;
9123
9124         bookHit = NULL;
9125         if (gameMode == TwoMachinesPlay) {
9126             /* [HGM] relaying draw offers moved to after reception of move */
9127             /* and interpreting offer as claim if it brings draw condition */
9128             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9129                 SendToProgram("draw\n", cps->other);
9130             }
9131             if (cps->other->sendTime) {
9132                 SendTimeRemaining(cps->other,
9133                                   cps->other->twoMachinesColor[0] == 'w');
9134             }
9135             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9136             if (firstMove && !bookHit) {
9137                 firstMove = FALSE;
9138                 if (cps->other->useColors) {
9139                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9140                 }
9141                 SendToProgram("go\n", cps->other);
9142             }
9143             cps->other->maybeThinking = TRUE;
9144         }
9145
9146         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9147
9148         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9149
9150         if (!pausing && appData.ringBellAfterMoves) {
9151             if(!roar) RingBell();
9152         }
9153
9154         /*
9155          * Reenable menu items that were disabled while
9156          * machine was thinking
9157          */
9158         if (gameMode != TwoMachinesPlay)
9159             SetUserThinkingEnables();
9160
9161         // [HGM] book: after book hit opponent has received move and is now in force mode
9162         // force the book reply into it, and then fake that it outputted this move by jumping
9163         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9164         if(bookHit) {
9165                 static char bookMove[MSG_SIZ]; // a bit generous?
9166
9167                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9168                 strcat(bookMove, bookHit);
9169                 message = bookMove;
9170                 cps = cps->other;
9171                 programStats.nodes = programStats.depth = programStats.time =
9172                 programStats.score = programStats.got_only_move = 0;
9173                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9174
9175                 if(cps->lastPing != cps->lastPong) {
9176                     savedMessage = message; // args for deferred call
9177                     savedState = cps;
9178                     ScheduleDelayedEvent(DeferredBookMove, 10);
9179                     return;
9180                 }
9181                 goto FakeBookMove;
9182         }
9183
9184         return;
9185     }
9186
9187     /* Set special modes for chess engines.  Later something general
9188      *  could be added here; for now there is just one kludge feature,
9189      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9190      *  when "xboard" is given as an interactive command.
9191      */
9192     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9193         cps->useSigint = FALSE;
9194         cps->useSigterm = FALSE;
9195     }
9196     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9197       ParseFeatures(message+8, cps);
9198       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9199     }
9200
9201     if (!strncmp(message, "setup ", 6) && 
9202         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9203           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9204                                         ) { // [HGM] allow first engine to define opening position
9205       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9206       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9207       *buf = NULLCHAR;
9208       if(sscanf(message, "setup (%s", buf) == 1) {
9209         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9210         ASSIGN(appData.pieceToCharTable, buf);
9211       }
9212       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9213       if(dummy >= 3) {
9214         while(message[s] && message[s++] != ' ');
9215         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9216            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9217 //          if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9218             if(hand > h) handSize = hand; else handSize = h;
9219             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9220             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9221           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9222           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9223           startedFromSetupPosition = FALSE;
9224         }
9225       }
9226       if(startedFromSetupPosition) return;
9227       ParseFEN(boards[0], &dummy, message+s, FALSE);
9228       DrawPosition(TRUE, boards[0]);
9229       CopyBoard(initialPosition, boards[0]);
9230       startedFromSetupPosition = TRUE;
9231       return;
9232     }
9233     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9234       ChessSquare piece = WhitePawn;
9235       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9236       if(*p == '+') promoted++, ID = *++p;
9237       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9238       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9239       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9240       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9241       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9242       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9243       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9244       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9245                                                && gameInfo.variant != VariantGreat
9246                                                && gameInfo.variant != VariantFairy    ) return;
9247       if(piece < EmptySquare) {
9248         pieceDefs = TRUE;
9249         ASSIGN(pieceDesc[piece], buf1);
9250         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9251       }
9252       return;
9253     }
9254     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9255       if(deferChoice) {
9256         LeftClick(Press, 0, 0); // finish the click that was interrupted
9257       } else if(promoSweep != EmptySquare) {
9258         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9259         if(strlen(promoRestrict) > 1) Sweep(0);
9260       }
9261       return;
9262     }
9263     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9264      * want this, I was asked to put it in, and obliged.
9265      */
9266     if (!strncmp(message, "setboard ", 9)) {
9267         Board initial_position;
9268
9269         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9270
9271         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9272             DisplayError(_("Bad FEN received from engine"), 0);
9273             return ;
9274         } else {
9275            Reset(TRUE, FALSE);
9276            CopyBoard(boards[0], initial_position);
9277            initialRulePlies = FENrulePlies;
9278            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9279            else gameMode = MachinePlaysBlack;
9280            DrawPosition(FALSE, boards[currentMove]);
9281         }
9282         return;
9283     }
9284
9285     /*
9286      * Look for communication commands
9287      */
9288     if (!strncmp(message, "telluser ", 9)) {
9289         if(message[9] == '\\' && message[10] == '\\')
9290             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9291         PlayTellSound();
9292         DisplayNote(message + 9);
9293         return;
9294     }
9295     if (!strncmp(message, "tellusererror ", 14)) {
9296         cps->userError = 1;
9297         if(message[14] == '\\' && message[15] == '\\')
9298             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9299         PlayTellSound();
9300         DisplayError(message + 14, 0);
9301         return;
9302     }
9303     if (!strncmp(message, "tellopponent ", 13)) {
9304       if (appData.icsActive) {
9305         if (loggedOn) {
9306           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9307           SendToICS(buf1);
9308         }
9309       } else {
9310         DisplayNote(message + 13);
9311       }
9312       return;
9313     }
9314     if (!strncmp(message, "tellothers ", 11)) {
9315       if (appData.icsActive) {
9316         if (loggedOn) {
9317           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9318           SendToICS(buf1);
9319         }
9320       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9321       return;
9322     }
9323     if (!strncmp(message, "tellall ", 8)) {
9324       if (appData.icsActive) {
9325         if (loggedOn) {
9326           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9327           SendToICS(buf1);
9328         }
9329       } else {
9330         DisplayNote(message + 8);
9331       }
9332       return;
9333     }
9334     if (strncmp(message, "warning", 7) == 0) {
9335         /* Undocumented feature, use tellusererror in new code */
9336         DisplayError(message, 0);
9337         return;
9338     }
9339     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9340         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9341         strcat(realname, " query");
9342         AskQuestion(realname, buf2, buf1, cps->pr);
9343         return;
9344     }
9345     /* Commands from the engine directly to ICS.  We don't allow these to be
9346      *  sent until we are logged on. Crafty kibitzes have been known to
9347      *  interfere with the login process.
9348      */
9349     if (loggedOn) {
9350         if (!strncmp(message, "tellics ", 8)) {
9351             SendToICS(message + 8);
9352             SendToICS("\n");
9353             return;
9354         }
9355         if (!strncmp(message, "tellicsnoalias ", 15)) {
9356             SendToICS(ics_prefix);
9357             SendToICS(message + 15);
9358             SendToICS("\n");
9359             return;
9360         }
9361         /* The following are for backward compatibility only */
9362         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9363             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9364             SendToICS(ics_prefix);
9365             SendToICS(message);
9366             SendToICS("\n");
9367             return;
9368         }
9369     }
9370     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9371         if(initPing == cps->lastPong) {
9372             if(gameInfo.variant == VariantUnknown) {
9373                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9374                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9375                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9376             }
9377             initPing = -1;
9378         }
9379         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9380             abortEngineThink = FALSE;
9381             DisplayMessage("", "");
9382             ThawUI();
9383         }
9384         return;
9385     }
9386     if(!strncmp(message, "highlight ", 10)) {
9387         if(appData.testLegality && !*engineVariant && appData.markers) return;
9388         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9389         return;
9390     }
9391     if(!strncmp(message, "click ", 6)) {
9392         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9393         if(appData.testLegality || !appData.oneClick) return;
9394         sscanf(message+6, "%c%d%c", &f, &y, &c);
9395         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9396         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9397         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9398         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9399         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9400         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9401             LeftClick(Release, lastLeftX, lastLeftY);
9402         controlKey  = (c == ',');
9403         LeftClick(Press, x, y);
9404         LeftClick(Release, x, y);
9405         first.highlight = f;
9406         return;
9407     }
9408     /*
9409      * If the move is illegal, cancel it and redraw the board.
9410      * Also deal with other error cases.  Matching is rather loose
9411      * here to accommodate engines written before the spec.
9412      */
9413     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9414         strncmp(message, "Error", 5) == 0) {
9415         if (StrStr(message, "name") ||
9416             StrStr(message, "rating") || StrStr(message, "?") ||
9417             StrStr(message, "result") || StrStr(message, "board") ||
9418             StrStr(message, "bk") || StrStr(message, "computer") ||
9419             StrStr(message, "variant") || StrStr(message, "hint") ||
9420             StrStr(message, "random") || StrStr(message, "depth") ||
9421             StrStr(message, "accepted")) {
9422             return;
9423         }
9424         if (StrStr(message, "protover")) {
9425           /* Program is responding to input, so it's apparently done
9426              initializing, and this error message indicates it is
9427              protocol version 1.  So we don't need to wait any longer
9428              for it to initialize and send feature commands. */
9429           FeatureDone(cps, 1);
9430           cps->protocolVersion = 1;
9431           return;
9432         }
9433         cps->maybeThinking = FALSE;
9434
9435         if (StrStr(message, "draw")) {
9436             /* Program doesn't have "draw" command */
9437             cps->sendDrawOffers = 0;
9438             return;
9439         }
9440         if (cps->sendTime != 1 &&
9441             (StrStr(message, "time") || StrStr(message, "otim"))) {
9442           /* Program apparently doesn't have "time" or "otim" command */
9443           cps->sendTime = 0;
9444           return;
9445         }
9446         if (StrStr(message, "analyze")) {
9447             cps->analysisSupport = FALSE;
9448             cps->analyzing = FALSE;
9449 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9450             EditGameEvent(); // [HGM] try to preserve loaded game
9451             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9452             DisplayError(buf2, 0);
9453             return;
9454         }
9455         if (StrStr(message, "(no matching move)st")) {
9456           /* Special kludge for GNU Chess 4 only */
9457           cps->stKludge = TRUE;
9458           SendTimeControl(cps, movesPerSession, timeControl,
9459                           timeIncrement, appData.searchDepth,
9460                           searchTime);
9461           return;
9462         }
9463         if (StrStr(message, "(no matching move)sd")) {
9464           /* Special kludge for GNU Chess 4 only */
9465           cps->sdKludge = TRUE;
9466           SendTimeControl(cps, movesPerSession, timeControl,
9467                           timeIncrement, appData.searchDepth,
9468                           searchTime);
9469           return;
9470         }
9471         if (!StrStr(message, "llegal")) {
9472             return;
9473         }
9474         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9475             gameMode == IcsIdle) return;
9476         if (forwardMostMove <= backwardMostMove) return;
9477         if (pausing) PauseEvent();
9478       if(appData.forceIllegal) {
9479             // [HGM] illegal: machine refused move; force position after move into it
9480           SendToProgram("force\n", cps);
9481           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9482                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9483                 // when black is to move, while there might be nothing on a2 or black
9484                 // might already have the move. So send the board as if white has the move.
9485                 // But first we must change the stm of the engine, as it refused the last move
9486                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9487                 if(WhiteOnMove(forwardMostMove)) {
9488                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9489                     SendBoard(cps, forwardMostMove); // kludgeless board
9490                 } else {
9491                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9492                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9493                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9494                 }
9495           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9496             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9497                  gameMode == TwoMachinesPlay)
9498               SendToProgram("go\n", cps);
9499             return;
9500       } else
9501         if (gameMode == PlayFromGameFile) {
9502             /* Stop reading this game file */
9503             gameMode = EditGame;
9504             ModeHighlight();
9505         }
9506         /* [HGM] illegal-move claim should forfeit game when Xboard */
9507         /* only passes fully legal moves                            */
9508         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9509             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9510                                 "False illegal-move claim", GE_XBOARD );
9511             return; // do not take back move we tested as valid
9512         }
9513         currentMove = forwardMostMove-1;
9514         DisplayMove(currentMove-1); /* before DisplayMoveError */
9515         SwitchClocks(forwardMostMove-1); // [HGM] race
9516         DisplayBothClocks();
9517         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9518                 parseList[currentMove], _(cps->which));
9519         DisplayMoveError(buf1);
9520         DrawPosition(FALSE, boards[currentMove]);
9521
9522         SetUserThinkingEnables();
9523         return;
9524     }
9525     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9526         /* Program has a broken "time" command that
9527            outputs a string not ending in newline.
9528            Don't use it. */
9529         cps->sendTime = 0;
9530     }
9531     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9532         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9533             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9534     }
9535
9536     /*
9537      * If chess program startup fails, exit with an error message.
9538      * Attempts to recover here are futile. [HGM] Well, we try anyway
9539      */
9540     if ((StrStr(message, "unknown host") != NULL)
9541         || (StrStr(message, "No remote directory") != NULL)
9542         || (StrStr(message, "not found") != NULL)
9543         || (StrStr(message, "No such file") != NULL)
9544         || (StrStr(message, "can't alloc") != NULL)
9545         || (StrStr(message, "Permission denied") != NULL)) {
9546
9547         cps->maybeThinking = FALSE;
9548         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9549                 _(cps->which), cps->program, cps->host, message);
9550         RemoveInputSource(cps->isr);
9551         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9552             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9553             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9554         }
9555         return;
9556     }
9557
9558     /*
9559      * Look for hint output
9560      */
9561     if (sscanf(message, "Hint: %s", buf1) == 1) {
9562         if (cps == &first && hintRequested) {
9563             hintRequested = FALSE;
9564             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9565                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9566                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9567                                     PosFlags(forwardMostMove),
9568                                     fromY, fromX, toY, toX, promoChar, buf1);
9569                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9570                 DisplayInformation(buf2);
9571             } else {
9572                 /* Hint move could not be parsed!? */
9573               snprintf(buf2, sizeof(buf2),
9574                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9575                         buf1, _(cps->which));
9576                 DisplayError(buf2, 0);
9577             }
9578         } else {
9579           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9580         }
9581         return;
9582     }
9583
9584     /*
9585      * Ignore other messages if game is not in progress
9586      */
9587     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9588         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9589
9590     /*
9591      * look for win, lose, draw, or draw offer
9592      */
9593     if (strncmp(message, "1-0", 3) == 0) {
9594         char *p, *q, *r = "";
9595         p = strchr(message, '{');
9596         if (p) {
9597             q = strchr(p, '}');
9598             if (q) {
9599                 *q = NULLCHAR;
9600                 r = p + 1;
9601             }
9602         }
9603         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9604         return;
9605     } else if (strncmp(message, "0-1", 3) == 0) {
9606         char *p, *q, *r = "";
9607         p = strchr(message, '{');
9608         if (p) {
9609             q = strchr(p, '}');
9610             if (q) {
9611                 *q = NULLCHAR;
9612                 r = p + 1;
9613             }
9614         }
9615         /* Kludge for Arasan 4.1 bug */
9616         if (strcmp(r, "Black resigns") == 0) {
9617             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9618             return;
9619         }
9620         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9621         return;
9622     } else if (strncmp(message, "1/2", 3) == 0) {
9623         char *p, *q, *r = "";
9624         p = strchr(message, '{');
9625         if (p) {
9626             q = strchr(p, '}');
9627             if (q) {
9628                 *q = NULLCHAR;
9629                 r = p + 1;
9630             }
9631         }
9632
9633         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9634         return;
9635
9636     } else if (strncmp(message, "White resign", 12) == 0) {
9637         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9638         return;
9639     } else if (strncmp(message, "Black resign", 12) == 0) {
9640         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9641         return;
9642     } else if (strncmp(message, "White matches", 13) == 0 ||
9643                strncmp(message, "Black matches", 13) == 0   ) {
9644         /* [HGM] ignore GNUShogi noises */
9645         return;
9646     } else if (strncmp(message, "White", 5) == 0 &&
9647                message[5] != '(' &&
9648                StrStr(message, "Black") == NULL) {
9649         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9650         return;
9651     } else if (strncmp(message, "Black", 5) == 0 &&
9652                message[5] != '(') {
9653         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9654         return;
9655     } else if (strcmp(message, "resign") == 0 ||
9656                strcmp(message, "computer resigns") == 0) {
9657         switch (gameMode) {
9658           case MachinePlaysBlack:
9659           case IcsPlayingBlack:
9660             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9661             break;
9662           case MachinePlaysWhite:
9663           case IcsPlayingWhite:
9664             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9665             break;
9666           case TwoMachinesPlay:
9667             if (cps->twoMachinesColor[0] == 'w')
9668               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9669             else
9670               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9671             break;
9672           default:
9673             /* can't happen */
9674             break;
9675         }
9676         return;
9677     } else if (strncmp(message, "opponent mates", 14) == 0) {
9678         switch (gameMode) {
9679           case MachinePlaysBlack:
9680           case IcsPlayingBlack:
9681             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9682             break;
9683           case MachinePlaysWhite:
9684           case IcsPlayingWhite:
9685             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9686             break;
9687           case TwoMachinesPlay:
9688             if (cps->twoMachinesColor[0] == 'w')
9689               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9690             else
9691               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9692             break;
9693           default:
9694             /* can't happen */
9695             break;
9696         }
9697         return;
9698     } else if (strncmp(message, "computer mates", 14) == 0) {
9699         switch (gameMode) {
9700           case MachinePlaysBlack:
9701           case IcsPlayingBlack:
9702             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9703             break;
9704           case MachinePlaysWhite:
9705           case IcsPlayingWhite:
9706             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9707             break;
9708           case TwoMachinesPlay:
9709             if (cps->twoMachinesColor[0] == 'w')
9710               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9711             else
9712               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9713             break;
9714           default:
9715             /* can't happen */
9716             break;
9717         }
9718         return;
9719     } else if (strncmp(message, "checkmate", 9) == 0) {
9720         if (WhiteOnMove(forwardMostMove)) {
9721             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9722         } else {
9723             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9724         }
9725         return;
9726     } else if (strstr(message, "Draw") != NULL ||
9727                strstr(message, "game is a draw") != NULL) {
9728         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9729         return;
9730     } else if (strstr(message, "offer") != NULL &&
9731                strstr(message, "draw") != NULL) {
9732 #if ZIPPY
9733         if (appData.zippyPlay && first.initDone) {
9734             /* Relay offer to ICS */
9735             SendToICS(ics_prefix);
9736             SendToICS("draw\n");
9737         }
9738 #endif
9739         cps->offeredDraw = 2; /* valid until this engine moves twice */
9740         if (gameMode == TwoMachinesPlay) {
9741             if (cps->other->offeredDraw) {
9742                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9743             /* [HGM] in two-machine mode we delay relaying draw offer      */
9744             /* until after we also have move, to see if it is really claim */
9745             }
9746         } else if (gameMode == MachinePlaysWhite ||
9747                    gameMode == MachinePlaysBlack) {
9748           if (userOfferedDraw) {
9749             DisplayInformation(_("Machine accepts your draw offer"));
9750             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9751           } else {
9752             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9753           }
9754         }
9755     }
9756
9757
9758     /*
9759      * Look for thinking output
9760      */
9761     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9762           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9763                                 ) {
9764         int plylev, mvleft, mvtot, curscore, time;
9765         char mvname[MOVE_LEN];
9766         u64 nodes; // [DM]
9767         char plyext;
9768         int ignore = FALSE;
9769         int prefixHint = FALSE;
9770         mvname[0] = NULLCHAR;
9771
9772         switch (gameMode) {
9773           case MachinePlaysBlack:
9774           case IcsPlayingBlack:
9775             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9776             break;
9777           case MachinePlaysWhite:
9778           case IcsPlayingWhite:
9779             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9780             break;
9781           case AnalyzeMode:
9782           case AnalyzeFile:
9783             break;
9784           case IcsObserving: /* [DM] icsEngineAnalyze */
9785             if (!appData.icsEngineAnalyze) ignore = TRUE;
9786             break;
9787           case TwoMachinesPlay:
9788             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9789                 ignore = TRUE;
9790             }
9791             break;
9792           default:
9793             ignore = TRUE;
9794             break;
9795         }
9796
9797         if (!ignore) {
9798             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9799             int solved = 0;
9800             buf1[0] = NULLCHAR;
9801             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9802                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9803                 char score_buf[MSG_SIZ];
9804
9805                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9806                     nodes += u64Const(0x100000000);
9807
9808                 if (plyext != ' ' && plyext != '\t') {
9809                     time *= 100;
9810                 }
9811
9812                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9813                 if( cps->scoreIsAbsolute &&
9814                     ( gameMode == MachinePlaysBlack ||
9815                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9816                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9817                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9818                      !WhiteOnMove(currentMove)
9819                     ) )
9820                 {
9821                     curscore = -curscore;
9822                 }
9823
9824                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9825
9826                 if(*bestMove) { // rememer time best EPD move was first found
9827                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9828                     ChessMove mt; char *p = bestMove;
9829                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9830                     solved = 0;
9831                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9832                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9833                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9834                             solved = 1;
9835                             break;
9836                         }
9837                         while(*p && *p != ' ') p++;
9838                         while(*p == ' ') p++;
9839                     }
9840                     if(!solved) solvingTime = -1;
9841                 }
9842                 if(*avoidMove && !solved) {
9843                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9844                     ChessMove mt; char *p = avoidMove, solved = 1;
9845                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9846                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9847                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9848                             solved = 0; solvingTime = -2;
9849                             break;
9850                         }
9851                         while(*p && *p != ' ') p++;
9852                         while(*p == ' ') p++;
9853                     }
9854                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9855                 }
9856
9857                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9858                         char buf[MSG_SIZ];
9859                         FILE *f;
9860                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9861                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9862                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9863                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9864                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9865                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9866                                 fclose(f);
9867                         }
9868                         else
9869                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9870                           DisplayError(_("failed writing PV"), 0);
9871                 }
9872
9873                 tempStats.depth = plylev;
9874                 tempStats.nodes = nodes;
9875                 tempStats.time = time;
9876                 tempStats.score = curscore;
9877                 tempStats.got_only_move = 0;
9878
9879                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9880                         int ticklen;
9881
9882                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9883                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9884                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9885                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9886                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9887                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9888                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9889                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9890                 }
9891
9892                 /* Buffer overflow protection */
9893                 if (pv[0] != NULLCHAR) {
9894                     if (strlen(pv) >= sizeof(tempStats.movelist)
9895                         && appData.debugMode) {
9896                         fprintf(debugFP,
9897                                 "PV is too long; using the first %u bytes.\n",
9898                                 (unsigned) sizeof(tempStats.movelist) - 1);
9899                     }
9900
9901                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9902                 } else {
9903                     sprintf(tempStats.movelist, " no PV\n");
9904                 }
9905
9906                 if (tempStats.seen_stat) {
9907                     tempStats.ok_to_send = 1;
9908                 }
9909
9910                 if (strchr(tempStats.movelist, '(') != NULL) {
9911                     tempStats.line_is_book = 1;
9912                     tempStats.nr_moves = 0;
9913                     tempStats.moves_left = 0;
9914                 } else {
9915                     tempStats.line_is_book = 0;
9916                 }
9917
9918                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9919                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9920
9921                 SendProgramStatsToFrontend( cps, &tempStats );
9922
9923                 /*
9924                     [AS] Protect the thinkOutput buffer from overflow... this
9925                     is only useful if buf1 hasn't overflowed first!
9926                 */
9927                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9928                 if(curscore >= MATE_SCORE) 
9929                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9930                 else if(curscore <= -MATE_SCORE) 
9931                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9932                 else
9933                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9934                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9935                          plylev,
9936                          (gameMode == TwoMachinesPlay ?
9937                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9938                          score_buf,
9939                          prefixHint ? lastHint : "",
9940                          prefixHint ? " " : "" );
9941
9942                 if( buf1[0] != NULLCHAR ) {
9943                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9944
9945                     if( strlen(pv) > max_len ) {
9946                         if( appData.debugMode) {
9947                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9948                         }
9949                         pv[max_len+1] = '\0';
9950                     }
9951
9952                     strcat( thinkOutput, pv);
9953                 }
9954
9955                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9956                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9957                     DisplayMove(currentMove - 1);
9958                 }
9959                 return;
9960
9961             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9962                 /* crafty (9.25+) says "(only move) <move>"
9963                  * if there is only 1 legal move
9964                  */
9965                 sscanf(p, "(only move) %s", buf1);
9966                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9967                 sprintf(programStats.movelist, "%s (only move)", buf1);
9968                 programStats.depth = 1;
9969                 programStats.nr_moves = 1;
9970                 programStats.moves_left = 1;
9971                 programStats.nodes = 1;
9972                 programStats.time = 1;
9973                 programStats.got_only_move = 1;
9974
9975                 /* Not really, but we also use this member to
9976                    mean "line isn't going to change" (Crafty
9977                    isn't searching, so stats won't change) */
9978                 programStats.line_is_book = 1;
9979
9980                 SendProgramStatsToFrontend( cps, &programStats );
9981
9982                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9983                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9984                     DisplayMove(currentMove - 1);
9985                 }
9986                 return;
9987             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9988                               &time, &nodes, &plylev, &mvleft,
9989                               &mvtot, mvname) >= 5) {
9990                 /* The stat01: line is from Crafty (9.29+) in response
9991                    to the "." command */
9992                 programStats.seen_stat = 1;
9993                 cps->maybeThinking = TRUE;
9994
9995                 if (programStats.got_only_move || !appData.periodicUpdates)
9996                   return;
9997
9998                 programStats.depth = plylev;
9999                 programStats.time = time;
10000                 programStats.nodes = nodes;
10001                 programStats.moves_left = mvleft;
10002                 programStats.nr_moves = mvtot;
10003                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
10004                 programStats.ok_to_send = 1;
10005                 programStats.movelist[0] = '\0';
10006
10007                 SendProgramStatsToFrontend( cps, &programStats );
10008
10009                 return;
10010
10011             } else if (strncmp(message,"++",2) == 0) {
10012                 /* Crafty 9.29+ outputs this */
10013                 programStats.got_fail = 2;
10014                 return;
10015
10016             } else if (strncmp(message,"--",2) == 0) {
10017                 /* Crafty 9.29+ outputs this */
10018                 programStats.got_fail = 1;
10019                 return;
10020
10021             } else if (thinkOutput[0] != NULLCHAR &&
10022                        strncmp(message, "    ", 4) == 0) {
10023                 unsigned message_len;
10024
10025                 p = message;
10026                 while (*p && *p == ' ') p++;
10027
10028                 message_len = strlen( p );
10029
10030                 /* [AS] Avoid buffer overflow */
10031                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
10032                     strcat(thinkOutput, " ");
10033                     strcat(thinkOutput, p);
10034                 }
10035
10036                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
10037                     strcat(programStats.movelist, " ");
10038                     strcat(programStats.movelist, p);
10039                 }
10040
10041                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10042                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10043                     DisplayMove(currentMove - 1);
10044                 }
10045                 return;
10046             }
10047         }
10048         else {
10049             buf1[0] = NULLCHAR;
10050
10051             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
10052                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
10053             {
10054                 ChessProgramStats cpstats;
10055
10056                 if (plyext != ' ' && plyext != '\t') {
10057                     time *= 100;
10058                 }
10059
10060                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10061                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10062                     curscore = -curscore;
10063                 }
10064
10065                 cpstats.depth = plylev;
10066                 cpstats.nodes = nodes;
10067                 cpstats.time = time;
10068                 cpstats.score = curscore;
10069                 cpstats.got_only_move = 0;
10070                 cpstats.movelist[0] = '\0';
10071
10072                 if (buf1[0] != NULLCHAR) {
10073                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10074                 }
10075
10076                 cpstats.ok_to_send = 0;
10077                 cpstats.line_is_book = 0;
10078                 cpstats.nr_moves = 0;
10079                 cpstats.moves_left = 0;
10080
10081                 SendProgramStatsToFrontend( cps, &cpstats );
10082             }
10083         }
10084     }
10085 }
10086
10087
10088 /* Parse a game score from the character string "game", and
10089    record it as the history of the current game.  The game
10090    score is NOT assumed to start from the standard position.
10091    The display is not updated in any way.
10092    */
10093 void
10094 ParseGameHistory (char *game)
10095 {
10096     ChessMove moveType;
10097     int fromX, fromY, toX, toY, boardIndex, mask;
10098     char promoChar;
10099     char *p, *q;
10100     char buf[MSG_SIZ];
10101
10102     if (appData.debugMode)
10103       fprintf(debugFP, "Parsing game history: %s\n", game);
10104
10105     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10106     gameInfo.site = StrSave(appData.icsHost);
10107     gameInfo.date = PGNDate();
10108     gameInfo.round = StrSave("-");
10109
10110     /* Parse out names of players */
10111     while (*game == ' ') game++;
10112     p = buf;
10113     while (*game != ' ') *p++ = *game++;
10114     *p = NULLCHAR;
10115     gameInfo.white = StrSave(buf);
10116     while (*game == ' ') game++;
10117     p = buf;
10118     while (*game != ' ' && *game != '\n') *p++ = *game++;
10119     *p = NULLCHAR;
10120     gameInfo.black = StrSave(buf);
10121
10122     /* Parse moves */
10123     boardIndex = blackPlaysFirst ? 1 : 0;
10124     yynewstr(game);
10125     for (;;) {
10126         yyboardindex = boardIndex;
10127         moveType = (ChessMove) Myylex();
10128         switch (moveType) {
10129           case IllegalMove:             /* maybe suicide chess, etc. */
10130   if (appData.debugMode) {
10131     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10132     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10133     setbuf(debugFP, NULL);
10134   }
10135           case WhitePromotion:
10136           case BlackPromotion:
10137           case WhiteNonPromotion:
10138           case BlackNonPromotion:
10139           case NormalMove:
10140           case FirstLeg:
10141           case WhiteCapturesEnPassant:
10142           case BlackCapturesEnPassant:
10143           case WhiteKingSideCastle:
10144           case WhiteQueenSideCastle:
10145           case BlackKingSideCastle:
10146           case BlackQueenSideCastle:
10147           case WhiteKingSideCastleWild:
10148           case WhiteQueenSideCastleWild:
10149           case BlackKingSideCastleWild:
10150           case BlackQueenSideCastleWild:
10151           /* PUSH Fabien */
10152           case WhiteHSideCastleFR:
10153           case WhiteASideCastleFR:
10154           case BlackHSideCastleFR:
10155           case BlackASideCastleFR:
10156           /* POP Fabien */
10157             fromX = currentMoveString[0] - AAA;
10158             fromY = currentMoveString[1] - ONE;
10159             toX = currentMoveString[2] - AAA;
10160             toY = currentMoveString[3] - ONE;
10161             promoChar = currentMoveString[4];
10162             break;
10163           case WhiteDrop:
10164           case BlackDrop:
10165             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10166             fromX = moveType == WhiteDrop ?
10167               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10168             (int) CharToPiece(ToLower(currentMoveString[0]));
10169             fromY = DROP_RANK;
10170             toX = currentMoveString[2] - AAA;
10171             toY = currentMoveString[3] - ONE;
10172             promoChar = NULLCHAR;
10173             break;
10174           case AmbiguousMove:
10175             /* bug? */
10176             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10177   if (appData.debugMode) {
10178     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10179     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10180     setbuf(debugFP, NULL);
10181   }
10182             DisplayError(buf, 0);
10183             return;
10184           case ImpossibleMove:
10185             /* bug? */
10186             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10187   if (appData.debugMode) {
10188     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10189     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10190     setbuf(debugFP, NULL);
10191   }
10192             DisplayError(buf, 0);
10193             return;
10194           case EndOfFile:
10195             if (boardIndex < backwardMostMove) {
10196                 /* Oops, gap.  How did that happen? */
10197                 DisplayError(_("Gap in move list"), 0);
10198                 return;
10199             }
10200             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10201             if (boardIndex > forwardMostMove) {
10202                 forwardMostMove = boardIndex;
10203             }
10204             return;
10205           case ElapsedTime:
10206             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10207                 strcat(parseList[boardIndex-1], " ");
10208                 strcat(parseList[boardIndex-1], yy_text);
10209             }
10210             continue;
10211           case Comment:
10212           case PGNTag:
10213           case NAG:
10214           default:
10215             /* ignore */
10216             continue;
10217           case WhiteWins:
10218           case BlackWins:
10219           case GameIsDrawn:
10220           case GameUnfinished:
10221             if (gameMode == IcsExamining) {
10222                 if (boardIndex < backwardMostMove) {
10223                     /* Oops, gap.  How did that happen? */
10224                     return;
10225                 }
10226                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10227                 return;
10228             }
10229             gameInfo.result = moveType;
10230             p = strchr(yy_text, '{');
10231             if (p == NULL) p = strchr(yy_text, '(');
10232             if (p == NULL) {
10233                 p = yy_text;
10234                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10235             } else {
10236                 q = strchr(p, *p == '{' ? '}' : ')');
10237                 if (q != NULL) *q = NULLCHAR;
10238                 p++;
10239             }
10240             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10241             gameInfo.resultDetails = StrSave(p);
10242             continue;
10243         }
10244         if (boardIndex >= forwardMostMove &&
10245             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10246             backwardMostMove = blackPlaysFirst ? 1 : 0;
10247             return;
10248         }
10249         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10250                                  fromY, fromX, toY, toX, promoChar,
10251                                  parseList[boardIndex]);
10252         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10253         /* currentMoveString is set as a side-effect of yylex */
10254         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10255         strcat(moveList[boardIndex], "\n");
10256         boardIndex++;
10257         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10258         mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10259         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10260           case MT_NONE:
10261           case MT_STALEMATE:
10262           default:
10263             break;
10264           case MT_CHECK:
10265             if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10266             if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10267                 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10268                 break;
10269             }
10270           case MT_CHECKMATE:
10271           case MT_STAINMATE:
10272             strcat(parseList[boardIndex - 1], "#");
10273             break;
10274         }
10275     }
10276 }
10277
10278
10279 /* Apply a move to the given board  */
10280 void
10281 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10282 {
10283   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10284   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10285
10286     /* [HGM] compute & store e.p. status and castling rights for new position */
10287     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10288
10289       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10290       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10291       board[EP_STATUS] = EP_NONE;
10292       board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10293
10294   if (fromY == DROP_RANK) {
10295         /* must be first */
10296         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10297             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10298             return;
10299         }
10300         piece = board[toY][toX] = (ChessSquare) fromX;
10301   } else {
10302 //      ChessSquare victim;
10303       int i;
10304
10305       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10306 //           victim = board[killY][killX],
10307            killed = board[killY][killX],
10308            board[killY][killX] = EmptySquare,
10309            board[EP_STATUS] = EP_CAPTURE;
10310            if( kill2X >= 0 && kill2Y >= 0)
10311              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10312       }
10313
10314       if( board[toY][toX] != EmptySquare ) {
10315            board[EP_STATUS] = EP_CAPTURE;
10316            if( (fromX != toX || fromY != toY) && // not igui!
10317                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10318                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10319                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10320            }
10321       }
10322
10323       pawn = board[fromY][fromX];
10324       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10325         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10326             captured = board[lastRank][lastFile]; // remove victim
10327             board[lastRank][lastFile] = EmptySquare;
10328             pawn = EmptySquare; // kludge to suppress old e.p. code
10329         }
10330       }
10331       if( pawn == WhiteLance || pawn == BlackLance ) {
10332            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10333                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10334                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10335            }
10336       }
10337       if( pawn == WhitePawn ) {
10338            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10339                board[EP_STATUS] = EP_PAWN_MOVE;
10340            if( toY-fromY>=2) {
10341                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10342                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10343                         gameInfo.variant != VariantBerolina || toX < fromX)
10344                       board[EP_STATUS] = toX | berolina;
10345                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10346                         gameInfo.variant != VariantBerolina || toX > fromX)
10347                       board[EP_STATUS] = toX;
10348                board[LAST_TO] = toX + 256*toY;
10349            }
10350       } else
10351       if( pawn == BlackPawn ) {
10352            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10353                board[EP_STATUS] = EP_PAWN_MOVE;
10354            if( toY-fromY<= -2) {
10355                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10356                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10357                         gameInfo.variant != VariantBerolina || toX < fromX)
10358                       board[EP_STATUS] = toX | berolina;
10359                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10360                         gameInfo.variant != VariantBerolina || toX > fromX)
10361                       board[EP_STATUS] = toX;
10362                board[LAST_TO] = toX + 256*toY;
10363            }
10364        }
10365
10366        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10367        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10368        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10369        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10370
10371        for(i=0; i<nrCastlingRights; i++) {
10372            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10373               board[CASTLING][i] == toX   && castlingRank[i] == toY
10374              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10375        }
10376
10377        if(gameInfo.variant == VariantSChess) { // update virginity
10378            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10379            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10380            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10381            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10382        }
10383
10384      if (fromX == toX && fromY == toY && killX < 0) return;
10385
10386      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10387      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10388      if(gameInfo.variant == VariantKnightmate)
10389          king += (int) WhiteUnicorn - (int) WhiteKing;
10390
10391     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10392        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10393         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10394         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10395         board[EP_STATUS] = EP_NONE; // capture was fake!
10396     } else
10397     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10398         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10399         board[toY][toX] = piece;
10400         board[EP_STATUS] = EP_NONE; // capture was fake!
10401     } else
10402     /* Code added by Tord: */
10403     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10404     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10405         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10406       board[EP_STATUS] = EP_NONE; // capture was fake!
10407       board[fromY][fromX] = EmptySquare;
10408       board[toY][toX] = EmptySquare;
10409       if((toX > fromX) != (piece == WhiteRook)) {
10410         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10411       } else {
10412         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10413       }
10414     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10415                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10416       board[EP_STATUS] = EP_NONE;
10417       board[fromY][fromX] = EmptySquare;
10418       board[toY][toX] = EmptySquare;
10419       if((toX > fromX) != (piece == BlackRook)) {
10420         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10421       } else {
10422         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10423       }
10424     /* End of code added by Tord */
10425
10426     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10427         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10428         board[toY][toX] = piece;
10429     } else if (board[fromY][fromX] == king
10430         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10431         && toY == fromY && toX > fromX+1) {
10432         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10433                                                                                              ; // castle with nearest piece
10434         board[fromY][toX-1] = board[fromY][rookX];
10435         board[fromY][rookX] = EmptySquare;
10436         board[fromY][fromX] = EmptySquare;
10437         board[toY][toX] = king;
10438     } else if (board[fromY][fromX] == king
10439         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10440                && toY == fromY && toX < fromX-1) {
10441         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10442                                                                                   ; // castle with nearest piece
10443         board[fromY][toX+1] = board[fromY][rookX];
10444         board[fromY][rookX] = EmptySquare;
10445         board[fromY][fromX] = EmptySquare;
10446         board[toY][toX] = king;
10447     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10448                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10449                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10450                ) {
10451         /* white pawn promotion */
10452         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10453         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10454             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10455         board[fromY][fromX] = EmptySquare;
10456     } else if ((fromY >= BOARD_HEIGHT>>1)
10457                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10458                && (toX != fromX)
10459                && gameInfo.variant != VariantXiangqi
10460                && gameInfo.variant != VariantBerolina
10461                && (pawn == WhitePawn)
10462                && (board[toY][toX] == EmptySquare)) {
10463         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10464         board[fromY][fromX] = EmptySquare;
10465         board[toY][toX] = piece;
10466         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10467     } else if ((fromY == BOARD_HEIGHT-4)
10468                && (toX == fromX)
10469                && gameInfo.variant == VariantBerolina
10470                && (board[fromY][fromX] == WhitePawn)
10471                && (board[toY][toX] == EmptySquare)) {
10472         board[fromY][fromX] = EmptySquare;
10473         board[toY][toX] = WhitePawn;
10474         if(oldEP & EP_BEROLIN_A) {
10475                 captured = board[fromY][fromX-1];
10476                 board[fromY][fromX-1] = EmptySquare;
10477         }else{  captured = board[fromY][fromX+1];
10478                 board[fromY][fromX+1] = EmptySquare;
10479         }
10480     } else if (board[fromY][fromX] == king
10481         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10482                && toY == fromY && toX > fromX+1) {
10483         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10484                                                                                              ;
10485         board[fromY][toX-1] = board[fromY][rookX];
10486         board[fromY][rookX] = EmptySquare;
10487         board[fromY][fromX] = EmptySquare;
10488         board[toY][toX] = king;
10489     } else if (board[fromY][fromX] == king
10490         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10491                && toY == fromY && toX < fromX-1) {
10492         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10493                                                                                 ;
10494         board[fromY][toX+1] = board[fromY][rookX];
10495         board[fromY][rookX] = EmptySquare;
10496         board[fromY][fromX] = EmptySquare;
10497         board[toY][toX] = king;
10498     } else if (fromY == 7 && fromX == 3
10499                && board[fromY][fromX] == BlackKing
10500                && toY == 7 && toX == 5) {
10501         board[fromY][fromX] = EmptySquare;
10502         board[toY][toX] = BlackKing;
10503         board[fromY][7] = EmptySquare;
10504         board[toY][4] = BlackRook;
10505     } else if (fromY == 7 && fromX == 3
10506                && board[fromY][fromX] == BlackKing
10507                && toY == 7 && toX == 1) {
10508         board[fromY][fromX] = EmptySquare;
10509         board[toY][toX] = BlackKing;
10510         board[fromY][0] = EmptySquare;
10511         board[toY][2] = BlackRook;
10512     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10513                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10514                && toY < promoRank && promoChar
10515                ) {
10516         /* black pawn promotion */
10517         board[toY][toX] = CharToPiece(ToLower(promoChar));
10518         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10519             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10520         board[fromY][fromX] = EmptySquare;
10521     } else if ((fromY < BOARD_HEIGHT>>1)
10522                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10523                && (toX != fromX)
10524                && gameInfo.variant != VariantXiangqi
10525                && gameInfo.variant != VariantBerolina
10526                && (pawn == BlackPawn)
10527                && (board[toY][toX] == EmptySquare)) {
10528         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10529         board[fromY][fromX] = EmptySquare;
10530         board[toY][toX] = piece;
10531         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10532     } else if ((fromY == 3)
10533                && (toX == fromX)
10534                && gameInfo.variant == VariantBerolina
10535                && (board[fromY][fromX] == BlackPawn)
10536                && (board[toY][toX] == EmptySquare)) {
10537         board[fromY][fromX] = EmptySquare;
10538         board[toY][toX] = BlackPawn;
10539         if(oldEP & EP_BEROLIN_A) {
10540                 captured = board[fromY][fromX-1];
10541                 board[fromY][fromX-1] = EmptySquare;
10542         }else{  captured = board[fromY][fromX+1];
10543                 board[fromY][fromX+1] = EmptySquare;
10544         }
10545     } else {
10546         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10547         board[fromY][fromX] = EmptySquare;
10548         board[toY][toX] = piece;
10549     }
10550   }
10551
10552     if (gameInfo.holdingsWidth != 0) {
10553
10554       /* !!A lot more code needs to be written to support holdings  */
10555       /* [HGM] OK, so I have written it. Holdings are stored in the */
10556       /* penultimate board files, so they are automaticlly stored   */
10557       /* in the game history.                                       */
10558       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10559                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10560         /* Delete from holdings, by decreasing count */
10561         /* and erasing image if necessary            */
10562         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10563         if(p < (int) BlackPawn) { /* white drop */
10564              p -= (int)WhitePawn;
10565                  p = PieceToNumber((ChessSquare)p);
10566              if(p >= gameInfo.holdingsSize) p = 0;
10567              if(--board[p][BOARD_WIDTH-2] <= 0)
10568                   board[p][BOARD_WIDTH-1] = EmptySquare;
10569              if((int)board[p][BOARD_WIDTH-2] < 0)
10570                         board[p][BOARD_WIDTH-2] = 0;
10571         } else {                  /* black drop */
10572              p -= (int)BlackPawn;
10573                  p = PieceToNumber((ChessSquare)p);
10574              if(p >= gameInfo.holdingsSize) p = 0;
10575              if(--board[handSize-1-p][1] <= 0)
10576                   board[handSize-1-p][0] = EmptySquare;
10577              if((int)board[handSize-1-p][1] < 0)
10578                         board[handSize-1-p][1] = 0;
10579         }
10580       }
10581       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10582           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10583         /* [HGM] holdings: Add to holdings, if holdings exist */
10584         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10585                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10586                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10587         }
10588         p = (int) captured;
10589         if (p >= (int) BlackPawn) {
10590           p -= (int)BlackPawn;
10591           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10592                   /* Restore shogi-promoted piece to its original  first */
10593                   captured = (ChessSquare) (DEMOTED(captured));
10594                   p = DEMOTED(p);
10595           }
10596           p = PieceToNumber((ChessSquare)p);
10597           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10598           board[p][BOARD_WIDTH-2]++;
10599           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10600         } else {
10601           p -= (int)WhitePawn;
10602           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10603                   captured = (ChessSquare) (DEMOTED(captured));
10604                   p = DEMOTED(p);
10605           }
10606           p = PieceToNumber((ChessSquare)p);
10607           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10608           board[handSize-1-p][1]++;
10609           board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10610         }
10611       }
10612     } else if (gameInfo.variant == VariantAtomic) {
10613       if (captured != EmptySquare) {
10614         int y, x;
10615         for (y = toY-1; y <= toY+1; y++) {
10616           for (x = toX-1; x <= toX+1; x++) {
10617             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10618                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10619               board[y][x] = EmptySquare;
10620             }
10621           }
10622         }
10623         board[toY][toX] = EmptySquare;
10624       }
10625     }
10626
10627     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10628         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10629     } else
10630     if(promoChar == '+') {
10631         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10632         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10633         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10634           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10635     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10636         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10637         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10638            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10639         board[toY][toX] = newPiece;
10640     }
10641     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10642                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10643         // [HGM] superchess: take promotion piece out of holdings
10644         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10645         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10646             if(!--board[k][BOARD_WIDTH-2])
10647                 board[k][BOARD_WIDTH-1] = EmptySquare;
10648         } else {
10649             if(!--board[handSize-1-k][1])
10650                 board[handSize-1-k][0] = EmptySquare;
10651         }
10652     }
10653 }
10654
10655 /* Updates forwardMostMove */
10656 void
10657 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10658 {
10659     int x = toX, y = toY, mask;
10660     char *s = parseList[forwardMostMove];
10661     ChessSquare p = boards[forwardMostMove][toY][toX];
10662 //    forwardMostMove++; // [HGM] bare: moved downstream
10663
10664     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10665     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10666     (void) CoordsToAlgebraic(boards[forwardMostMove],
10667                              PosFlags(forwardMostMove),
10668                              fromY, fromX, y, x, (killX < 0)*promoChar,
10669                              s);
10670     if(kill2X >= 0 && kill2Y >= 0)
10671         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10672     if(killX >= 0 && killY >= 0)
10673         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10674                                            toX + AAA, toY + ONE - '0', promoChar);
10675
10676     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10677         int timeLeft; static int lastLoadFlag=0; int king, piece;
10678         piece = boards[forwardMostMove][fromY][fromX];
10679         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10680         if(gameInfo.variant == VariantKnightmate)
10681             king += (int) WhiteUnicorn - (int) WhiteKing;
10682         if(forwardMostMove == 0) {
10683             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10684                 fprintf(serverMoves, "%s;", UserName());
10685             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10686                 fprintf(serverMoves, "%s;", second.tidy);
10687             fprintf(serverMoves, "%s;", first.tidy);
10688             if(gameMode == MachinePlaysWhite)
10689                 fprintf(serverMoves, "%s;", UserName());
10690             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10691                 fprintf(serverMoves, "%s;", second.tidy);
10692         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10693         lastLoadFlag = loadFlag;
10694         // print base move
10695         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10696         // print castling suffix
10697         if( toY == fromY && piece == king ) {
10698             if(toX-fromX > 1)
10699                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10700             if(fromX-toX >1)
10701                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10702         }
10703         // e.p. suffix
10704         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10705              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10706              boards[forwardMostMove][toY][toX] == EmptySquare
10707              && fromX != toX && fromY != toY)
10708                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10709         // promotion suffix
10710         if(promoChar != NULLCHAR) {
10711             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10712                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10713                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10714             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10715         }
10716         if(!loadFlag) {
10717                 char buf[MOVE_LEN*2], *p; int len;
10718             fprintf(serverMoves, "/%d/%d",
10719                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10720             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10721             else                      timeLeft = blackTimeRemaining/1000;
10722             fprintf(serverMoves, "/%d", timeLeft);
10723                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10724                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10725                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10726                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10727             fprintf(serverMoves, "/%s", buf);
10728         }
10729         fflush(serverMoves);
10730     }
10731
10732     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10733         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10734       return;
10735     }
10736     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10737     if (commentList[forwardMostMove+1] != NULL) {
10738         free(commentList[forwardMostMove+1]);
10739         commentList[forwardMostMove+1] = NULL;
10740     }
10741     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10742     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10743     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10744     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10745     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10746     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10747     adjustedClock = FALSE;
10748     gameInfo.result = GameUnfinished;
10749     if (gameInfo.resultDetails != NULL) {
10750         free(gameInfo.resultDetails);
10751         gameInfo.resultDetails = NULL;
10752     }
10753     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10754                               moveList[forwardMostMove - 1]);
10755     mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10756     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10757       case MT_NONE:
10758       case MT_STALEMATE:
10759       default:
10760         break;
10761       case MT_CHECK:
10762         if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10763         if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10764             if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10765             break;
10766         }
10767       case MT_CHECKMATE:
10768       case MT_STAINMATE:
10769         strcat(parseList[forwardMostMove - 1], "#");
10770         break;
10771     }
10772 }
10773
10774 /* Updates currentMove if not pausing */
10775 void
10776 ShowMove (int fromX, int fromY, int toX, int toY)
10777 {
10778     int instant = (gameMode == PlayFromGameFile) ?
10779         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10780     if(appData.noGUI) return;
10781     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10782         if (!instant) {
10783             if (forwardMostMove == currentMove + 1) {
10784                 AnimateMove(boards[forwardMostMove - 1],
10785                             fromX, fromY, toX, toY);
10786             }
10787         }
10788         currentMove = forwardMostMove;
10789     }
10790
10791     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10792
10793     if (instant) return;
10794
10795     DisplayMove(currentMove - 1);
10796     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10797             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10798                 SetHighlights(fromX, fromY, toX, toY);
10799             }
10800     }
10801     DrawPosition(FALSE, boards[currentMove]);
10802     DisplayBothClocks();
10803     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10804 }
10805
10806 void
10807 SendEgtPath (ChessProgramState *cps)
10808 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10809         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10810
10811         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10812
10813         while(*p) {
10814             char c, *q = name+1, *r, *s;
10815
10816             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10817             while(*p && *p != ',') *q++ = *p++;
10818             *q++ = ':'; *q = 0;
10819             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10820                 strcmp(name, ",nalimov:") == 0 ) {
10821                 // take nalimov path from the menu-changeable option first, if it is defined
10822               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10823                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10824             } else
10825             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10826                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10827                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10828                 s = r = StrStr(s, ":") + 1; // beginning of path info
10829                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10830                 c = *r; *r = 0;             // temporarily null-terminate path info
10831                     *--q = 0;               // strip of trailig ':' from name
10832                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10833                 *r = c;
10834                 SendToProgram(buf,cps);     // send egtbpath command for this format
10835             }
10836             if(*p == ',') p++; // read away comma to position for next format name
10837         }
10838 }
10839
10840 static int
10841 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10842 {
10843       int width = 8, height = 8, holdings = 0;             // most common sizes
10844       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10845       // correct the deviations default for each variant
10846       if( v == VariantXiangqi ) width = 9,  height = 10;
10847       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10848       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10849       if( v == VariantCapablanca || v == VariantCapaRandom ||
10850           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10851                                 width = 10;
10852       if( v == VariantCourier ) width = 12;
10853       if( v == VariantSuper )                            holdings = 8;
10854       if( v == VariantGreat )   width = 10,              holdings = 8;
10855       if( v == VariantSChess )                           holdings = 7;
10856       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10857       if( v == VariantChuChess) width = 10, height = 10;
10858       if( v == VariantChu )     width = 12, height = 12;
10859       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10860              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10861              holdingsSize >= 0 && holdingsSize != holdings;
10862 }
10863
10864 char variantError[MSG_SIZ];
10865
10866 char *
10867 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10868 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10869       char *p, *variant = VariantName(v);
10870       static char b[MSG_SIZ];
10871       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10872            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10873                                                holdingsSize, variant); // cook up sized variant name
10874            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10875            if(StrStr(list, b) == NULL) {
10876                // specific sized variant not known, check if general sizing allowed
10877                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10878                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10879                             boardWidth, boardHeight, holdingsSize, engine);
10880                    return NULL;
10881                }
10882                /* [HGM] here we really should compare with the maximum supported board size */
10883            }
10884       } else snprintf(b, MSG_SIZ,"%s", variant);
10885       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10886       p = StrStr(list, b);
10887       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10888       if(p == NULL) {
10889           // occurs not at all in list, or only as sub-string
10890           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10891           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10892               int l = strlen(variantError);
10893               char *q;
10894               while(p != list && p[-1] != ',') p--;
10895               q = strchr(p, ',');
10896               if(q) *q = NULLCHAR;
10897               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10898               if(q) *q= ',';
10899           }
10900           return NULL;
10901       }
10902       return b;
10903 }
10904
10905 void
10906 InitChessProgram (ChessProgramState *cps, int setup)
10907 /* setup needed to setup FRC opening position */
10908 {
10909     char buf[MSG_SIZ], *b;
10910     if (appData.noChessProgram) return;
10911     hintRequested = FALSE;
10912     bookRequested = FALSE;
10913
10914     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10915     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10916     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10917     if(cps->memSize) { /* [HGM] memory */
10918       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10919         SendToProgram(buf, cps);
10920     }
10921     SendEgtPath(cps); /* [HGM] EGT */
10922     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10923       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10924         SendToProgram(buf, cps);
10925     }
10926
10927     setboardSpoiledMachineBlack = FALSE;
10928     SendToProgram(cps->initString, cps);
10929     if (gameInfo.variant != VariantNormal &&
10930         gameInfo.variant != VariantLoadable
10931         /* [HGM] also send variant if board size non-standard */
10932         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10933
10934       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10935                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10936
10937       if (b == NULL) {
10938         VariantClass v;
10939         char c, *q = cps->variants, *p = strchr(q, ',');
10940         if(p) *p = NULLCHAR;
10941         v = StringToVariant(q);
10942         DisplayError(variantError, 0);
10943         if(v != VariantUnknown && cps == &first) {
10944             int w, h, s;
10945             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10946                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10947             ASSIGN(appData.variant, q);
10948             Reset(TRUE, FALSE);
10949         }
10950         if(p) *p = ',';
10951         return;
10952       }
10953
10954       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10955       SendToProgram(buf, cps);
10956     }
10957     currentlyInitializedVariant = gameInfo.variant;
10958
10959     /* [HGM] send opening position in FRC to first engine */
10960     if(setup) {
10961           SendToProgram("force\n", cps);
10962           SendBoard(cps, 0);
10963           /* engine is now in force mode! Set flag to wake it up after first move. */
10964           setboardSpoiledMachineBlack = 1;
10965     }
10966
10967     if (cps->sendICS) {
10968       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10969       SendToProgram(buf, cps);
10970     }
10971     cps->maybeThinking = FALSE;
10972     cps->offeredDraw = 0;
10973     if (!appData.icsActive) {
10974         SendTimeControl(cps, movesPerSession, timeControl,
10975                         timeIncrement, appData.searchDepth,
10976                         searchTime);
10977     }
10978     if (appData.showThinking
10979         // [HGM] thinking: four options require thinking output to be sent
10980         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10981                                 ) {
10982         SendToProgram("post\n", cps);
10983     }
10984     SendToProgram("hard\n", cps);
10985     if (!appData.ponderNextMove) {
10986         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10987            it without being sure what state we are in first.  "hard"
10988            is not a toggle, so that one is OK.
10989          */
10990         SendToProgram("easy\n", cps);
10991     }
10992     if (cps->usePing) {
10993       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10994       SendToProgram(buf, cps);
10995     }
10996     cps->initDone = TRUE;
10997     ClearEngineOutputPane(cps == &second);
10998 }
10999
11000
11001 char *
11002 ResendOptions (ChessProgramState *cps, int toEngine)
11003 { // send the stored value of the options
11004   int i;
11005   static char buf2[MSG_SIZ*10];
11006   char buf[MSG_SIZ], *p = buf2;
11007   Option *opt = cps->option;
11008   *p = NULLCHAR;
11009   for(i=0; i<cps->nrOptions; i++, opt++) {
11010       *buf = NULLCHAR;
11011       switch(opt->type) {
11012         case Spin:
11013         case Slider:
11014         case CheckBox:
11015             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11016             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
11017           break;
11018         case ComboBox:
11019             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11020             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
11021           break;
11022         default:
11023             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
11024             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
11025           break;
11026         case Button:
11027         case SaveButton:
11028           continue;
11029       }
11030       if(*buf) {
11031         if(toEngine) {
11032           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
11033           SendToProgram(buf2, cps);
11034         } else {
11035           if(p != buf2) *p++ = ',';
11036           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
11037           while(*p) p++;
11038         }
11039       }
11040   }
11041   return buf2;
11042 }
11043
11044 void
11045 StartChessProgram (ChessProgramState *cps)
11046 {
11047     char buf[MSG_SIZ];
11048     int err;
11049
11050     if (appData.noChessProgram) return;
11051     cps->initDone = FALSE;
11052
11053     if (strcmp(cps->host, "localhost") == 0) {
11054         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
11055     } else if (*appData.remoteShell == NULLCHAR) {
11056         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
11057     } else {
11058         if (*appData.remoteUser == NULLCHAR) {
11059           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11060                     cps->program);
11061         } else {
11062           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11063                     cps->host, appData.remoteUser, cps->program);
11064         }
11065         err = StartChildProcess(buf, "", &cps->pr);
11066     }
11067
11068     if (err != 0) {
11069       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11070         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11071         if(cps != &first) return;
11072         appData.noChessProgram = TRUE;
11073         ThawUI();
11074         SetNCPMode();
11075 //      DisplayFatalError(buf, err, 1);
11076 //      cps->pr = NoProc;
11077 //      cps->isr = NULL;
11078         return;
11079     }
11080
11081     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11082     if (cps->protocolVersion > 1) {
11083       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11084       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11085         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11086         cps->comboCnt = 0;  //                and values of combo boxes
11087       }
11088       SendToProgram(buf, cps);
11089       if(cps->reload) ResendOptions(cps, TRUE);
11090     } else {
11091       SendToProgram("xboard\n", cps);
11092     }
11093 }
11094
11095 void
11096 TwoMachinesEventIfReady P((void))
11097 {
11098   static int curMess = 0;
11099   if (first.lastPing != first.lastPong) {
11100     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11101     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11102     return;
11103   }
11104   if (second.lastPing != second.lastPong) {
11105     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11106     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11107     return;
11108   }
11109   DisplayMessage("", ""); curMess = 0;
11110   TwoMachinesEvent();
11111 }
11112
11113 char *
11114 MakeName (char *template)
11115 {
11116     time_t clock;
11117     struct tm *tm;
11118     static char buf[MSG_SIZ];
11119     char *p = buf;
11120     int i;
11121
11122     clock = time((time_t *)NULL);
11123     tm = localtime(&clock);
11124
11125     while(*p++ = *template++) if(p[-1] == '%') {
11126         switch(*template++) {
11127           case 0:   *p = 0; return buf;
11128           case 'Y': i = tm->tm_year+1900; break;
11129           case 'y': i = tm->tm_year-100; break;
11130           case 'M': i = tm->tm_mon+1; break;
11131           case 'd': i = tm->tm_mday; break;
11132           case 'h': i = tm->tm_hour; break;
11133           case 'm': i = tm->tm_min; break;
11134           case 's': i = tm->tm_sec; break;
11135           default:  i = 0;
11136         }
11137         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11138     }
11139     return buf;
11140 }
11141
11142 int
11143 CountPlayers (char *p)
11144 {
11145     int n = 0;
11146     while(p = strchr(p, '\n')) p++, n++; // count participants
11147     return n;
11148 }
11149
11150 FILE *
11151 WriteTourneyFile (char *results, FILE *f)
11152 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11153     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11154     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11155         // create a file with tournament description
11156         fprintf(f, "-participants {%s}\n", appData.participants);
11157         fprintf(f, "-seedBase %d\n", appData.seedBase);
11158         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11159         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11160         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11161         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11162         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11163         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11164         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11165         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11166         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11167         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11168         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11169         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11170         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11171         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11172         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11173         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11174         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11175         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11176         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11177         fprintf(f, "-smpCores %d\n", appData.smpCores);
11178         if(searchTime > 0)
11179                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11180         else {
11181                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11182                 fprintf(f, "-tc %s\n", appData.timeControl);
11183                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11184         }
11185         fprintf(f, "-results \"%s\"\n", results);
11186     }
11187     return f;
11188 }
11189
11190 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11191
11192 void
11193 Substitute (char *participants, int expunge)
11194 {
11195     int i, changed, changes=0, nPlayers=0;
11196     char *p, *q, *r, buf[MSG_SIZ];
11197     if(participants == NULL) return;
11198     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11199     r = p = participants; q = appData.participants;
11200     while(*p && *p == *q) {
11201         if(*p == '\n') r = p+1, nPlayers++;
11202         p++; q++;
11203     }
11204     if(*p) { // difference
11205         while(*p && *p++ != '\n')
11206                                  ;
11207         while(*q && *q++ != '\n')
11208                                  ;
11209       changed = nPlayers;
11210         changes = 1 + (strcmp(p, q) != 0);
11211     }
11212     if(changes == 1) { // a single engine mnemonic was changed
11213         q = r; while(*q) nPlayers += (*q++ == '\n');
11214         p = buf; while(*r && (*p = *r++) != '\n') p++;
11215         *p = NULLCHAR;
11216         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11217         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11218         if(mnemonic[i]) { // The substitute is valid
11219             FILE *f;
11220             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11221                 flock(fileno(f), LOCK_EX);
11222                 ParseArgsFromFile(f);
11223                 fseek(f, 0, SEEK_SET);
11224                 FREE(appData.participants); appData.participants = participants;
11225                 if(expunge) { // erase results of replaced engine
11226                     int len = strlen(appData.results), w, b, dummy;
11227                     for(i=0; i<len; i++) {
11228                         Pairing(i, nPlayers, &w, &b, &dummy);
11229                         if((w == changed || b == changed) && appData.results[i] == '*') {
11230                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11231                             fclose(f);
11232                             return;
11233                         }
11234                     }
11235                     for(i=0; i<len; i++) {
11236                         Pairing(i, nPlayers, &w, &b, &dummy);
11237                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11238                     }
11239                 }
11240                 WriteTourneyFile(appData.results, f);
11241                 fclose(f); // release lock
11242                 return;
11243             }
11244         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11245     }
11246     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11247     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11248     free(participants);
11249     return;
11250 }
11251
11252 int
11253 CheckPlayers (char *participants)
11254 {
11255         int i;
11256         char buf[MSG_SIZ], *p;
11257         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11258         while(p = strchr(participants, '\n')) {
11259             *p = NULLCHAR;
11260             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11261             if(!mnemonic[i]) {
11262                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11263                 *p = '\n';
11264                 DisplayError(buf, 0);
11265                 return 1;
11266             }
11267             *p = '\n';
11268             participants = p + 1;
11269         }
11270         return 0;
11271 }
11272
11273 int
11274 CreateTourney (char *name)
11275 {
11276         FILE *f;
11277         if(matchMode && strcmp(name, appData.tourneyFile)) {
11278              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11279         }
11280         if(name[0] == NULLCHAR) {
11281             if(appData.participants[0])
11282                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11283             return 0;
11284         }
11285         f = fopen(name, "r");
11286         if(f) { // file exists
11287             ASSIGN(appData.tourneyFile, name);
11288             ParseArgsFromFile(f); // parse it
11289         } else {
11290             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11291             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11292                 DisplayError(_("Not enough participants"), 0);
11293                 return 0;
11294             }
11295             if(CheckPlayers(appData.participants)) return 0;
11296             ASSIGN(appData.tourneyFile, name);
11297             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11298             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11299         }
11300         fclose(f);
11301         appData.noChessProgram = FALSE;
11302         appData.clockMode = TRUE;
11303         SetGNUMode();
11304         return 1;
11305 }
11306
11307 int
11308 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11309 {
11310     char buf[2*MSG_SIZ], *p, *q;
11311     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11312     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11313     skip = !all && group[0]; // if group requested, we start in skip mode
11314     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11315         p = names; q = buf; header = 0;
11316         while(*p && *p != '\n') *q++ = *p++;
11317         *q = 0;
11318         if(*p == '\n') p++;
11319         if(buf[0] == '#') {
11320             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11321             depth++; // we must be entering a new group
11322             if(all) continue; // suppress printing group headers when complete list requested
11323             header = 1;
11324             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11325         }
11326         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11327         if(engineList[i]) free(engineList[i]);
11328         engineList[i] = strdup(buf);
11329         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11330         if(engineMnemonic[i]) free(engineMnemonic[i]);
11331         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11332             strcat(buf, " (");
11333             sscanf(q + 8, "%s", buf + strlen(buf));
11334             strcat(buf, ")");
11335         }
11336         engineMnemonic[i] = strdup(buf);
11337         i++;
11338     }
11339     engineList[i] = engineMnemonic[i] = NULL;
11340     return i;
11341 }
11342
11343 void
11344 SaveEngineSettings (int n)
11345 {
11346     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11347     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11348     p = strstr(firstChessProgramNames, currentEngine[n]);
11349     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11350     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11351     len = strlen(currentEngine[n]);
11352     q = p + len; *p = 0; // cut list into head and tail piece
11353     s = strstr(currentEngine[n], "firstOptions");
11354     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11355         char *r = s + 14;
11356         while(*r && *r != s[13]) r++;
11357         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11358         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11359     } else if(*optionSettings) {
11360         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11361     }
11362     ASSIGN(currentEngine[n], buf); // updated engine line
11363     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11364     s = malloc(len);
11365     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11366     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11367 }
11368
11369 // following implemented as macro to avoid type limitations
11370 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11371
11372 void
11373 SwapEngines (int n)
11374 {   // swap settings for first engine and other engine (so far only some selected options)
11375     int h;
11376     char *p;
11377     if(n == 0) return;
11378     SWAP(directory, p)
11379     SWAP(chessProgram, p)
11380     SWAP(isUCI, h)
11381     SWAP(hasOwnBookUCI, h)
11382     SWAP(protocolVersion, h)
11383     SWAP(reuse, h)
11384     SWAP(scoreIsAbsolute, h)
11385     SWAP(timeOdds, h)
11386     SWAP(logo, p)
11387     SWAP(pgnName, p)
11388     SWAP(pvSAN, h)
11389     SWAP(engOptions, p)
11390     SWAP(engInitString, p)
11391     SWAP(computerString, p)
11392     SWAP(features, p)
11393     SWAP(fenOverride, p)
11394     SWAP(NPS, h)
11395     SWAP(accumulateTC, h)
11396     SWAP(drawDepth, h)
11397     SWAP(host, p)
11398     SWAP(pseudo, h)
11399 }
11400
11401 int
11402 GetEngineLine (char *s, int n)
11403 {
11404     int i;
11405     char buf[MSG_SIZ];
11406     extern char *icsNames;
11407     if(!s || !*s) return 0;
11408     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11409     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11410     if(!mnemonic[i]) return 0;
11411     if(n == 11) return 1; // just testing if there was a match
11412     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11413     if(n == 1) SwapEngines(n);
11414     ParseArgsFromString(buf);
11415     if(n == 1) SwapEngines(n);
11416     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11417     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11418         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11419         ParseArgsFromString(buf);
11420     }
11421     return 1;
11422 }
11423
11424 int
11425 SetPlayer (int player, char *p)
11426 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11427     int i;
11428     char buf[MSG_SIZ], *engineName;
11429     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11430     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11431     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11432     if(mnemonic[i]) {
11433         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11434         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11435         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11436         ParseArgsFromString(buf);
11437     } else { // no engine with this nickname is installed!
11438         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11439         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11440         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11441         ModeHighlight();
11442         DisplayError(buf, 0);
11443         return 0;
11444     }
11445     free(engineName);
11446     return i;
11447 }
11448
11449 char *recentEngines;
11450
11451 void
11452 RecentEngineEvent (int nr)
11453 {
11454     int n;
11455 //    SwapEngines(1); // bump first to second
11456 //    ReplaceEngine(&second, 1); // and load it there
11457     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11458     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11459     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11460         ReplaceEngine(&first, 0);
11461         FloatToFront(&appData.recentEngineList, command[n]);
11462         ASSIGN(currentEngine[0], command[n]);
11463     }
11464 }
11465
11466 int
11467 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11468 {   // determine players from game number
11469     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11470
11471     if(appData.tourneyType == 0) {
11472         roundsPerCycle = (nPlayers - 1) | 1;
11473         pairingsPerRound = nPlayers / 2;
11474     } else if(appData.tourneyType > 0) {
11475         roundsPerCycle = nPlayers - appData.tourneyType;
11476         pairingsPerRound = appData.tourneyType;
11477     }
11478     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11479     gamesPerCycle = gamesPerRound * roundsPerCycle;
11480     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11481     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11482     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11483     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11484     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11485     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11486
11487     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11488     if(appData.roundSync) *syncInterval = gamesPerRound;
11489
11490     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11491
11492     if(appData.tourneyType == 0) {
11493         if(curPairing == (nPlayers-1)/2 ) {
11494             *whitePlayer = curRound;
11495             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11496         } else {
11497             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11498             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11499             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11500             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11501         }
11502     } else if(appData.tourneyType > 1) {
11503         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11504         *whitePlayer = curRound + appData.tourneyType;
11505     } else if(appData.tourneyType > 0) {
11506         *whitePlayer = curPairing;
11507         *blackPlayer = curRound + appData.tourneyType;
11508     }
11509
11510     // take care of white/black alternation per round.
11511     // For cycles and games this is already taken care of by default, derived from matchGame!
11512     return curRound & 1;
11513 }
11514
11515 int
11516 NextTourneyGame (int nr, int *swapColors)
11517 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11518     char *p, *q;
11519     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11520     FILE *tf;
11521     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11522     tf = fopen(appData.tourneyFile, "r");
11523     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11524     ParseArgsFromFile(tf); fclose(tf);
11525     InitTimeControls(); // TC might be altered from tourney file
11526
11527     nPlayers = CountPlayers(appData.participants); // count participants
11528     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11529     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11530
11531     if(syncInterval) {
11532         p = q = appData.results;
11533         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11534         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11535             DisplayMessage(_("Waiting for other game(s)"),"");
11536             waitingForGame = TRUE;
11537             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11538             return 0;
11539         }
11540         waitingForGame = FALSE;
11541     }
11542
11543     if(appData.tourneyType < 0) {
11544         if(nr>=0 && !pairingReceived) {
11545             char buf[1<<16];
11546             if(pairing.pr == NoProc) {
11547                 if(!appData.pairingEngine[0]) {
11548                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11549                     return 0;
11550                 }
11551                 StartChessProgram(&pairing); // starts the pairing engine
11552             }
11553             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11554             SendToProgram(buf, &pairing);
11555             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11556             SendToProgram(buf, &pairing);
11557             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11558         }
11559         pairingReceived = 0;                              // ... so we continue here
11560         *swapColors = 0;
11561         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11562         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11563         matchGame = 1; roundNr = nr / syncInterval + 1;
11564     }
11565
11566     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11567
11568     // redefine engines, engine dir, etc.
11569     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11570     if(first.pr == NoProc) {
11571       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11572       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11573     }
11574     if(second.pr == NoProc) {
11575       SwapEngines(1);
11576       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11577       SwapEngines(1);         // and make that valid for second engine by swapping
11578       InitEngine(&second, 1);
11579     }
11580     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11581     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11582     return OK;
11583 }
11584
11585 void
11586 NextMatchGame ()
11587 {   // performs game initialization that does not invoke engines, and then tries to start the game
11588     int res, firstWhite, swapColors = 0;
11589     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11590     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
11591         char buf[MSG_SIZ];
11592         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11593         if(strcmp(buf, currentDebugFile)) { // name has changed
11594             FILE *f = fopen(buf, "w");
11595             if(f) { // if opening the new file failed, just keep using the old one
11596                 ASSIGN(currentDebugFile, buf);
11597                 fclose(debugFP);
11598                 debugFP = f;
11599             }
11600             if(appData.serverFileName) {
11601                 if(serverFP) fclose(serverFP);
11602                 serverFP = fopen(appData.serverFileName, "w");
11603                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11604                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11605             }
11606         }
11607     }
11608     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11609     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11610     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11611     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11612     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11613     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11614     Reset(FALSE, first.pr != NoProc);
11615     res = LoadGameOrPosition(matchGame); // setup game
11616     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11617     if(!res) return; // abort when bad game/pos file
11618     if(appData.epd) {// in EPD mode we make sure first engine is to move
11619         firstWhite = !(forwardMostMove & 1);
11620         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11621         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11622     }
11623     TwoMachinesEvent();
11624 }
11625
11626 void
11627 UserAdjudicationEvent (int result)
11628 {
11629     ChessMove gameResult = GameIsDrawn;
11630
11631     if( result > 0 ) {
11632         gameResult = WhiteWins;
11633     }
11634     else if( result < 0 ) {
11635         gameResult = BlackWins;
11636     }
11637
11638     if( gameMode == TwoMachinesPlay ) {
11639         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11640     }
11641 }
11642
11643
11644 // [HGM] save: calculate checksum of game to make games easily identifiable
11645 int
11646 StringCheckSum (char *s)
11647 {
11648         int i = 0;
11649         if(s==NULL) return 0;
11650         while(*s) i = i*259 + *s++;
11651         return i;
11652 }
11653
11654 int
11655 GameCheckSum ()
11656 {
11657         int i, sum=0;
11658         for(i=backwardMostMove; i<forwardMostMove; i++) {
11659                 sum += pvInfoList[i].depth;
11660                 sum += StringCheckSum(parseList[i]);
11661                 sum += StringCheckSum(commentList[i]);
11662                 sum *= 261;
11663         }
11664         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11665         return sum + StringCheckSum(commentList[i]);
11666 } // end of save patch
11667
11668 void
11669 GameEnds (ChessMove result, char *resultDetails, int whosays)
11670 {
11671     GameMode nextGameMode;
11672     int isIcsGame;
11673     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11674
11675     if(endingGame) return; /* [HGM] crash: forbid recursion */
11676     endingGame = 1;
11677     if(twoBoards) { // [HGM] dual: switch back to one board
11678         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11679         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11680     }
11681     if (appData.debugMode) {
11682       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11683               result, resultDetails ? resultDetails : "(null)", whosays);
11684     }
11685
11686     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11687
11688     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11689
11690     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11691         /* If we are playing on ICS, the server decides when the
11692            game is over, but the engine can offer to draw, claim
11693            a draw, or resign.
11694          */
11695 #if ZIPPY
11696         if (appData.zippyPlay && first.initDone) {
11697             if (result == GameIsDrawn) {
11698                 /* In case draw still needs to be claimed */
11699                 SendToICS(ics_prefix);
11700                 SendToICS("draw\n");
11701             } else if (StrCaseStr(resultDetails, "resign")) {
11702                 SendToICS(ics_prefix);
11703                 SendToICS("resign\n");
11704             }
11705         }
11706 #endif
11707         endingGame = 0; /* [HGM] crash */
11708         return;
11709     }
11710
11711     /* If we're loading the game from a file, stop */
11712     if (whosays == GE_FILE) {
11713       (void) StopLoadGameTimer();
11714       gameFileFP = NULL;
11715     }
11716
11717     /* Cancel draw offers */
11718     first.offeredDraw = second.offeredDraw = 0;
11719
11720     /* If this is an ICS game, only ICS can really say it's done;
11721        if not, anyone can. */
11722     isIcsGame = (gameMode == IcsPlayingWhite ||
11723                  gameMode == IcsPlayingBlack ||
11724                  gameMode == IcsObserving    ||
11725                  gameMode == IcsExamining);
11726
11727     if (!isIcsGame || whosays == GE_ICS) {
11728         /* OK -- not an ICS game, or ICS said it was done */
11729         StopClocks();
11730         if (!isIcsGame && !appData.noChessProgram)
11731           SetUserThinkingEnables();
11732
11733         /* [HGM] if a machine claims the game end we verify this claim */
11734         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11735             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11736                 char claimer;
11737                 ChessMove trueResult = (ChessMove) -1;
11738
11739                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11740                                             first.twoMachinesColor[0] :
11741                                             second.twoMachinesColor[0] ;
11742
11743                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11744                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11745                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11746                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11747                 } else
11748                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11749                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11750                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11751                 } else
11752                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11753                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11754                 }
11755
11756                 // now verify win claims, but not in drop games, as we don't understand those yet
11757                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11758                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11759                     (result == WhiteWins && claimer == 'w' ||
11760                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11761                       if (appData.debugMode) {
11762                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11763                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11764                       }
11765                       if(result != trueResult) {
11766                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11767                               result = claimer == 'w' ? BlackWins : WhiteWins;
11768                               resultDetails = buf;
11769                       }
11770                 } else
11771                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11772                     && (forwardMostMove <= backwardMostMove ||
11773                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11774                         (claimer=='b')==(forwardMostMove&1))
11775                                                                                   ) {
11776                       /* [HGM] verify: draws that were not flagged are false claims */
11777                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11778                       result = claimer == 'w' ? BlackWins : WhiteWins;
11779                       resultDetails = buf;
11780                 }
11781                 /* (Claiming a loss is accepted no questions asked!) */
11782             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11783                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11784                 result = GameUnfinished;
11785                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11786             }
11787             /* [HGM] bare: don't allow bare King to win */
11788             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11789                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11790                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11791                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11792                && result != GameIsDrawn)
11793             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11794                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11795                         int p = (int)boards[forwardMostMove][i][j] - color;
11796                         if(p >= 0 && p <= (int)WhiteKing) k++;
11797                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11798                 }
11799                 if (appData.debugMode) {
11800                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11801                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11802                 }
11803                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11804                         result = GameIsDrawn;
11805                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11806                         resultDetails = buf;
11807                 }
11808             }
11809         }
11810
11811
11812         if(serverMoves != NULL && !loadFlag) { char c = '=';
11813             if(result==WhiteWins) c = '+';
11814             if(result==BlackWins) c = '-';
11815             if(resultDetails != NULL)
11816                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11817         }
11818         if (resultDetails != NULL) {
11819             gameInfo.result = result;
11820             gameInfo.resultDetails = StrSave(resultDetails);
11821
11822             /* display last move only if game was not loaded from file */
11823             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11824                 DisplayMove(currentMove - 1);
11825
11826             if (forwardMostMove != 0) {
11827                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11828                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11829                                                                 ) {
11830                     if (*appData.saveGameFile != NULLCHAR) {
11831                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11832                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11833                         else
11834                         SaveGameToFile(appData.saveGameFile, TRUE);
11835                     } else if (appData.autoSaveGames) {
11836                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11837                     }
11838                     if (*appData.savePositionFile != NULLCHAR) {
11839                         SavePositionToFile(appData.savePositionFile);
11840                     }
11841                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11842                 }
11843             }
11844
11845             /* Tell program how game ended in case it is learning */
11846             /* [HGM] Moved this to after saving the PGN, just in case */
11847             /* engine died and we got here through time loss. In that */
11848             /* case we will get a fatal error writing the pipe, which */
11849             /* would otherwise lose us the PGN.                       */
11850             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11851             /* output during GameEnds should never be fatal anymore   */
11852             if (gameMode == MachinePlaysWhite ||
11853                 gameMode == MachinePlaysBlack ||
11854                 gameMode == TwoMachinesPlay ||
11855                 gameMode == IcsPlayingWhite ||
11856                 gameMode == IcsPlayingBlack ||
11857                 gameMode == BeginningOfGame) {
11858                 char buf[MSG_SIZ];
11859                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11860                         resultDetails);
11861                 if (first.pr != NoProc) {
11862                     SendToProgram(buf, &first);
11863                 }
11864                 if (second.pr != NoProc &&
11865                     gameMode == TwoMachinesPlay) {
11866                     SendToProgram(buf, &second);
11867                 }
11868             }
11869         }
11870
11871         if (appData.icsActive) {
11872             if (appData.quietPlay &&
11873                 (gameMode == IcsPlayingWhite ||
11874                  gameMode == IcsPlayingBlack)) {
11875                 SendToICS(ics_prefix);
11876                 SendToICS("set shout 1\n");
11877             }
11878             nextGameMode = IcsIdle;
11879             ics_user_moved = FALSE;
11880             /* clean up premove.  It's ugly when the game has ended and the
11881              * premove highlights are still on the board.
11882              */
11883             if (gotPremove) {
11884               gotPremove = FALSE;
11885               ClearPremoveHighlights();
11886               DrawPosition(FALSE, boards[currentMove]);
11887             }
11888             if (whosays == GE_ICS) {
11889                 switch (result) {
11890                 case WhiteWins:
11891                     if (gameMode == IcsPlayingWhite)
11892                         PlayIcsWinSound();
11893                     else if(gameMode == IcsPlayingBlack)
11894                         PlayIcsLossSound();
11895                     break;
11896                 case BlackWins:
11897                     if (gameMode == IcsPlayingBlack)
11898                         PlayIcsWinSound();
11899                     else if(gameMode == IcsPlayingWhite)
11900                         PlayIcsLossSound();
11901                     break;
11902                 case GameIsDrawn:
11903                     PlayIcsDrawSound();
11904                     break;
11905                 default:
11906                     PlayIcsUnfinishedSound();
11907                 }
11908             }
11909             if(appData.quitNext) { ExitEvent(0); return; }
11910         } else if (gameMode == EditGame ||
11911                    gameMode == PlayFromGameFile ||
11912                    gameMode == AnalyzeMode ||
11913                    gameMode == AnalyzeFile) {
11914             nextGameMode = gameMode;
11915         } else {
11916             nextGameMode = EndOfGame;
11917         }
11918         pausing = FALSE;
11919         ModeHighlight();
11920     } else {
11921         nextGameMode = gameMode;
11922     }
11923
11924     if (appData.noChessProgram) {
11925         gameMode = nextGameMode;
11926         ModeHighlight();
11927         endingGame = 0; /* [HGM] crash */
11928         return;
11929     }
11930
11931     if (first.reuse) {
11932         /* Put first chess program into idle state */
11933         if (first.pr != NoProc &&
11934             (gameMode == MachinePlaysWhite ||
11935              gameMode == MachinePlaysBlack ||
11936              gameMode == TwoMachinesPlay ||
11937              gameMode == IcsPlayingWhite ||
11938              gameMode == IcsPlayingBlack ||
11939              gameMode == BeginningOfGame)) {
11940             SendToProgram("force\n", &first);
11941             if (first.usePing) {
11942               char buf[MSG_SIZ];
11943               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11944               SendToProgram(buf, &first);
11945             }
11946         }
11947     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11948         /* Kill off first chess program */
11949         if (first.isr != NULL)
11950           RemoveInputSource(first.isr);
11951         first.isr = NULL;
11952
11953         if (first.pr != NoProc) {
11954             ExitAnalyzeMode();
11955             DoSleep( appData.delayBeforeQuit );
11956             SendToProgram("quit\n", &first);
11957             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11958             first.reload = TRUE;
11959         }
11960         first.pr = NoProc;
11961     }
11962     if (second.reuse) {
11963         /* Put second chess program into idle state */
11964         if (second.pr != NoProc &&
11965             gameMode == TwoMachinesPlay) {
11966             SendToProgram("force\n", &second);
11967             if (second.usePing) {
11968               char buf[MSG_SIZ];
11969               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11970               SendToProgram(buf, &second);
11971             }
11972         }
11973     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11974         /* Kill off second chess program */
11975         if (second.isr != NULL)
11976           RemoveInputSource(second.isr);
11977         second.isr = NULL;
11978
11979         if (second.pr != NoProc) {
11980             DoSleep( appData.delayBeforeQuit );
11981             SendToProgram("quit\n", &second);
11982             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11983             second.reload = TRUE;
11984         }
11985         second.pr = NoProc;
11986     }
11987
11988     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11989         char resChar = '=';
11990         switch (result) {
11991         case WhiteWins:
11992           resChar = '+';
11993           if (first.twoMachinesColor[0] == 'w') {
11994             first.matchWins++;
11995           } else {
11996             second.matchWins++;
11997           }
11998           break;
11999         case BlackWins:
12000           resChar = '-';
12001           if (first.twoMachinesColor[0] == 'b') {
12002             first.matchWins++;
12003           } else {
12004             second.matchWins++;
12005           }
12006           break;
12007         case GameUnfinished:
12008           resChar = ' ';
12009         default:
12010           break;
12011         }
12012
12013         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
12014         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
12015             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
12016             ReserveGame(nextGame, resChar); // sets nextGame
12017             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
12018             else ranking = strdup("busy"); //suppress popup when aborted but not finished
12019         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
12020
12021         if (nextGame <= appData.matchGames && !abortMatch) {
12022             gameMode = nextGameMode;
12023             matchGame = nextGame; // this will be overruled in tourney mode!
12024             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
12025             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
12026             endingGame = 0; /* [HGM] crash */
12027             return;
12028         } else {
12029             gameMode = nextGameMode;
12030             if(appData.epd) {
12031                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
12032                 OutputKibitz(2, buf);
12033                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
12034                 OutputKibitz(2, buf);
12035                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
12036                 if(second.matchWins) OutputKibitz(2, buf);
12037                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
12038                 OutputKibitz(2, buf);
12039             }
12040             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
12041                      first.tidy, second.tidy,
12042                      first.matchWins, second.matchWins,
12043                      appData.matchGames - (first.matchWins + second.matchWins));
12044             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
12045             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
12046             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
12047             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
12048                 first.twoMachinesColor = "black\n";
12049                 second.twoMachinesColor = "white\n";
12050             } else {
12051                 first.twoMachinesColor = "white\n";
12052                 second.twoMachinesColor = "black\n";
12053             }
12054         }
12055     }
12056     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
12057         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
12058       ExitAnalyzeMode();
12059     gameMode = nextGameMode;
12060     ModeHighlight();
12061     endingGame = 0;  /* [HGM] crash */
12062     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12063         if(matchMode == TRUE) { // match through command line: exit with or without popup
12064             if(ranking) {
12065                 ToNrEvent(forwardMostMove);
12066                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12067                 else ExitEvent(0);
12068             } else DisplayFatalError(buf, 0, 0);
12069         } else { // match through menu; just stop, with or without popup
12070             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12071             ModeHighlight();
12072             if(ranking){
12073                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12074             } else DisplayNote(buf);
12075       }
12076       if(ranking) free(ranking);
12077     }
12078 }
12079
12080 /* Assumes program was just initialized (initString sent).
12081    Leaves program in force mode. */
12082 void
12083 FeedMovesToProgram (ChessProgramState *cps, int upto)
12084 {
12085     int i;
12086
12087     if (appData.debugMode)
12088       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12089               startedFromSetupPosition ? "position and " : "",
12090               backwardMostMove, upto, cps->which);
12091     if(currentlyInitializedVariant != gameInfo.variant) {
12092       char buf[MSG_SIZ];
12093         // [HGM] variantswitch: make engine aware of new variant
12094         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12095                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12096                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12097         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12098         SendToProgram(buf, cps);
12099         currentlyInitializedVariant = gameInfo.variant;
12100     }
12101     SendToProgram("force\n", cps);
12102     if (startedFromSetupPosition) {
12103         SendBoard(cps, backwardMostMove);
12104     if (appData.debugMode) {
12105         fprintf(debugFP, "feedMoves\n");
12106     }
12107     }
12108     for (i = backwardMostMove; i < upto; i++) {
12109         SendMoveToProgram(i, cps);
12110     }
12111 }
12112
12113
12114 int
12115 ResurrectChessProgram ()
12116 {
12117      /* The chess program may have exited.
12118         If so, restart it and feed it all the moves made so far. */
12119     static int doInit = 0;
12120
12121     if (appData.noChessProgram) return 1;
12122
12123     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12124         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12125         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12126         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12127     } else {
12128         if (first.pr != NoProc) return 1;
12129         StartChessProgram(&first);
12130     }
12131     InitChessProgram(&first, FALSE);
12132     FeedMovesToProgram(&first, currentMove);
12133
12134     if (!first.sendTime) {
12135         /* can't tell gnuchess what its clock should read,
12136            so we bow to its notion. */
12137         ResetClocks();
12138         timeRemaining[0][currentMove] = whiteTimeRemaining;
12139         timeRemaining[1][currentMove] = blackTimeRemaining;
12140     }
12141
12142     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12143                 appData.icsEngineAnalyze) && first.analysisSupport) {
12144       SendToProgram("analyze\n", &first);
12145       first.analyzing = TRUE;
12146     }
12147     return 1;
12148 }
12149
12150 /*
12151  * Button procedures
12152  */
12153 void
12154 Reset (int redraw, int init)
12155 {
12156     int i;
12157
12158     if (appData.debugMode) {
12159         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12160                 redraw, init, gameMode);
12161     }
12162     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12163     deadRanks = 0; // assume entire board is used
12164     handSize = 0;
12165     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12166     CleanupTail(); // [HGM] vari: delete any stored variations
12167     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12168     pausing = pauseExamInvalid = FALSE;
12169     startedFromSetupPosition = blackPlaysFirst = FALSE;
12170     firstMove = TRUE;
12171     whiteFlag = blackFlag = FALSE;
12172     userOfferedDraw = FALSE;
12173     hintRequested = bookRequested = FALSE;
12174     first.maybeThinking = FALSE;
12175     second.maybeThinking = FALSE;
12176     first.bookSuspend = FALSE; // [HGM] book
12177     second.bookSuspend = FALSE;
12178     thinkOutput[0] = NULLCHAR;
12179     lastHint[0] = NULLCHAR;
12180     ClearGameInfo(&gameInfo);
12181     gameInfo.variant = StringToVariant(appData.variant);
12182     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12183         gameInfo.variant = VariantUnknown;
12184         strncpy(engineVariant, appData.variant, MSG_SIZ);
12185     }
12186     ics_user_moved = ics_clock_paused = FALSE;
12187     ics_getting_history = H_FALSE;
12188     ics_gamenum = -1;
12189     white_holding[0] = black_holding[0] = NULLCHAR;
12190     ClearProgramStats();
12191     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12192
12193     ResetFrontEnd();
12194     ClearHighlights();
12195     flipView = appData.flipView;
12196     ClearPremoveHighlights();
12197     gotPremove = FALSE;
12198     alarmSounded = FALSE;
12199     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12200
12201     GameEnds(EndOfFile, NULL, GE_PLAYER);
12202     if(appData.serverMovesName != NULL) {
12203         /* [HGM] prepare to make moves file for broadcasting */
12204         clock_t t = clock();
12205         if(serverMoves != NULL) fclose(serverMoves);
12206         serverMoves = fopen(appData.serverMovesName, "r");
12207         if(serverMoves != NULL) {
12208             fclose(serverMoves);
12209             /* delay 15 sec before overwriting, so all clients can see end */
12210             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12211         }
12212         serverMoves = fopen(appData.serverMovesName, "w");
12213     }
12214
12215     ExitAnalyzeMode();
12216     gameMode = BeginningOfGame;
12217     ModeHighlight();
12218     if(appData.icsActive) gameInfo.variant = VariantNormal;
12219     currentMove = forwardMostMove = backwardMostMove = 0;
12220     MarkTargetSquares(1);
12221     InitPosition(redraw);
12222     for (i = 0; i < MAX_MOVES; i++) {
12223         if (commentList[i] != NULL) {
12224             free(commentList[i]);
12225             commentList[i] = NULL;
12226         }
12227     }
12228     ResetClocks();
12229     timeRemaining[0][0] = whiteTimeRemaining;
12230     timeRemaining[1][0] = blackTimeRemaining;
12231
12232     if (first.pr == NoProc) {
12233         StartChessProgram(&first);
12234     }
12235     if (init) {
12236             InitChessProgram(&first, startedFromSetupPosition);
12237     }
12238     DisplayTitle("");
12239     DisplayMessage("", "");
12240     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12241     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12242     ClearMap();        // [HGM] exclude: invalidate map
12243 }
12244
12245 void
12246 AutoPlayGameLoop ()
12247 {
12248     for (;;) {
12249         if (!AutoPlayOneMove())
12250           return;
12251         if (matchMode || appData.timeDelay == 0)
12252           continue;
12253         if (appData.timeDelay < 0)
12254           return;
12255         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12256         break;
12257     }
12258 }
12259
12260 void
12261 AnalyzeNextGame()
12262 {
12263     ReloadGame(1); // next game
12264 }
12265
12266 int
12267 AutoPlayOneMove ()
12268 {
12269     int fromX, fromY, toX, toY;
12270
12271     if (appData.debugMode) {
12272       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12273     }
12274
12275     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12276       return FALSE;
12277
12278     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12279       pvInfoList[currentMove].depth = programStats.depth;
12280       pvInfoList[currentMove].score = programStats.score;
12281       pvInfoList[currentMove].time  = 0;
12282       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12283       else { // append analysis of final position as comment
12284         char buf[MSG_SIZ];
12285         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12286         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12287       }
12288       programStats.depth = 0;
12289     }
12290
12291     if (currentMove >= forwardMostMove) {
12292       if(gameMode == AnalyzeFile) {
12293           if(appData.loadGameIndex == -1) {
12294             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12295           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12296           } else {
12297           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12298         }
12299       }
12300 //      gameMode = EndOfGame;
12301 //      ModeHighlight();
12302
12303       /* [AS] Clear current move marker at the end of a game */
12304       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12305
12306       return FALSE;
12307     }
12308
12309     toX = moveList[currentMove][2] - AAA;
12310     toY = moveList[currentMove][3] - ONE;
12311
12312     if (moveList[currentMove][1] == '@') {
12313         if (appData.highlightLastMove) {
12314             SetHighlights(-1, -1, toX, toY);
12315         }
12316     } else {
12317         fromX = moveList[currentMove][0] - AAA;
12318         fromY = moveList[currentMove][1] - ONE;
12319
12320         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12321
12322         if(moveList[currentMove][4] == ';') { // multi-leg
12323             killX = moveList[currentMove][5] - AAA;
12324             killY = moveList[currentMove][6] - ONE;
12325         }
12326         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12327         killX = killY = -1;
12328
12329         if (appData.highlightLastMove) {
12330             SetHighlights(fromX, fromY, toX, toY);
12331         }
12332     }
12333     DisplayMove(currentMove);
12334     SendMoveToProgram(currentMove++, &first);
12335     DisplayBothClocks();
12336     DrawPosition(FALSE, boards[currentMove]);
12337     // [HGM] PV info: always display, routine tests if empty
12338     DisplayComment(currentMove - 1, commentList[currentMove]);
12339     return TRUE;
12340 }
12341
12342
12343 int
12344 LoadGameOneMove (ChessMove readAhead)
12345 {
12346     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12347     char promoChar = NULLCHAR;
12348     ChessMove moveType;
12349     char move[MSG_SIZ];
12350     char *p, *q;
12351
12352     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12353         gameMode != AnalyzeMode && gameMode != Training) {
12354         gameFileFP = NULL;
12355         return FALSE;
12356     }
12357
12358     yyboardindex = forwardMostMove;
12359     if (readAhead != EndOfFile) {
12360       moveType = readAhead;
12361     } else {
12362       if (gameFileFP == NULL)
12363           return FALSE;
12364       moveType = (ChessMove) Myylex();
12365     }
12366
12367     done = FALSE;
12368     switch (moveType) {
12369       case Comment:
12370         if (appData.debugMode)
12371           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12372         p = yy_text;
12373
12374         /* append the comment but don't display it */
12375         AppendComment(currentMove, p, FALSE);
12376         return TRUE;
12377
12378       case WhiteCapturesEnPassant:
12379       case BlackCapturesEnPassant:
12380       case WhitePromotion:
12381       case BlackPromotion:
12382       case WhiteNonPromotion:
12383       case BlackNonPromotion:
12384       case NormalMove:
12385       case FirstLeg:
12386       case WhiteKingSideCastle:
12387       case WhiteQueenSideCastle:
12388       case BlackKingSideCastle:
12389       case BlackQueenSideCastle:
12390       case WhiteKingSideCastleWild:
12391       case WhiteQueenSideCastleWild:
12392       case BlackKingSideCastleWild:
12393       case BlackQueenSideCastleWild:
12394       /* PUSH Fabien */
12395       case WhiteHSideCastleFR:
12396       case WhiteASideCastleFR:
12397       case BlackHSideCastleFR:
12398       case BlackASideCastleFR:
12399       /* POP Fabien */
12400         if (appData.debugMode)
12401           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12402         fromX = currentMoveString[0] - AAA;
12403         fromY = currentMoveString[1] - ONE;
12404         toX = currentMoveString[2] - AAA;
12405         toY = currentMoveString[3] - ONE;
12406         promoChar = currentMoveString[4];
12407         if(promoChar == ';') promoChar = currentMoveString[7];
12408         break;
12409
12410       case WhiteDrop:
12411       case BlackDrop:
12412         if (appData.debugMode)
12413           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12414         fromX = moveType == WhiteDrop ?
12415           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12416         (int) CharToPiece(ToLower(currentMoveString[0]));
12417         fromY = DROP_RANK;
12418         toX = currentMoveString[2] - AAA;
12419         toY = currentMoveString[3] - ONE;
12420         break;
12421
12422       case WhiteWins:
12423       case BlackWins:
12424       case GameIsDrawn:
12425       case GameUnfinished:
12426         if (appData.debugMode)
12427           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12428         p = strchr(yy_text, '{');
12429         if (p == NULL) p = strchr(yy_text, '(');
12430         if (p == NULL) {
12431             p = yy_text;
12432             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12433         } else {
12434             q = strchr(p, *p == '{' ? '}' : ')');
12435             if (q != NULL) *q = NULLCHAR;
12436             p++;
12437         }
12438         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12439         GameEnds(moveType, p, GE_FILE);
12440         done = TRUE;
12441         if (cmailMsgLoaded) {
12442             ClearHighlights();
12443             flipView = WhiteOnMove(currentMove);
12444             if (moveType == GameUnfinished) flipView = !flipView;
12445             if (appData.debugMode)
12446               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12447         }
12448         break;
12449
12450       case EndOfFile:
12451         if (appData.debugMode)
12452           fprintf(debugFP, "Parser hit end of file\n");
12453         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12454           case MT_NONE:
12455           case MT_CHECK:
12456             break;
12457           case MT_CHECKMATE:
12458           case MT_STAINMATE:
12459             if (WhiteOnMove(currentMove)) {
12460                 GameEnds(BlackWins, "Black mates", GE_FILE);
12461             } else {
12462                 GameEnds(WhiteWins, "White mates", GE_FILE);
12463             }
12464             break;
12465           case MT_STALEMATE:
12466             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12467             break;
12468         }
12469         done = TRUE;
12470         break;
12471
12472       case MoveNumberOne:
12473         if (lastLoadGameStart == GNUChessGame) {
12474             /* GNUChessGames have numbers, but they aren't move numbers */
12475             if (appData.debugMode)
12476               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12477                       yy_text, (int) moveType);
12478             return LoadGameOneMove(EndOfFile); /* tail recursion */
12479         }
12480         /* else fall thru */
12481
12482       case XBoardGame:
12483       case GNUChessGame:
12484       case PGNTag:
12485         /* Reached start of next game in file */
12486         if (appData.debugMode)
12487           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12488         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12489           case MT_NONE:
12490           case MT_CHECK:
12491             break;
12492           case MT_CHECKMATE:
12493           case MT_STAINMATE:
12494             if (WhiteOnMove(currentMove)) {
12495                 GameEnds(BlackWins, "Black mates", GE_FILE);
12496             } else {
12497                 GameEnds(WhiteWins, "White mates", GE_FILE);
12498             }
12499             break;
12500           case MT_STALEMATE:
12501             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12502             break;
12503         }
12504         done = TRUE;
12505         break;
12506
12507       case PositionDiagram:     /* should not happen; ignore */
12508       case ElapsedTime:         /* ignore */
12509       case NAG:                 /* ignore */
12510         if (appData.debugMode)
12511           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12512                   yy_text, (int) moveType);
12513         return LoadGameOneMove(EndOfFile); /* tail recursion */
12514
12515       case IllegalMove:
12516         if (appData.testLegality) {
12517             if (appData.debugMode)
12518               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12519             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12520                     (forwardMostMove / 2) + 1,
12521                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12522             DisplayError(move, 0);
12523             done = TRUE;
12524         } else {
12525             if (appData.debugMode)
12526               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12527                       yy_text, currentMoveString);
12528             if(currentMoveString[1] == '@') {
12529                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12530                 fromY = DROP_RANK;
12531             } else {
12532                 fromX = currentMoveString[0] - AAA;
12533                 fromY = currentMoveString[1] - ONE;
12534             }
12535             toX = currentMoveString[2] - AAA;
12536             toY = currentMoveString[3] - ONE;
12537             promoChar = currentMoveString[4];
12538         }
12539         break;
12540
12541       case AmbiguousMove:
12542         if (appData.debugMode)
12543           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12544         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12545                 (forwardMostMove / 2) + 1,
12546                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12547         DisplayError(move, 0);
12548         done = TRUE;
12549         break;
12550
12551       default:
12552       case ImpossibleMove:
12553         if (appData.debugMode)
12554           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12555         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12556                 (forwardMostMove / 2) + 1,
12557                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12558         DisplayError(move, 0);
12559         done = TRUE;
12560         break;
12561     }
12562
12563     if (done) {
12564         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12565             DrawPosition(FALSE, boards[currentMove]);
12566             DisplayBothClocks();
12567             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12568               DisplayComment(currentMove - 1, commentList[currentMove]);
12569         }
12570         (void) StopLoadGameTimer();
12571         gameFileFP = NULL;
12572         cmailOldMove = forwardMostMove;
12573         return FALSE;
12574     } else {
12575         /* currentMoveString is set as a side-effect of yylex */
12576
12577         thinkOutput[0] = NULLCHAR;
12578         MakeMove(fromX, fromY, toX, toY, promoChar);
12579         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12580         currentMove = forwardMostMove;
12581         return TRUE;
12582     }
12583 }
12584
12585 /* Load the nth game from the given file */
12586 int
12587 LoadGameFromFile (char *filename, int n, char *title, int useList)
12588 {
12589     FILE *f;
12590     char buf[MSG_SIZ];
12591
12592     if (strcmp(filename, "-") == 0) {
12593         f = stdin;
12594         title = "stdin";
12595     } else {
12596         f = fopen(filename, "rb");
12597         if (f == NULL) {
12598           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12599             DisplayError(buf, errno);
12600             return FALSE;
12601         }
12602     }
12603     if (fseek(f, 0, 0) == -1) {
12604         /* f is not seekable; probably a pipe */
12605         useList = FALSE;
12606     }
12607     if (useList && n == 0) {
12608         int error = GameListBuild(f);
12609         if (error) {
12610             DisplayError(_("Cannot build game list"), error);
12611         } else if (!ListEmpty(&gameList) &&
12612                    ((ListGame *) gameList.tailPred)->number > 1) {
12613             GameListPopUp(f, title);
12614             return TRUE;
12615         }
12616         GameListDestroy();
12617         n = 1;
12618     }
12619     if (n == 0) n = 1;
12620     return LoadGame(f, n, title, FALSE);
12621 }
12622
12623
12624 void
12625 MakeRegisteredMove ()
12626 {
12627     int fromX, fromY, toX, toY;
12628     char promoChar;
12629     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12630         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12631           case CMAIL_MOVE:
12632           case CMAIL_DRAW:
12633             if (appData.debugMode)
12634               fprintf(debugFP, "Restoring %s for game %d\n",
12635                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12636
12637             thinkOutput[0] = NULLCHAR;
12638             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12639             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12640             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12641             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12642             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12643             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12644             MakeMove(fromX, fromY, toX, toY, promoChar);
12645             ShowMove(fromX, fromY, toX, toY);
12646
12647             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12648               case MT_NONE:
12649               case MT_CHECK:
12650                 break;
12651
12652               case MT_CHECKMATE:
12653               case MT_STAINMATE:
12654                 if (WhiteOnMove(currentMove)) {
12655                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12656                 } else {
12657                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12658                 }
12659                 break;
12660
12661               case MT_STALEMATE:
12662                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12663                 break;
12664             }
12665
12666             break;
12667
12668           case CMAIL_RESIGN:
12669             if (WhiteOnMove(currentMove)) {
12670                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12671             } else {
12672                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12673             }
12674             break;
12675
12676           case CMAIL_ACCEPT:
12677             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12678             break;
12679
12680           default:
12681             break;
12682         }
12683     }
12684
12685     return;
12686 }
12687
12688 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12689 int
12690 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12691 {
12692     int retVal;
12693
12694     if (gameNumber > nCmailGames) {
12695         DisplayError(_("No more games in this message"), 0);
12696         return FALSE;
12697     }
12698     if (f == lastLoadGameFP) {
12699         int offset = gameNumber - lastLoadGameNumber;
12700         if (offset == 0) {
12701             cmailMsg[0] = NULLCHAR;
12702             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12703                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12704                 nCmailMovesRegistered--;
12705             }
12706             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12707             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12708                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12709             }
12710         } else {
12711             if (! RegisterMove()) return FALSE;
12712         }
12713     }
12714
12715     retVal = LoadGame(f, gameNumber, title, useList);
12716
12717     /* Make move registered during previous look at this game, if any */
12718     MakeRegisteredMove();
12719
12720     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12721         commentList[currentMove]
12722           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12723         DisplayComment(currentMove - 1, commentList[currentMove]);
12724     }
12725
12726     return retVal;
12727 }
12728
12729 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12730 int
12731 ReloadGame (int offset)
12732 {
12733     int gameNumber = lastLoadGameNumber + offset;
12734     if (lastLoadGameFP == NULL) {
12735         DisplayError(_("No game has been loaded yet"), 0);
12736         return FALSE;
12737     }
12738     if (gameNumber <= 0) {
12739         DisplayError(_("Can't back up any further"), 0);
12740         return FALSE;
12741     }
12742     if (cmailMsgLoaded) {
12743         return CmailLoadGame(lastLoadGameFP, gameNumber,
12744                              lastLoadGameTitle, lastLoadGameUseList);
12745     } else {
12746         return LoadGame(lastLoadGameFP, gameNumber,
12747                         lastLoadGameTitle, lastLoadGameUseList);
12748     }
12749 }
12750
12751 int keys[EmptySquare+1];
12752
12753 int
12754 PositionMatches (Board b1, Board b2)
12755 {
12756     int r, f, sum=0;
12757     switch(appData.searchMode) {
12758         case 1: return CompareWithRights(b1, b2);
12759         case 2:
12760             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12761                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12762             }
12763             return TRUE;
12764         case 3:
12765             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12766               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12767                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12768             }
12769             return sum==0;
12770         case 4:
12771             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12772                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12773             }
12774             return sum==0;
12775     }
12776     return TRUE;
12777 }
12778
12779 #define Q_PROMO  4
12780 #define Q_EP     3
12781 #define Q_BCASTL 2
12782 #define Q_WCASTL 1
12783
12784 int pieceList[256], quickBoard[256];
12785 ChessSquare pieceType[256] = { EmptySquare };
12786 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12787 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12788 int soughtTotal, turn;
12789 Boolean epOK, flipSearch;
12790
12791 typedef struct {
12792     unsigned char piece, to;
12793 } Move;
12794
12795 #define DSIZE (250000)
12796
12797 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12798 Move *moveDatabase = initialSpace;
12799 unsigned int movePtr, dataSize = DSIZE;
12800
12801 int
12802 MakePieceList (Board board, int *counts)
12803 {
12804     int r, f, n=Q_PROMO, total=0;
12805     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12806     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12807         int sq = f + (r<<4);
12808         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12809             quickBoard[sq] = ++n;
12810             pieceList[n] = sq;
12811             pieceType[n] = board[r][f];
12812             counts[board[r][f]]++;
12813             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12814             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12815             total++;
12816         }
12817     }
12818     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12819     return total;
12820 }
12821
12822 void
12823 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12824 {
12825     int sq = fromX + (fromY<<4);
12826     int piece = quickBoard[sq], rook;
12827     quickBoard[sq] = 0;
12828     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12829     if(piece == pieceList[1] && fromY == toY) {
12830       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12831         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12832         moveDatabase[movePtr++].piece = Q_WCASTL;
12833         quickBoard[sq] = piece;
12834         piece = quickBoard[from]; quickBoard[from] = 0;
12835         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12836       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12837         quickBoard[sq] = 0; // remove Rook
12838         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12839         moveDatabase[movePtr++].piece = Q_WCASTL;
12840         quickBoard[sq] = pieceList[1]; // put King
12841         piece = rook;
12842         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12843       }
12844     } else
12845     if(piece == pieceList[2] && fromY == toY) {
12846       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12847         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12848         moveDatabase[movePtr++].piece = Q_BCASTL;
12849         quickBoard[sq] = piece;
12850         piece = quickBoard[from]; quickBoard[from] = 0;
12851         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12852       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12853         quickBoard[sq] = 0; // remove Rook
12854         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12855         moveDatabase[movePtr++].piece = Q_BCASTL;
12856         quickBoard[sq] = pieceList[2]; // put King
12857         piece = rook;
12858         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12859       }
12860     } else
12861     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12862         quickBoard[(fromY<<4)+toX] = 0;
12863         moveDatabase[movePtr].piece = Q_EP;
12864         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12865         moveDatabase[movePtr].to = sq;
12866     } else
12867     if(promoPiece != pieceType[piece]) {
12868         moveDatabase[movePtr++].piece = Q_PROMO;
12869         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12870     }
12871     moveDatabase[movePtr].piece = piece;
12872     quickBoard[sq] = piece;
12873     movePtr++;
12874 }
12875
12876 int
12877 PackGame (Board board)
12878 {
12879     Move *newSpace = NULL;
12880     moveDatabase[movePtr].piece = 0; // terminate previous game
12881     if(movePtr > dataSize) {
12882         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12883         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12884         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12885         if(newSpace) {
12886             int i;
12887             Move *p = moveDatabase, *q = newSpace;
12888             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12889             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12890             moveDatabase = newSpace;
12891         } else { // calloc failed, we must be out of memory. Too bad...
12892             dataSize = 0; // prevent calloc events for all subsequent games
12893             return 0;     // and signal this one isn't cached
12894         }
12895     }
12896     movePtr++;
12897     MakePieceList(board, counts);
12898     return movePtr;
12899 }
12900
12901 int
12902 QuickCompare (Board board, int *minCounts, int *maxCounts)
12903 {   // compare according to search mode
12904     int r, f;
12905     switch(appData.searchMode)
12906     {
12907       case 1: // exact position match
12908         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12909         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12910             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12911         }
12912         break;
12913       case 2: // can have extra material on empty squares
12914         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12915             if(board[r][f] == EmptySquare) continue;
12916             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12917         }
12918         break;
12919       case 3: // material with exact Pawn structure
12920         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12921             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12922             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12923         } // fall through to material comparison
12924       case 4: // exact material
12925         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12926         break;
12927       case 6: // material range with given imbalance
12928         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12929         // fall through to range comparison
12930       case 5: // material range
12931         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12932     }
12933     return TRUE;
12934 }
12935
12936 int
12937 QuickScan (Board board, Move *move)
12938 {   // reconstruct game,and compare all positions in it
12939     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12940     do {
12941         int piece = move->piece;
12942         int to = move->to, from = pieceList[piece];
12943         if(found < 0) { // if already found just scan to game end for final piece count
12944           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12945            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12946            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12947                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12948             ) {
12949             static int lastCounts[EmptySquare+1];
12950             int i;
12951             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12952             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12953           } else stretch = 0;
12954           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12955           if(found >= 0 && !appData.minPieces) return found;
12956         }
12957         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12958           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12959           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12960             piece = (++move)->piece;
12961             from = pieceList[piece];
12962             counts[pieceType[piece]]--;
12963             pieceType[piece] = (ChessSquare) move->to;
12964             counts[move->to]++;
12965           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12966             counts[pieceType[quickBoard[to]]]--;
12967             quickBoard[to] = 0; total--;
12968             move++;
12969             continue;
12970           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12971             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12972             from  = pieceList[piece]; // so this must be King
12973             quickBoard[from] = 0;
12974             pieceList[piece] = to;
12975             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12976             quickBoard[from] = 0; // rook
12977             quickBoard[to] = piece;
12978             to = move->to; piece = move->piece;
12979             goto aftercastle;
12980           }
12981         }
12982         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12983         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12984         quickBoard[from] = 0;
12985       aftercastle:
12986         quickBoard[to] = piece;
12987         pieceList[piece] = to;
12988         cnt++; turn ^= 3;
12989         move++;
12990     } while(1);
12991 }
12992
12993 void
12994 InitSearch ()
12995 {
12996     int r, f;
12997     flipSearch = FALSE;
12998     CopyBoard(soughtBoard, boards[currentMove]);
12999     soughtTotal = MakePieceList(soughtBoard, maxSought);
13000     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
13001     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
13002     CopyBoard(reverseBoard, boards[currentMove]);
13003     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13004         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
13005         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
13006         reverseBoard[r][f] = piece;
13007     }
13008     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
13009     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
13010     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
13011                  || (boards[currentMove][CASTLING][2] == NoRights ||
13012                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
13013                  && (boards[currentMove][CASTLING][5] == NoRights ||
13014                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
13015       ) {
13016         flipSearch = TRUE;
13017         CopyBoard(flipBoard, soughtBoard);
13018         CopyBoard(rotateBoard, reverseBoard);
13019         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13020             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
13021             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
13022         }
13023     }
13024     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
13025     if(appData.searchMode >= 5) {
13026         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
13027         MakePieceList(soughtBoard, minSought);
13028         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
13029     }
13030     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
13031         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
13032 }
13033
13034 GameInfo dummyInfo;
13035 static int creatingBook;
13036
13037 int
13038 GameContainsPosition (FILE *f, ListGame *lg)
13039 {
13040     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
13041     int fromX, fromY, toX, toY;
13042     char promoChar;
13043     static int initDone=FALSE;
13044
13045     // weed out games based on numerical tag comparison
13046     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
13047     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
13048     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
13049     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
13050     if(!initDone) {
13051         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
13052         initDone = TRUE;
13053     }
13054     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
13055     else CopyBoard(boards[scratch], initialPosition); // default start position
13056     if(lg->moves) {
13057         turn = btm + 1;
13058         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
13059         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13060     }
13061     if(btm) plyNr++;
13062     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13063     fseek(f, lg->offset, 0);
13064     yynewfile(f);
13065     while(1) {
13066         yyboardindex = scratch;
13067         quickFlag = plyNr+1;
13068         next = Myylex();
13069         quickFlag = 0;
13070         switch(next) {
13071             case PGNTag:
13072                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13073             default:
13074                 continue;
13075
13076             case XBoardGame:
13077             case GNUChessGame:
13078                 if(plyNr) return -1; // after we have seen moves, this is for new game
13079               continue;
13080
13081             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13082             case ImpossibleMove:
13083             case WhiteWins: // game ends here with these four
13084             case BlackWins:
13085             case GameIsDrawn:
13086             case GameUnfinished:
13087                 return -1;
13088
13089             case IllegalMove:
13090                 if(appData.testLegality) return -1;
13091             case WhiteCapturesEnPassant:
13092             case BlackCapturesEnPassant:
13093             case WhitePromotion:
13094             case BlackPromotion:
13095             case WhiteNonPromotion:
13096             case BlackNonPromotion:
13097             case NormalMove:
13098             case FirstLeg:
13099             case WhiteKingSideCastle:
13100             case WhiteQueenSideCastle:
13101             case BlackKingSideCastle:
13102             case BlackQueenSideCastle:
13103             case WhiteKingSideCastleWild:
13104             case WhiteQueenSideCastleWild:
13105             case BlackKingSideCastleWild:
13106             case BlackQueenSideCastleWild:
13107             case WhiteHSideCastleFR:
13108             case WhiteASideCastleFR:
13109             case BlackHSideCastleFR:
13110             case BlackASideCastleFR:
13111                 fromX = currentMoveString[0] - AAA;
13112                 fromY = currentMoveString[1] - ONE;
13113                 toX = currentMoveString[2] - AAA;
13114                 toY = currentMoveString[3] - ONE;
13115                 promoChar = currentMoveString[4];
13116                 break;
13117             case WhiteDrop:
13118             case BlackDrop:
13119                 fromX = next == WhiteDrop ?
13120                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13121                   (int) CharToPiece(ToLower(currentMoveString[0]));
13122                 fromY = DROP_RANK;
13123                 toX = currentMoveString[2] - AAA;
13124                 toY = currentMoveString[3] - ONE;
13125                 promoChar = 0;
13126                 break;
13127         }
13128         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13129         plyNr++;
13130         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13131         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13132         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13133         if(appData.findMirror) {
13134             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13135             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13136         }
13137     }
13138 }
13139
13140 /* Load the nth game from open file f */
13141 int
13142 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13143 {
13144     ChessMove cm;
13145     char buf[MSG_SIZ];
13146     int gn = gameNumber;
13147     ListGame *lg = NULL;
13148     int numPGNTags = 0, i;
13149     int err, pos = -1;
13150     GameMode oldGameMode;
13151     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13152     char oldName[MSG_SIZ];
13153
13154     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13155
13156     if (appData.debugMode)
13157         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13158
13159     if (gameMode == Training )
13160         SetTrainingModeOff();
13161
13162     oldGameMode = gameMode;
13163     if (gameMode != BeginningOfGame) {
13164       Reset(FALSE, TRUE);
13165     }
13166     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13167
13168     gameFileFP = f;
13169     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13170         fclose(lastLoadGameFP);
13171     }
13172
13173     if (useList) {
13174         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13175
13176         if (lg) {
13177             fseek(f, lg->offset, 0);
13178             GameListHighlight(gameNumber);
13179             pos = lg->position;
13180             gn = 1;
13181         }
13182         else {
13183             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13184               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13185             else
13186             DisplayError(_("Game number out of range"), 0);
13187             return FALSE;
13188         }
13189     } else {
13190         GameListDestroy();
13191         if (fseek(f, 0, 0) == -1) {
13192             if (f == lastLoadGameFP ?
13193                 gameNumber == lastLoadGameNumber + 1 :
13194                 gameNumber == 1) {
13195                 gn = 1;
13196             } else {
13197                 DisplayError(_("Can't seek on game file"), 0);
13198                 return FALSE;
13199             }
13200         }
13201     }
13202     lastLoadGameFP = f;
13203     lastLoadGameNumber = gameNumber;
13204     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13205     lastLoadGameUseList = useList;
13206
13207     yynewfile(f);
13208
13209     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13210       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13211                 lg->gameInfo.black);
13212             DisplayTitle(buf);
13213     } else if (*title != NULLCHAR) {
13214         if (gameNumber > 1) {
13215           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13216             DisplayTitle(buf);
13217         } else {
13218             DisplayTitle(title);
13219         }
13220     }
13221
13222     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13223         gameMode = PlayFromGameFile;
13224         ModeHighlight();
13225     }
13226
13227     currentMove = forwardMostMove = backwardMostMove = 0;
13228     CopyBoard(boards[0], initialPosition);
13229     StopClocks();
13230
13231     /*
13232      * Skip the first gn-1 games in the file.
13233      * Also skip over anything that precedes an identifiable
13234      * start of game marker, to avoid being confused by
13235      * garbage at the start of the file.  Currently
13236      * recognized start of game markers are the move number "1",
13237      * the pattern "gnuchess .* game", the pattern
13238      * "^[#;%] [^ ]* game file", and a PGN tag block.
13239      * A game that starts with one of the latter two patterns
13240      * will also have a move number 1, possibly
13241      * following a position diagram.
13242      * 5-4-02: Let's try being more lenient and allowing a game to
13243      * start with an unnumbered move.  Does that break anything?
13244      */
13245     cm = lastLoadGameStart = EndOfFile;
13246     while (gn > 0) {
13247         yyboardindex = forwardMostMove;
13248         cm = (ChessMove) Myylex();
13249         switch (cm) {
13250           case EndOfFile:
13251             if (cmailMsgLoaded) {
13252                 nCmailGames = CMAIL_MAX_GAMES - gn;
13253             } else {
13254                 Reset(TRUE, TRUE);
13255                 DisplayError(_("Game not found in file"), 0);
13256             }
13257             return FALSE;
13258
13259           case GNUChessGame:
13260           case XBoardGame:
13261             gn--;
13262             lastLoadGameStart = cm;
13263             break;
13264
13265           case MoveNumberOne:
13266             switch (lastLoadGameStart) {
13267               case GNUChessGame:
13268               case XBoardGame:
13269               case PGNTag:
13270                 break;
13271               case MoveNumberOne:
13272               case EndOfFile:
13273                 gn--;           /* count this game */
13274                 lastLoadGameStart = cm;
13275                 break;
13276               default:
13277                 /* impossible */
13278                 break;
13279             }
13280             break;
13281
13282           case PGNTag:
13283             switch (lastLoadGameStart) {
13284               case GNUChessGame:
13285               case PGNTag:
13286               case MoveNumberOne:
13287               case EndOfFile:
13288                 gn--;           /* count this game */
13289                 lastLoadGameStart = cm;
13290                 break;
13291               case XBoardGame:
13292                 lastLoadGameStart = cm; /* game counted already */
13293                 break;
13294               default:
13295                 /* impossible */
13296                 break;
13297             }
13298             if (gn > 0) {
13299                 do {
13300                     yyboardindex = forwardMostMove;
13301                     cm = (ChessMove) Myylex();
13302                 } while (cm == PGNTag || cm == Comment);
13303             }
13304             break;
13305
13306           case WhiteWins:
13307           case BlackWins:
13308           case GameIsDrawn:
13309             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13310                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13311                     != CMAIL_OLD_RESULT) {
13312                     nCmailResults ++ ;
13313                     cmailResult[  CMAIL_MAX_GAMES
13314                                 - gn - 1] = CMAIL_OLD_RESULT;
13315                 }
13316             }
13317             break;
13318
13319           case NormalMove:
13320           case FirstLeg:
13321             /* Only a NormalMove can be at the start of a game
13322              * without a position diagram. */
13323             if (lastLoadGameStart == EndOfFile ) {
13324               gn--;
13325               lastLoadGameStart = MoveNumberOne;
13326             }
13327             break;
13328
13329           default:
13330             break;
13331         }
13332     }
13333
13334     if (appData.debugMode)
13335       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13336
13337     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13338
13339     if (cm == XBoardGame) {
13340         /* Skip any header junk before position diagram and/or move 1 */
13341         for (;;) {
13342             yyboardindex = forwardMostMove;
13343             cm = (ChessMove) Myylex();
13344
13345             if (cm == EndOfFile ||
13346                 cm == GNUChessGame || cm == XBoardGame) {
13347                 /* Empty game; pretend end-of-file and handle later */
13348                 cm = EndOfFile;
13349                 break;
13350             }
13351
13352             if (cm == MoveNumberOne || cm == PositionDiagram ||
13353                 cm == PGNTag || cm == Comment)
13354               break;
13355         }
13356     } else if (cm == GNUChessGame) {
13357         if (gameInfo.event != NULL) {
13358             free(gameInfo.event);
13359         }
13360         gameInfo.event = StrSave(yy_text);
13361     }
13362
13363     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13364     while (cm == PGNTag) {
13365         if (appData.debugMode)
13366           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13367         err = ParsePGNTag(yy_text, &gameInfo);
13368         if (!err) numPGNTags++;
13369
13370         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13371         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13372             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13373             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13374             InitPosition(TRUE);
13375             oldVariant = gameInfo.variant;
13376             if (appData.debugMode)
13377               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13378         }
13379
13380
13381         if (gameInfo.fen != NULL) {
13382           Board initial_position;
13383           startedFromSetupPosition = TRUE;
13384           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13385             Reset(TRUE, TRUE);
13386             DisplayError(_("Bad FEN position in file"), 0);
13387             return FALSE;
13388           }
13389           CopyBoard(boards[0], initial_position);
13390           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13391             CopyBoard(initialPosition, initial_position);
13392           if (blackPlaysFirst) {
13393             currentMove = forwardMostMove = backwardMostMove = 1;
13394             CopyBoard(boards[1], initial_position);
13395             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13396             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13397             timeRemaining[0][1] = whiteTimeRemaining;
13398             timeRemaining[1][1] = blackTimeRemaining;
13399             if (commentList[0] != NULL) {
13400               commentList[1] = commentList[0];
13401               commentList[0] = NULL;
13402             }
13403           } else {
13404             currentMove = forwardMostMove = backwardMostMove = 0;
13405           }
13406           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13407           {   int i;
13408               initialRulePlies = FENrulePlies;
13409               for( i=0; i< nrCastlingRights; i++ )
13410                   initialRights[i] = initial_position[CASTLING][i];
13411           }
13412           yyboardindex = forwardMostMove;
13413           free(gameInfo.fen);
13414           gameInfo.fen = NULL;
13415         }
13416
13417         yyboardindex = forwardMostMove;
13418         cm = (ChessMove) Myylex();
13419
13420         /* Handle comments interspersed among the tags */
13421         while (cm == Comment) {
13422             char *p;
13423             if (appData.debugMode)
13424               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13425             p = yy_text;
13426             AppendComment(currentMove, p, FALSE);
13427             yyboardindex = forwardMostMove;
13428             cm = (ChessMove) Myylex();
13429         }
13430     }
13431
13432     /* don't rely on existence of Event tag since if game was
13433      * pasted from clipboard the Event tag may not exist
13434      */
13435     if (numPGNTags > 0){
13436         char *tags;
13437         if (gameInfo.variant == VariantNormal) {
13438           VariantClass v = StringToVariant(gameInfo.event);
13439           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13440           if(v < VariantShogi) gameInfo.variant = v;
13441         }
13442         if (!matchMode) {
13443           if( appData.autoDisplayTags ) {
13444             tags = PGNTags(&gameInfo);
13445             TagsPopUp(tags, CmailMsg());
13446             free(tags);
13447           }
13448         }
13449     } else {
13450         /* Make something up, but don't display it now */
13451         SetGameInfo();
13452         TagsPopDown();
13453     }
13454
13455     if (cm == PositionDiagram) {
13456         int i, j;
13457         char *p;
13458         Board initial_position;
13459
13460         if (appData.debugMode)
13461           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13462
13463         if (!startedFromSetupPosition) {
13464             p = yy_text;
13465             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13466               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13467                 switch (*p) {
13468                   case '{':
13469                   case '[':
13470                   case '-':
13471                   case ' ':
13472                   case '\t':
13473                   case '\n':
13474                   case '\r':
13475                     break;
13476                   default:
13477                     initial_position[i][j++] = CharToPiece(*p);
13478                     break;
13479                 }
13480             while (*p == ' ' || *p == '\t' ||
13481                    *p == '\n' || *p == '\r') p++;
13482
13483             if (strncmp(p, "black", strlen("black"))==0)
13484               blackPlaysFirst = TRUE;
13485             else
13486               blackPlaysFirst = FALSE;
13487             startedFromSetupPosition = TRUE;
13488
13489             CopyBoard(boards[0], initial_position);
13490             if (blackPlaysFirst) {
13491                 currentMove = forwardMostMove = backwardMostMove = 1;
13492                 CopyBoard(boards[1], initial_position);
13493                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13494                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13495                 timeRemaining[0][1] = whiteTimeRemaining;
13496                 timeRemaining[1][1] = blackTimeRemaining;
13497                 if (commentList[0] != NULL) {
13498                     commentList[1] = commentList[0];
13499                     commentList[0] = NULL;
13500                 }
13501             } else {
13502                 currentMove = forwardMostMove = backwardMostMove = 0;
13503             }
13504         }
13505         yyboardindex = forwardMostMove;
13506         cm = (ChessMove) Myylex();
13507     }
13508
13509   if(!creatingBook) {
13510     if (first.pr == NoProc) {
13511         StartChessProgram(&first);
13512     }
13513     InitChessProgram(&first, FALSE);
13514     if(gameInfo.variant == VariantUnknown && *oldName) {
13515         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13516         gameInfo.variant = v;
13517     }
13518     SendToProgram("force\n", &first);
13519     if (startedFromSetupPosition) {
13520         SendBoard(&first, forwardMostMove);
13521     if (appData.debugMode) {
13522         fprintf(debugFP, "Load Game\n");
13523     }
13524         DisplayBothClocks();
13525     }
13526   }
13527
13528     /* [HGM] server: flag to write setup moves in broadcast file as one */
13529     loadFlag = appData.suppressLoadMoves;
13530
13531     while (cm == Comment) {
13532         char *p;
13533         if (appData.debugMode)
13534           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13535         p = yy_text;
13536         AppendComment(currentMove, p, FALSE);
13537         yyboardindex = forwardMostMove;
13538         cm = (ChessMove) Myylex();
13539     }
13540
13541     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13542         cm == WhiteWins || cm == BlackWins ||
13543         cm == GameIsDrawn || cm == GameUnfinished) {
13544         DisplayMessage("", _("No moves in game"));
13545         if (cmailMsgLoaded) {
13546             if (appData.debugMode)
13547               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13548             ClearHighlights();
13549             flipView = FALSE;
13550         }
13551         DrawPosition(FALSE, boards[currentMove]);
13552         DisplayBothClocks();
13553         gameMode = EditGame;
13554         ModeHighlight();
13555         gameFileFP = NULL;
13556         cmailOldMove = 0;
13557         return TRUE;
13558     }
13559
13560     // [HGM] PV info: routine tests if comment empty
13561     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13562         DisplayComment(currentMove - 1, commentList[currentMove]);
13563     }
13564     if (!matchMode && appData.timeDelay != 0)
13565       DrawPosition(FALSE, boards[currentMove]);
13566
13567     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13568       programStats.ok_to_send = 1;
13569     }
13570
13571     /* if the first token after the PGN tags is a move
13572      * and not move number 1, retrieve it from the parser
13573      */
13574     if (cm != MoveNumberOne)
13575         LoadGameOneMove(cm);
13576
13577     /* load the remaining moves from the file */
13578     while (LoadGameOneMove(EndOfFile)) {
13579       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13580       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13581     }
13582
13583     /* rewind to the start of the game */
13584     currentMove = backwardMostMove;
13585
13586     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13587
13588     if (oldGameMode == AnalyzeFile) {
13589       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13590       AnalyzeFileEvent();
13591     } else
13592     if (oldGameMode == AnalyzeMode) {
13593       AnalyzeFileEvent();
13594     }
13595
13596     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13597         long int w, b; // [HGM] adjourn: restore saved clock times
13598         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13599         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13600             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13601             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13602         }
13603     }
13604
13605     if(creatingBook) return TRUE;
13606     if (!matchMode && pos > 0) {
13607         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13608     } else
13609     if (matchMode || appData.timeDelay == 0) {
13610       ToEndEvent();
13611     } else if (appData.timeDelay > 0) {
13612       AutoPlayGameLoop();
13613     }
13614
13615     if (appData.debugMode)
13616         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13617
13618     loadFlag = 0; /* [HGM] true game starts */
13619     return TRUE;
13620 }
13621
13622 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13623 int
13624 ReloadPosition (int offset)
13625 {
13626     int positionNumber = lastLoadPositionNumber + offset;
13627     if (lastLoadPositionFP == NULL) {
13628         DisplayError(_("No position has been loaded yet"), 0);
13629         return FALSE;
13630     }
13631     if (positionNumber <= 0) {
13632         DisplayError(_("Can't back up any further"), 0);
13633         return FALSE;
13634     }
13635     return LoadPosition(lastLoadPositionFP, positionNumber,
13636                         lastLoadPositionTitle);
13637 }
13638
13639 /* Load the nth position from the given file */
13640 int
13641 LoadPositionFromFile (char *filename, int n, char *title)
13642 {
13643     FILE *f;
13644     char buf[MSG_SIZ];
13645
13646     if (strcmp(filename, "-") == 0) {
13647         return LoadPosition(stdin, n, "stdin");
13648     } else {
13649         f = fopen(filename, "rb");
13650         if (f == NULL) {
13651             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13652             DisplayError(buf, errno);
13653             return FALSE;
13654         } else {
13655             return LoadPosition(f, n, title);
13656         }
13657     }
13658 }
13659
13660 /* Load the nth position from the given open file, and close it */
13661 int
13662 LoadPosition (FILE *f, int positionNumber, char *title)
13663 {
13664     char *p, line[MSG_SIZ];
13665     Board initial_position;
13666     int i, j, fenMode, pn;
13667
13668     if (gameMode == Training )
13669         SetTrainingModeOff();
13670
13671     if (gameMode != BeginningOfGame) {
13672         Reset(FALSE, TRUE);
13673     }
13674     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13675         fclose(lastLoadPositionFP);
13676     }
13677     if (positionNumber == 0) positionNumber = 1;
13678     lastLoadPositionFP = f;
13679     lastLoadPositionNumber = positionNumber;
13680     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13681     if (first.pr == NoProc && !appData.noChessProgram) {
13682       StartChessProgram(&first);
13683       InitChessProgram(&first, FALSE);
13684     }
13685     pn = positionNumber;
13686     if (positionNumber < 0) {
13687         /* Negative position number means to seek to that byte offset */
13688         if (fseek(f, -positionNumber, 0) == -1) {
13689             DisplayError(_("Can't seek on position file"), 0);
13690             return FALSE;
13691         };
13692         pn = 1;
13693     } else {
13694         if (fseek(f, 0, 0) == -1) {
13695             if (f == lastLoadPositionFP ?
13696                 positionNumber == lastLoadPositionNumber + 1 :
13697                 positionNumber == 1) {
13698                 pn = 1;
13699             } else {
13700                 DisplayError(_("Can't seek on position file"), 0);
13701                 return FALSE;
13702             }
13703         }
13704     }
13705     /* See if this file is FEN or old-style xboard */
13706     if (fgets(line, MSG_SIZ, f) == NULL) {
13707         DisplayError(_("Position not found in file"), 0);
13708         return FALSE;
13709     }
13710     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13711     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13712
13713     if (pn >= 2) {
13714         if (fenMode || line[0] == '#') pn--;
13715         while (pn > 0) {
13716             /* skip positions before number pn */
13717             if (fgets(line, MSG_SIZ, f) == NULL) {
13718                 Reset(TRUE, TRUE);
13719                 DisplayError(_("Position not found in file"), 0);
13720                 return FALSE;
13721             }
13722             if (fenMode || line[0] == '#') pn--;
13723         }
13724     }
13725
13726     if (fenMode) {
13727         char *p;
13728         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13729             DisplayError(_("Bad FEN position in file"), 0);
13730             return FALSE;
13731         }
13732         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13733             sscanf(p+4, "%[^;]", bestMove);
13734         } else *bestMove = NULLCHAR;
13735         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13736             sscanf(p+4, "%[^;]", avoidMove);
13737         } else *avoidMove = NULLCHAR;
13738     } else {
13739         (void) fgets(line, MSG_SIZ, f);
13740         (void) fgets(line, MSG_SIZ, f);
13741
13742         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13743             (void) fgets(line, MSG_SIZ, f);
13744             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13745                 if (*p == ' ')
13746                   continue;
13747                 initial_position[i][j++] = CharToPiece(*p);
13748             }
13749         }
13750
13751         blackPlaysFirst = FALSE;
13752         if (!feof(f)) {
13753             (void) fgets(line, MSG_SIZ, f);
13754             if (strncmp(line, "black", strlen("black"))==0)
13755               blackPlaysFirst = TRUE;
13756         }
13757     }
13758     startedFromSetupPosition = TRUE;
13759
13760     CopyBoard(boards[0], initial_position);
13761     if (blackPlaysFirst) {
13762         currentMove = forwardMostMove = backwardMostMove = 1;
13763         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13764         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13765         CopyBoard(boards[1], initial_position);
13766         DisplayMessage("", _("Black to play"));
13767     } else {
13768         currentMove = forwardMostMove = backwardMostMove = 0;
13769         DisplayMessage("", _("White to play"));
13770     }
13771     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13772     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13773         SendToProgram("force\n", &first);
13774         SendBoard(&first, forwardMostMove);
13775     }
13776     if (appData.debugMode) {
13777 int i, j;
13778   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13779   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13780         fprintf(debugFP, "Load Position\n");
13781     }
13782
13783     if (positionNumber > 1) {
13784       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13785         DisplayTitle(line);
13786     } else {
13787         DisplayTitle(title);
13788     }
13789     gameMode = EditGame;
13790     ModeHighlight();
13791     ResetClocks();
13792     timeRemaining[0][1] = whiteTimeRemaining;
13793     timeRemaining[1][1] = blackTimeRemaining;
13794     DrawPosition(FALSE, boards[currentMove]);
13795
13796     return TRUE;
13797 }
13798
13799
13800 void
13801 CopyPlayerNameIntoFileName (char **dest, char *src)
13802 {
13803     while (*src != NULLCHAR && *src != ',') {
13804         if (*src == ' ') {
13805             *(*dest)++ = '_';
13806             src++;
13807         } else {
13808             *(*dest)++ = *src++;
13809         }
13810     }
13811 }
13812
13813 char *
13814 DefaultFileName (char *ext)
13815 {
13816     static char def[MSG_SIZ];
13817     char *p;
13818
13819     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13820         p = def;
13821         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13822         *p++ = '-';
13823         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13824         *p++ = '.';
13825         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13826     } else {
13827         def[0] = NULLCHAR;
13828     }
13829     return def;
13830 }
13831
13832 /* Save the current game to the given file */
13833 int
13834 SaveGameToFile (char *filename, int append)
13835 {
13836     FILE *f;
13837     char buf[MSG_SIZ];
13838     int result, i, t,tot=0;
13839
13840     if (strcmp(filename, "-") == 0) {
13841         return SaveGame(stdout, 0, NULL);
13842     } else {
13843         for(i=0; i<10; i++) { // upto 10 tries
13844              f = fopen(filename, append ? "a" : "w");
13845              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13846              if(f || errno != 13) break;
13847              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13848              tot += t;
13849         }
13850         if (f == NULL) {
13851             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13852             DisplayError(buf, errno);
13853             return FALSE;
13854         } else {
13855             safeStrCpy(buf, lastMsg, MSG_SIZ);
13856             DisplayMessage(_("Waiting for access to save file"), "");
13857             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13858             DisplayMessage(_("Saving game"), "");
13859             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13860             result = SaveGame(f, 0, NULL);
13861             DisplayMessage(buf, "");
13862             return result;
13863         }
13864     }
13865 }
13866
13867 char *
13868 SavePart (char *str)
13869 {
13870     static char buf[MSG_SIZ];
13871     char *p;
13872
13873     p = strchr(str, ' ');
13874     if (p == NULL) return str;
13875     strncpy(buf, str, p - str);
13876     buf[p - str] = NULLCHAR;
13877     return buf;
13878 }
13879
13880 #define PGN_MAX_LINE 75
13881
13882 #define PGN_SIDE_WHITE  0
13883 #define PGN_SIDE_BLACK  1
13884
13885 static int
13886 FindFirstMoveOutOfBook (int side)
13887 {
13888     int result = -1;
13889
13890     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13891         int index = backwardMostMove;
13892         int has_book_hit = 0;
13893
13894         if( (index % 2) != side ) {
13895             index++;
13896         }
13897
13898         while( index < forwardMostMove ) {
13899             /* Check to see if engine is in book */
13900             int depth = pvInfoList[index].depth;
13901             int score = pvInfoList[index].score;
13902             int in_book = 0;
13903
13904             if( depth <= 2 ) {
13905                 in_book = 1;
13906             }
13907             else if( score == 0 && depth == 63 ) {
13908                 in_book = 1; /* Zappa */
13909             }
13910             else if( score == 2 && depth == 99 ) {
13911                 in_book = 1; /* Abrok */
13912             }
13913
13914             has_book_hit += in_book;
13915
13916             if( ! in_book ) {
13917                 result = index;
13918
13919                 break;
13920             }
13921
13922             index += 2;
13923         }
13924     }
13925
13926     return result;
13927 }
13928
13929 void
13930 GetOutOfBookInfo (char * buf)
13931 {
13932     int oob[2];
13933     int i;
13934     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13935
13936     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13937     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13938
13939     *buf = '\0';
13940
13941     if( oob[0] >= 0 || oob[1] >= 0 ) {
13942         for( i=0; i<2; i++ ) {
13943             int idx = oob[i];
13944
13945             if( idx >= 0 ) {
13946                 if( i > 0 && oob[0] >= 0 ) {
13947                     strcat( buf, "   " );
13948                 }
13949
13950                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13951                 sprintf( buf+strlen(buf), "%s%.2f",
13952                     pvInfoList[idx].score >= 0 ? "+" : "",
13953                     pvInfoList[idx].score / 100.0 );
13954             }
13955         }
13956     }
13957 }
13958
13959 /* Save game in PGN style */
13960 static void
13961 SaveGamePGN2 (FILE *f)
13962 {
13963     int i, offset, linelen, newblock;
13964 //    char *movetext;
13965     char numtext[32];
13966     int movelen, numlen, blank;
13967     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13968
13969     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13970
13971     PrintPGNTags(f, &gameInfo);
13972
13973     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13974
13975     if (backwardMostMove > 0 || startedFromSetupPosition) {
13976         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13977         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13978         fprintf(f, "\n{--------------\n");
13979         PrintPosition(f, backwardMostMove);
13980         fprintf(f, "--------------}\n");
13981         free(fen);
13982     }
13983     else {
13984         /* [AS] Out of book annotation */
13985         if( appData.saveOutOfBookInfo ) {
13986             char buf[64];
13987
13988             GetOutOfBookInfo( buf );
13989
13990             if( buf[0] != '\0' ) {
13991                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13992             }
13993         }
13994
13995         fprintf(f, "\n");
13996     }
13997
13998     i = backwardMostMove;
13999     linelen = 0;
14000     newblock = TRUE;
14001
14002     while (i < forwardMostMove) {
14003         /* Print comments preceding this move */
14004         if (commentList[i] != NULL) {
14005             if (linelen > 0) fprintf(f, "\n");
14006             fprintf(f, "%s", commentList[i]);
14007             linelen = 0;
14008             newblock = TRUE;
14009         }
14010
14011         /* Format move number */
14012         if ((i % 2) == 0)
14013           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
14014         else
14015           if (newblock)
14016             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
14017           else
14018             numtext[0] = NULLCHAR;
14019
14020         numlen = strlen(numtext);
14021         newblock = FALSE;
14022
14023         /* Print move number */
14024         blank = linelen > 0 && numlen > 0;
14025         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
14026             fprintf(f, "\n");
14027             linelen = 0;
14028             blank = 0;
14029         }
14030         if (blank) {
14031             fprintf(f, " ");
14032             linelen++;
14033         }
14034         fprintf(f, "%s", numtext);
14035         linelen += numlen;
14036
14037         /* Get move */
14038         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
14039         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
14040
14041         /* Print move */
14042         blank = linelen > 0 && movelen > 0;
14043         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14044             fprintf(f, "\n");
14045             linelen = 0;
14046             blank = 0;
14047         }
14048         if (blank) {
14049             fprintf(f, " ");
14050             linelen++;
14051         }
14052         fprintf(f, "%s", move_buffer);
14053         linelen += movelen;
14054
14055         /* [AS] Add PV info if present */
14056         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
14057             /* [HGM] add time */
14058             char buf[MSG_SIZ]; int seconds;
14059
14060             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14061
14062             if( seconds <= 0)
14063               buf[0] = 0;
14064             else
14065               if( seconds < 30 )
14066                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14067               else
14068                 {
14069                   seconds = (seconds + 4)/10; // round to full seconds
14070                   if( seconds < 60 )
14071                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14072                   else
14073                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14074                 }
14075
14076             if(appData.cumulativeTimePGN) {
14077                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14078             }
14079
14080             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14081                       pvInfoList[i].score >= 0 ? "+" : "",
14082                       pvInfoList[i].score / 100.0,
14083                       pvInfoList[i].depth,
14084                       buf );
14085
14086             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14087
14088             /* Print score/depth */
14089             blank = linelen > 0 && movelen > 0;
14090             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14091                 fprintf(f, "\n");
14092                 linelen = 0;
14093                 blank = 0;
14094             }
14095             if (blank) {
14096                 fprintf(f, " ");
14097                 linelen++;
14098             }
14099             fprintf(f, "%s", move_buffer);
14100             linelen += movelen;
14101         }
14102
14103         i++;
14104     }
14105
14106     /* Start a new line */
14107     if (linelen > 0) fprintf(f, "\n");
14108
14109     /* Print comments after last move */
14110     if (commentList[i] != NULL) {
14111         fprintf(f, "%s\n", commentList[i]);
14112     }
14113
14114     /* Print result */
14115     if (gameInfo.resultDetails != NULL &&
14116         gameInfo.resultDetails[0] != NULLCHAR) {
14117         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14118         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14119            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14120             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14121         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14122     } else {
14123         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14124     }
14125 }
14126
14127 /* Save game in PGN style and close the file */
14128 int
14129 SaveGamePGN (FILE *f)
14130 {
14131     SaveGamePGN2(f);
14132     fclose(f);
14133     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14134     return TRUE;
14135 }
14136
14137 /* Save game in old style and close the file */
14138 int
14139 SaveGameOldStyle (FILE *f)
14140 {
14141     int i, offset;
14142     time_t tm;
14143
14144     tm = time((time_t *) NULL);
14145
14146     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14147     PrintOpponents(f);
14148
14149     if (backwardMostMove > 0 || startedFromSetupPosition) {
14150         fprintf(f, "\n[--------------\n");
14151         PrintPosition(f, backwardMostMove);
14152         fprintf(f, "--------------]\n");
14153     } else {
14154         fprintf(f, "\n");
14155     }
14156
14157     i = backwardMostMove;
14158     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14159
14160     while (i < forwardMostMove) {
14161         if (commentList[i] != NULL) {
14162             fprintf(f, "[%s]\n", commentList[i]);
14163         }
14164
14165         if ((i % 2) == 1) {
14166             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14167             i++;
14168         } else {
14169             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14170             i++;
14171             if (commentList[i] != NULL) {
14172                 fprintf(f, "\n");
14173                 continue;
14174             }
14175             if (i >= forwardMostMove) {
14176                 fprintf(f, "\n");
14177                 break;
14178             }
14179             fprintf(f, "%s\n", parseList[i]);
14180             i++;
14181         }
14182     }
14183
14184     if (commentList[i] != NULL) {
14185         fprintf(f, "[%s]\n", commentList[i]);
14186     }
14187
14188     /* This isn't really the old style, but it's close enough */
14189     if (gameInfo.resultDetails != NULL &&
14190         gameInfo.resultDetails[0] != NULLCHAR) {
14191         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14192                 gameInfo.resultDetails);
14193     } else {
14194         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14195     }
14196
14197     fclose(f);
14198     return TRUE;
14199 }
14200
14201 /* Save the current game to open file f and close the file */
14202 int
14203 SaveGame (FILE *f, int dummy, char *dummy2)
14204 {
14205     if (gameMode == EditPosition) EditPositionDone(TRUE);
14206     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14207     if (appData.oldSaveStyle)
14208       return SaveGameOldStyle(f);
14209     else
14210       return SaveGamePGN(f);
14211 }
14212
14213 /* Save the current position to the given file */
14214 int
14215 SavePositionToFile (char *filename)
14216 {
14217     FILE *f;
14218     char buf[MSG_SIZ];
14219
14220     if (strcmp(filename, "-") == 0) {
14221         return SavePosition(stdout, 0, NULL);
14222     } else {
14223         f = fopen(filename, "a");
14224         if (f == NULL) {
14225             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14226             DisplayError(buf, errno);
14227             return FALSE;
14228         } else {
14229             safeStrCpy(buf, lastMsg, MSG_SIZ);
14230             DisplayMessage(_("Waiting for access to save file"), "");
14231             flock(fileno(f), LOCK_EX); // [HGM] lock
14232             DisplayMessage(_("Saving position"), "");
14233             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14234             SavePosition(f, 0, NULL);
14235             DisplayMessage(buf, "");
14236             return TRUE;
14237         }
14238     }
14239 }
14240
14241 /* Save the current position to the given open file and close the file */
14242 int
14243 SavePosition (FILE *f, int dummy, char *dummy2)
14244 {
14245     time_t tm;
14246     char *fen;
14247
14248     if (gameMode == EditPosition) EditPositionDone(TRUE);
14249     if (appData.oldSaveStyle) {
14250         tm = time((time_t *) NULL);
14251
14252         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14253         PrintOpponents(f);
14254         fprintf(f, "[--------------\n");
14255         PrintPosition(f, currentMove);
14256         fprintf(f, "--------------]\n");
14257     } else {
14258         fen = PositionToFEN(currentMove, NULL, 1);
14259         fprintf(f, "%s\n", fen);
14260         free(fen);
14261     }
14262     fclose(f);
14263     return TRUE;
14264 }
14265
14266 void
14267 ReloadCmailMsgEvent (int unregister)
14268 {
14269 #if !WIN32
14270     static char *inFilename = NULL;
14271     static char *outFilename;
14272     int i;
14273     struct stat inbuf, outbuf;
14274     int status;
14275
14276     /* Any registered moves are unregistered if unregister is set, */
14277     /* i.e. invoked by the signal handler */
14278     if (unregister) {
14279         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14280             cmailMoveRegistered[i] = FALSE;
14281             if (cmailCommentList[i] != NULL) {
14282                 free(cmailCommentList[i]);
14283                 cmailCommentList[i] = NULL;
14284             }
14285         }
14286         nCmailMovesRegistered = 0;
14287     }
14288
14289     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14290         cmailResult[i] = CMAIL_NOT_RESULT;
14291     }
14292     nCmailResults = 0;
14293
14294     if (inFilename == NULL) {
14295         /* Because the filenames are static they only get malloced once  */
14296         /* and they never get freed                                      */
14297         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14298         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14299
14300         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14301         sprintf(outFilename, "%s.out", appData.cmailGameName);
14302     }
14303
14304     status = stat(outFilename, &outbuf);
14305     if (status < 0) {
14306         cmailMailedMove = FALSE;
14307     } else {
14308         status = stat(inFilename, &inbuf);
14309         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14310     }
14311
14312     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14313        counts the games, notes how each one terminated, etc.
14314
14315        It would be nice to remove this kludge and instead gather all
14316        the information while building the game list.  (And to keep it
14317        in the game list nodes instead of having a bunch of fixed-size
14318        parallel arrays.)  Note this will require getting each game's
14319        termination from the PGN tags, as the game list builder does
14320        not process the game moves.  --mann
14321        */
14322     cmailMsgLoaded = TRUE;
14323     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14324
14325     /* Load first game in the file or popup game menu */
14326     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14327
14328 #endif /* !WIN32 */
14329     return;
14330 }
14331
14332 int
14333 RegisterMove ()
14334 {
14335     FILE *f;
14336     char string[MSG_SIZ];
14337
14338     if (   cmailMailedMove
14339         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14340         return TRUE;            /* Allow free viewing  */
14341     }
14342
14343     /* Unregister move to ensure that we don't leave RegisterMove        */
14344     /* with the move registered when the conditions for registering no   */
14345     /* longer hold                                                       */
14346     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14347         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14348         nCmailMovesRegistered --;
14349
14350         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14351           {
14352               free(cmailCommentList[lastLoadGameNumber - 1]);
14353               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14354           }
14355     }
14356
14357     if (cmailOldMove == -1) {
14358         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14359         return FALSE;
14360     }
14361
14362     if (currentMove > cmailOldMove + 1) {
14363         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14364         return FALSE;
14365     }
14366
14367     if (currentMove < cmailOldMove) {
14368         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14369         return FALSE;
14370     }
14371
14372     if (forwardMostMove > currentMove) {
14373         /* Silently truncate extra moves */
14374         TruncateGame();
14375     }
14376
14377     if (   (currentMove == cmailOldMove + 1)
14378         || (   (currentMove == cmailOldMove)
14379             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14380                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14381         if (gameInfo.result != GameUnfinished) {
14382             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14383         }
14384
14385         if (commentList[currentMove] != NULL) {
14386             cmailCommentList[lastLoadGameNumber - 1]
14387               = StrSave(commentList[currentMove]);
14388         }
14389         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14390
14391         if (appData.debugMode)
14392           fprintf(debugFP, "Saving %s for game %d\n",
14393                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14394
14395         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14396
14397         f = fopen(string, "w");
14398         if (appData.oldSaveStyle) {
14399             SaveGameOldStyle(f); /* also closes the file */
14400
14401             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14402             f = fopen(string, "w");
14403             SavePosition(f, 0, NULL); /* also closes the file */
14404         } else {
14405             fprintf(f, "{--------------\n");
14406             PrintPosition(f, currentMove);
14407             fprintf(f, "--------------}\n\n");
14408
14409             SaveGame(f, 0, NULL); /* also closes the file*/
14410         }
14411
14412         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14413         nCmailMovesRegistered ++;
14414     } else if (nCmailGames == 1) {
14415         DisplayError(_("You have not made a move yet"), 0);
14416         return FALSE;
14417     }
14418
14419     return TRUE;
14420 }
14421
14422 void
14423 MailMoveEvent ()
14424 {
14425 #if !WIN32
14426     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14427     FILE *commandOutput;
14428     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14429     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14430     int nBuffers;
14431     int i;
14432     int archived;
14433     char *arcDir;
14434
14435     if (! cmailMsgLoaded) {
14436         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14437         return;
14438     }
14439
14440     if (nCmailGames == nCmailResults) {
14441         DisplayError(_("No unfinished games"), 0);
14442         return;
14443     }
14444
14445 #if CMAIL_PROHIBIT_REMAIL
14446     if (cmailMailedMove) {
14447       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);
14448         DisplayError(msg, 0);
14449         return;
14450     }
14451 #endif
14452
14453     if (! (cmailMailedMove || RegisterMove())) return;
14454
14455     if (   cmailMailedMove
14456         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14457       snprintf(string, MSG_SIZ, partCommandString,
14458                appData.debugMode ? " -v" : "", appData.cmailGameName);
14459         commandOutput = popen(string, "r");
14460
14461         if (commandOutput == NULL) {
14462             DisplayError(_("Failed to invoke cmail"), 0);
14463         } else {
14464             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14465                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14466             }
14467             if (nBuffers > 1) {
14468                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14469                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14470                 nBytes = MSG_SIZ - 1;
14471             } else {
14472                 (void) memcpy(msg, buffer, nBytes);
14473             }
14474             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14475
14476             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14477                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14478
14479                 archived = TRUE;
14480                 for (i = 0; i < nCmailGames; i ++) {
14481                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14482                         archived = FALSE;
14483                     }
14484                 }
14485                 if (   archived
14486                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14487                         != NULL)) {
14488                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14489                            arcDir,
14490                            appData.cmailGameName,
14491                            gameInfo.date);
14492                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14493                     cmailMsgLoaded = FALSE;
14494                 }
14495             }
14496
14497             DisplayInformation(msg);
14498             pclose(commandOutput);
14499         }
14500     } else {
14501         if ((*cmailMsg) != '\0') {
14502             DisplayInformation(cmailMsg);
14503         }
14504     }
14505
14506     return;
14507 #endif /* !WIN32 */
14508 }
14509
14510 char *
14511 CmailMsg ()
14512 {
14513 #if WIN32
14514     return NULL;
14515 #else
14516     int  prependComma = 0;
14517     char number[5];
14518     char string[MSG_SIZ];       /* Space for game-list */
14519     int  i;
14520
14521     if (!cmailMsgLoaded) return "";
14522
14523     if (cmailMailedMove) {
14524       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14525     } else {
14526         /* Create a list of games left */
14527       snprintf(string, MSG_SIZ, "[");
14528         for (i = 0; i < nCmailGames; i ++) {
14529             if (! (   cmailMoveRegistered[i]
14530                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14531                 if (prependComma) {
14532                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14533                 } else {
14534                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14535                     prependComma = 1;
14536                 }
14537
14538                 strcat(string, number);
14539             }
14540         }
14541         strcat(string, "]");
14542
14543         if (nCmailMovesRegistered + nCmailResults == 0) {
14544             switch (nCmailGames) {
14545               case 1:
14546                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14547                 break;
14548
14549               case 2:
14550                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14551                 break;
14552
14553               default:
14554                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14555                          nCmailGames);
14556                 break;
14557             }
14558         } else {
14559             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14560               case 1:
14561                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14562                          string);
14563                 break;
14564
14565               case 0:
14566                 if (nCmailResults == nCmailGames) {
14567                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14568                 } else {
14569                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14570                 }
14571                 break;
14572
14573               default:
14574                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14575                          string);
14576             }
14577         }
14578     }
14579     return cmailMsg;
14580 #endif /* WIN32 */
14581 }
14582
14583 void
14584 ResetGameEvent ()
14585 {
14586     if (gameMode == Training)
14587       SetTrainingModeOff();
14588
14589     Reset(TRUE, TRUE);
14590     cmailMsgLoaded = FALSE;
14591     if (appData.icsActive) {
14592       SendToICS(ics_prefix);
14593       SendToICS("refresh\n");
14594     }
14595 }
14596
14597 void
14598 ExitEvent (int status)
14599 {
14600     exiting++;
14601     if (exiting > 2) {
14602       /* Give up on clean exit */
14603       exit(status);
14604     }
14605     if (exiting > 1) {
14606       /* Keep trying for clean exit */
14607       return;
14608     }
14609
14610     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14611     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14612
14613     if (telnetISR != NULL) {
14614       RemoveInputSource(telnetISR);
14615     }
14616     if (icsPR != NoProc) {
14617       DestroyChildProcess(icsPR, TRUE);
14618     }
14619
14620     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14621     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14622
14623     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14624     /* make sure this other one finishes before killing it!                  */
14625     if(endingGame) { int count = 0;
14626         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14627         while(endingGame && count++ < 10) DoSleep(1);
14628         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14629     }
14630
14631     /* Kill off chess programs */
14632     if (first.pr != NoProc) {
14633         ExitAnalyzeMode();
14634
14635         DoSleep( appData.delayBeforeQuit );
14636         SendToProgram("quit\n", &first);
14637         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14638     }
14639     if (second.pr != NoProc) {
14640         DoSleep( appData.delayBeforeQuit );
14641         SendToProgram("quit\n", &second);
14642         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14643     }
14644     if (first.isr != NULL) {
14645         RemoveInputSource(first.isr);
14646     }
14647     if (second.isr != NULL) {
14648         RemoveInputSource(second.isr);
14649     }
14650
14651     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14652     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14653
14654     ShutDownFrontEnd();
14655     exit(status);
14656 }
14657
14658 void
14659 PauseEngine (ChessProgramState *cps)
14660 {
14661     SendToProgram("pause\n", cps);
14662     cps->pause = 2;
14663 }
14664
14665 void
14666 UnPauseEngine (ChessProgramState *cps)
14667 {
14668     SendToProgram("resume\n", cps);
14669     cps->pause = 1;
14670 }
14671
14672 void
14673 PauseEvent ()
14674 {
14675     if (appData.debugMode)
14676         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14677     if (pausing) {
14678         pausing = FALSE;
14679         ModeHighlight();
14680         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14681             StartClocks();
14682             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14683                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14684                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14685             }
14686             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14687             HandleMachineMove(stashedInputMove, stalledEngine);
14688             stalledEngine = NULL;
14689             return;
14690         }
14691         if (gameMode == MachinePlaysWhite ||
14692             gameMode == TwoMachinesPlay   ||
14693             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14694             if(first.pause)  UnPauseEngine(&first);
14695             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14696             if(second.pause) UnPauseEngine(&second);
14697             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14698             StartClocks();
14699         } else {
14700             DisplayBothClocks();
14701         }
14702         if (gameMode == PlayFromGameFile) {
14703             if (appData.timeDelay >= 0)
14704                 AutoPlayGameLoop();
14705         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14706             Reset(FALSE, TRUE);
14707             SendToICS(ics_prefix);
14708             SendToICS("refresh\n");
14709         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14710             ForwardInner(forwardMostMove);
14711         }
14712         pauseExamInvalid = FALSE;
14713     } else {
14714         switch (gameMode) {
14715           default:
14716             return;
14717           case IcsExamining:
14718             pauseExamForwardMostMove = forwardMostMove;
14719             pauseExamInvalid = FALSE;
14720             /* fall through */
14721           case IcsObserving:
14722           case IcsPlayingWhite:
14723           case IcsPlayingBlack:
14724             pausing = TRUE;
14725             ModeHighlight();
14726             return;
14727           case PlayFromGameFile:
14728             (void) StopLoadGameTimer();
14729             pausing = TRUE;
14730             ModeHighlight();
14731             break;
14732           case BeginningOfGame:
14733             if (appData.icsActive) return;
14734             /* else fall through */
14735           case MachinePlaysWhite:
14736           case MachinePlaysBlack:
14737           case TwoMachinesPlay:
14738             if (forwardMostMove == 0)
14739               return;           /* don't pause if no one has moved */
14740             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14741                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14742                 if(onMove->pause) {           // thinking engine can be paused
14743                     PauseEngine(onMove);      // do it
14744                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14745                         PauseEngine(onMove->other);
14746                     else
14747                         SendToProgram("easy\n", onMove->other);
14748                     StopClocks();
14749                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14750             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14751                 if(first.pause) {
14752                     PauseEngine(&first);
14753                     StopClocks();
14754                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14755             } else { // human on move, pause pondering by either method
14756                 if(first.pause)
14757                     PauseEngine(&first);
14758                 else if(appData.ponderNextMove)
14759                     SendToProgram("easy\n", &first);
14760                 StopClocks();
14761             }
14762             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14763           case AnalyzeMode:
14764             pausing = TRUE;
14765             ModeHighlight();
14766             break;
14767         }
14768     }
14769 }
14770
14771 void
14772 EditCommentEvent ()
14773 {
14774     char title[MSG_SIZ];
14775
14776     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14777       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14778     } else {
14779       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14780                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14781                parseList[currentMove - 1]);
14782     }
14783
14784     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14785 }
14786
14787
14788 void
14789 EditTagsEvent ()
14790 {
14791     char *tags = PGNTags(&gameInfo);
14792     bookUp = FALSE;
14793     EditTagsPopUp(tags, NULL);
14794     free(tags);
14795 }
14796
14797 void
14798 StartSecond ()
14799 {
14800     if(WaitForEngine(&second, StartSecond)) return;
14801     InitChessProgram(&second, FALSE);
14802     FeedMovesToProgram(&second, currentMove);
14803
14804     SendToProgram("analyze\n", &second);
14805     second.analyzing = TRUE;
14806     ThawUI();
14807 }
14808
14809 void
14810 ToggleSecond ()
14811 {
14812   if(second.analyzing) {
14813     SendToProgram("exit\n", &second);
14814     second.analyzing = FALSE;
14815   } else {
14816     StartSecond();
14817   }
14818 }
14819
14820 /* Toggle ShowThinking */
14821 void
14822 ToggleShowThinking()
14823 {
14824   appData.showThinking = !appData.showThinking;
14825   ShowThinkingEvent();
14826 }
14827
14828 int
14829 AnalyzeModeEvent ()
14830 {
14831     char buf[MSG_SIZ];
14832
14833     if (!first.analysisSupport) {
14834       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14835       DisplayError(buf, 0);
14836       return 0;
14837     }
14838     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14839     if (appData.icsActive) {
14840         if (gameMode != IcsObserving) {
14841           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14842             DisplayError(buf, 0);
14843             /* secure check */
14844             if (appData.icsEngineAnalyze) {
14845                 if (appData.debugMode)
14846                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14847                 ExitAnalyzeMode();
14848                 ModeHighlight();
14849             }
14850             return 0;
14851         }
14852         /* if enable, user wants to disable icsEngineAnalyze */
14853         if (appData.icsEngineAnalyze) {
14854                 ExitAnalyzeMode();
14855                 ModeHighlight();
14856                 return 0;
14857         }
14858         appData.icsEngineAnalyze = TRUE;
14859         if (appData.debugMode)
14860             fprintf(debugFP, "ICS engine analyze starting... \n");
14861     }
14862
14863     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14864     if (appData.noChessProgram || gameMode == AnalyzeMode)
14865       return 0;
14866
14867     if (gameMode != AnalyzeFile) {
14868         if (!appData.icsEngineAnalyze) {
14869                EditGameEvent();
14870                if (gameMode != EditGame) return 0;
14871         }
14872         if (!appData.showThinking) ToggleShowThinking();
14873         ResurrectChessProgram();
14874         SendToProgram("analyze\n", &first);
14875         first.analyzing = TRUE;
14876         /*first.maybeThinking = TRUE;*/
14877         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14878         EngineOutputPopUp();
14879     }
14880     if (!appData.icsEngineAnalyze) {
14881         gameMode = AnalyzeMode;
14882         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14883     }
14884     pausing = FALSE;
14885     ModeHighlight();
14886     SetGameInfo();
14887
14888     StartAnalysisClock();
14889     GetTimeMark(&lastNodeCountTime);
14890     lastNodeCount = 0;
14891     return 1;
14892 }
14893
14894 void
14895 AnalyzeFileEvent ()
14896 {
14897     if (appData.noChessProgram || gameMode == AnalyzeFile)
14898       return;
14899
14900     if (!first.analysisSupport) {
14901       char buf[MSG_SIZ];
14902       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14903       DisplayError(buf, 0);
14904       return;
14905     }
14906
14907     if (gameMode != AnalyzeMode) {
14908         keepInfo = 1; // mere annotating should not alter PGN tags
14909         EditGameEvent();
14910         keepInfo = 0;
14911         if (gameMode != EditGame) return;
14912         if (!appData.showThinking) ToggleShowThinking();
14913         ResurrectChessProgram();
14914         SendToProgram("analyze\n", &first);
14915         first.analyzing = TRUE;
14916         /*first.maybeThinking = TRUE;*/
14917         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14918         EngineOutputPopUp();
14919     }
14920     gameMode = AnalyzeFile;
14921     pausing = FALSE;
14922     ModeHighlight();
14923
14924     StartAnalysisClock();
14925     GetTimeMark(&lastNodeCountTime);
14926     lastNodeCount = 0;
14927     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14928     AnalysisPeriodicEvent(1);
14929 }
14930
14931 void
14932 MachineWhiteEvent ()
14933 {
14934     char buf[MSG_SIZ];
14935     char *bookHit = NULL;
14936
14937     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14938       return;
14939
14940
14941     if (gameMode == PlayFromGameFile ||
14942         gameMode == TwoMachinesPlay  ||
14943         gameMode == Training         ||
14944         gameMode == AnalyzeMode      ||
14945         gameMode == EndOfGame)
14946         EditGameEvent();
14947
14948     if (gameMode == EditPosition)
14949         EditPositionDone(TRUE);
14950
14951     if (!WhiteOnMove(currentMove)) {
14952         DisplayError(_("It is not White's turn"), 0);
14953         return;
14954     }
14955
14956     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14957       ExitAnalyzeMode();
14958
14959     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14960         gameMode == AnalyzeFile)
14961         TruncateGame();
14962
14963     ResurrectChessProgram();    /* in case it isn't running */
14964     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14965         gameMode = MachinePlaysWhite;
14966         ResetClocks();
14967     } else
14968     gameMode = MachinePlaysWhite;
14969     pausing = FALSE;
14970     ModeHighlight();
14971     SetGameInfo();
14972     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14973     DisplayTitle(buf);
14974     if (first.sendName) {
14975       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14976       SendToProgram(buf, &first);
14977     }
14978     if (first.sendTime) {
14979       if (first.useColors) {
14980         SendToProgram("black\n", &first); /*gnu kludge*/
14981       }
14982       SendTimeRemaining(&first, TRUE);
14983     }
14984     if (first.useColors) {
14985       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14986     }
14987     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14988     SetMachineThinkingEnables();
14989     first.maybeThinking = TRUE;
14990     StartClocks();
14991     firstMove = FALSE;
14992
14993     if (appData.autoFlipView && !flipView) {
14994       flipView = !flipView;
14995       DrawPosition(FALSE, NULL);
14996       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14997     }
14998
14999     if(bookHit) { // [HGM] book: simulate book reply
15000         static char bookMove[MSG_SIZ]; // a bit generous?
15001
15002         programStats.nodes = programStats.depth = programStats.time =
15003         programStats.score = programStats.got_only_move = 0;
15004         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15005
15006         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15007         strcat(bookMove, bookHit);
15008         savedMessage = bookMove; // args for deferred call
15009         savedState = &first;
15010         ScheduleDelayedEvent(DeferredBookMove, 1);
15011     }
15012 }
15013
15014 void
15015 MachineBlackEvent ()
15016 {
15017   char buf[MSG_SIZ];
15018   char *bookHit = NULL;
15019
15020     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
15021         return;
15022
15023
15024     if (gameMode == PlayFromGameFile ||
15025         gameMode == TwoMachinesPlay  ||
15026         gameMode == Training         ||
15027         gameMode == AnalyzeMode      ||
15028         gameMode == EndOfGame)
15029         EditGameEvent();
15030
15031     if (gameMode == EditPosition)
15032         EditPositionDone(TRUE);
15033
15034     if (WhiteOnMove(currentMove)) {
15035         DisplayError(_("It is not Black's turn"), 0);
15036         return;
15037     }
15038
15039     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15040       ExitAnalyzeMode();
15041
15042     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15043         gameMode == AnalyzeFile)
15044         TruncateGame();
15045
15046     ResurrectChessProgram();    /* in case it isn't running */
15047     gameMode = MachinePlaysBlack;
15048     pausing = FALSE;
15049     ModeHighlight();
15050     SetGameInfo();
15051     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15052     DisplayTitle(buf);
15053     if (first.sendName) {
15054       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
15055       SendToProgram(buf, &first);
15056     }
15057     if (first.sendTime) {
15058       if (first.useColors) {
15059         SendToProgram("white\n", &first); /*gnu kludge*/
15060       }
15061       SendTimeRemaining(&first, FALSE);
15062     }
15063     if (first.useColors) {
15064       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
15065     }
15066     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15067     SetMachineThinkingEnables();
15068     first.maybeThinking = TRUE;
15069     StartClocks();
15070
15071     if (appData.autoFlipView && flipView) {
15072       flipView = !flipView;
15073       DrawPosition(FALSE, NULL);
15074       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15075     }
15076     if(bookHit) { // [HGM] book: simulate book reply
15077         static char bookMove[MSG_SIZ]; // a bit generous?
15078
15079         programStats.nodes = programStats.depth = programStats.time =
15080         programStats.score = programStats.got_only_move = 0;
15081         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15082
15083         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15084         strcat(bookMove, bookHit);
15085         savedMessage = bookMove; // args for deferred call
15086         savedState = &first;
15087         ScheduleDelayedEvent(DeferredBookMove, 1);
15088     }
15089 }
15090
15091
15092 void
15093 DisplayTwoMachinesTitle ()
15094 {
15095     char buf[MSG_SIZ];
15096     if (appData.matchGames > 0) {
15097         if(appData.tourneyFile[0]) {
15098           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15099                    gameInfo.white, _("vs."), gameInfo.black,
15100                    nextGame+1, appData.matchGames+1,
15101                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15102         } else
15103         if (first.twoMachinesColor[0] == 'w') {
15104           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15105                    gameInfo.white, _("vs."),  gameInfo.black,
15106                    first.matchWins, second.matchWins,
15107                    matchGame - 1 - (first.matchWins + second.matchWins));
15108         } else {
15109           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15110                    gameInfo.white, _("vs."), gameInfo.black,
15111                    second.matchWins, first.matchWins,
15112                    matchGame - 1 - (first.matchWins + second.matchWins));
15113         }
15114     } else {
15115       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15116     }
15117     DisplayTitle(buf);
15118 }
15119
15120 void
15121 SettingsMenuIfReady ()
15122 {
15123   if (second.lastPing != second.lastPong) {
15124     DisplayMessage("", _("Waiting for second chess program"));
15125     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15126     return;
15127   }
15128   ThawUI();
15129   DisplayMessage("", "");
15130   SettingsPopUp(&second);
15131 }
15132
15133 int
15134 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15135 {
15136     char buf[MSG_SIZ];
15137     if (cps->pr == NoProc) {
15138         StartChessProgram(cps);
15139         if (cps->protocolVersion == 1) {
15140           retry();
15141           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15142         } else {
15143           /* kludge: allow timeout for initial "feature" command */
15144           if(retry != TwoMachinesEventIfReady) FreezeUI();
15145           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15146           DisplayMessage("", buf);
15147           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15148         }
15149         return 1;
15150     }
15151     return 0;
15152 }
15153
15154 void
15155 TwoMachinesEvent P((void))
15156 {
15157     int i, move = forwardMostMove;
15158     char buf[MSG_SIZ];
15159     ChessProgramState *onmove;
15160     char *bookHit = NULL;
15161     static int stalling = 0;
15162     TimeMark now;
15163     long wait;
15164
15165     if (appData.noChessProgram) return;
15166
15167     switch (gameMode) {
15168       case TwoMachinesPlay:
15169         return;
15170       case MachinePlaysWhite:
15171       case MachinePlaysBlack:
15172         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15173             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15174             return;
15175         }
15176         /* fall through */
15177       case BeginningOfGame:
15178       case PlayFromGameFile:
15179       case EndOfGame:
15180         EditGameEvent();
15181         if (gameMode != EditGame) return;
15182         break;
15183       case EditPosition:
15184         EditPositionDone(TRUE);
15185         break;
15186       case AnalyzeMode:
15187       case AnalyzeFile:
15188         ExitAnalyzeMode();
15189         break;
15190       case EditGame:
15191       default:
15192         break;
15193     }
15194
15195 //    forwardMostMove = currentMove;
15196     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15197     startingEngine = TRUE;
15198
15199     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15200
15201     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15202     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15203       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15204       return;
15205     }
15206   if(!appData.epd) {
15207     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15208
15209     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15210                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15211         startingEngine = matchMode = FALSE;
15212         DisplayError("second engine does not play this", 0);
15213         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15214         EditGameEvent(); // switch back to EditGame mode
15215         return;
15216     }
15217
15218     if(!stalling) {
15219       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15220       SendToProgram("force\n", &second);
15221       stalling = 1;
15222       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15223       return;
15224     }
15225   }
15226     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15227     if(appData.matchPause>10000 || appData.matchPause<10)
15228                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15229     wait = SubtractTimeMarks(&now, &pauseStart);
15230     if(wait < appData.matchPause) {
15231         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15232         return;
15233     }
15234     // we are now committed to starting the game
15235     stalling = 0;
15236     DisplayMessage("", "");
15237   if(!appData.epd) {
15238     if (startedFromSetupPosition) {
15239         SendBoard(&second, backwardMostMove);
15240     if (appData.debugMode) {
15241         fprintf(debugFP, "Two Machines\n");
15242     }
15243     }
15244     for (i = backwardMostMove; i < forwardMostMove; i++) {
15245         SendMoveToProgram(i, &second);
15246     }
15247   }
15248
15249     gameMode = TwoMachinesPlay;
15250     pausing = startingEngine = FALSE;
15251     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15252     SetGameInfo();
15253     DisplayTwoMachinesTitle();
15254     firstMove = TRUE;
15255     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15256         onmove = &first;
15257     } else {
15258         onmove = &second;
15259     }
15260     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15261     SendToProgram(first.computerString, &first);
15262     if (first.sendName) {
15263       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15264       SendToProgram(buf, &first);
15265     }
15266   if(!appData.epd) {
15267     SendToProgram(second.computerString, &second);
15268     if (second.sendName) {
15269       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15270       SendToProgram(buf, &second);
15271     }
15272   }
15273
15274     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15275         ResetClocks();
15276         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15277         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15278     }
15279     if (onmove->sendTime) {
15280       if (onmove->useColors) {
15281         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15282       }
15283       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15284     }
15285     if (onmove->useColors) {
15286       SendToProgram(onmove->twoMachinesColor, onmove);
15287     }
15288     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15289 //    SendToProgram("go\n", onmove);
15290     onmove->maybeThinking = TRUE;
15291     SetMachineThinkingEnables();
15292
15293     StartClocks();
15294
15295     if(bookHit) { // [HGM] book: simulate book reply
15296         static char bookMove[MSG_SIZ]; // a bit generous?
15297
15298         programStats.nodes = programStats.depth = programStats.time =
15299         programStats.score = programStats.got_only_move = 0;
15300         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15301
15302         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15303         strcat(bookMove, bookHit);
15304         savedMessage = bookMove; // args for deferred call
15305         savedState = onmove;
15306         ScheduleDelayedEvent(DeferredBookMove, 1);
15307     }
15308 }
15309
15310 void
15311 TrainingEvent ()
15312 {
15313     if (gameMode == Training) {
15314       SetTrainingModeOff();
15315       gameMode = PlayFromGameFile;
15316       DisplayMessage("", _("Training mode off"));
15317     } else {
15318       gameMode = Training;
15319       animateTraining = appData.animate;
15320
15321       /* make sure we are not already at the end of the game */
15322       if (currentMove < forwardMostMove) {
15323         SetTrainingModeOn();
15324         DisplayMessage("", _("Training mode on"));
15325       } else {
15326         gameMode = PlayFromGameFile;
15327         DisplayError(_("Already at end of game"), 0);
15328       }
15329     }
15330     ModeHighlight();
15331 }
15332
15333 void
15334 IcsClientEvent ()
15335 {
15336     if (!appData.icsActive) return;
15337     switch (gameMode) {
15338       case IcsPlayingWhite:
15339       case IcsPlayingBlack:
15340       case IcsObserving:
15341       case IcsIdle:
15342       case BeginningOfGame:
15343       case IcsExamining:
15344         return;
15345
15346       case EditGame:
15347         break;
15348
15349       case EditPosition:
15350         EditPositionDone(TRUE);
15351         break;
15352
15353       case AnalyzeMode:
15354       case AnalyzeFile:
15355         ExitAnalyzeMode();
15356         break;
15357
15358       default:
15359         EditGameEvent();
15360         break;
15361     }
15362
15363     gameMode = IcsIdle;
15364     ModeHighlight();
15365     return;
15366 }
15367
15368 void
15369 EditGameEvent ()
15370 {
15371     int i;
15372
15373     switch (gameMode) {
15374       case Training:
15375         SetTrainingModeOff();
15376         break;
15377       case MachinePlaysWhite:
15378       case MachinePlaysBlack:
15379       case BeginningOfGame:
15380         SendToProgram("force\n", &first);
15381         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15382             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15383                 char buf[MSG_SIZ];
15384                 abortEngineThink = TRUE;
15385                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15386                 SendToProgram(buf, &first);
15387                 DisplayMessage("Aborting engine think", "");
15388                 FreezeUI();
15389             }
15390         }
15391         SetUserThinkingEnables();
15392         break;
15393       case PlayFromGameFile:
15394         (void) StopLoadGameTimer();
15395         if (gameFileFP != NULL) {
15396             gameFileFP = NULL;
15397         }
15398         break;
15399       case EditPosition:
15400         EditPositionDone(TRUE);
15401         break;
15402       case AnalyzeMode:
15403       case AnalyzeFile:
15404         ExitAnalyzeMode();
15405         SendToProgram("force\n", &first);
15406         break;
15407       case TwoMachinesPlay:
15408         GameEnds(EndOfFile, NULL, GE_PLAYER);
15409         ResurrectChessProgram();
15410         SetUserThinkingEnables();
15411         break;
15412       case EndOfGame:
15413         ResurrectChessProgram();
15414         break;
15415       case IcsPlayingBlack:
15416       case IcsPlayingWhite:
15417         DisplayError(_("Warning: You are still playing a game"), 0);
15418         break;
15419       case IcsObserving:
15420         DisplayError(_("Warning: You are still observing a game"), 0);
15421         break;
15422       case IcsExamining:
15423         DisplayError(_("Warning: You are still examining a game"), 0);
15424         break;
15425       case IcsIdle:
15426         break;
15427       case EditGame:
15428       default:
15429         return;
15430     }
15431
15432     pausing = FALSE;
15433     StopClocks();
15434     first.offeredDraw = second.offeredDraw = 0;
15435
15436     if (gameMode == PlayFromGameFile) {
15437         whiteTimeRemaining = timeRemaining[0][currentMove];
15438         blackTimeRemaining = timeRemaining[1][currentMove];
15439         DisplayTitle("");
15440     }
15441
15442     if (gameMode == MachinePlaysWhite ||
15443         gameMode == MachinePlaysBlack ||
15444         gameMode == TwoMachinesPlay ||
15445         gameMode == EndOfGame) {
15446         i = forwardMostMove;
15447         while (i > currentMove) {
15448             SendToProgram("undo\n", &first);
15449             i--;
15450         }
15451         if(!adjustedClock) {
15452         whiteTimeRemaining = timeRemaining[0][currentMove];
15453         blackTimeRemaining = timeRemaining[1][currentMove];
15454         DisplayBothClocks();
15455         }
15456         if (whiteFlag || blackFlag) {
15457             whiteFlag = blackFlag = 0;
15458         }
15459         DisplayTitle("");
15460     }
15461
15462     gameMode = EditGame;
15463     ModeHighlight();
15464     SetGameInfo();
15465 }
15466
15467 void
15468 EditPositionEvent ()
15469 {
15470     int i;
15471     if (gameMode == EditPosition) {
15472         EditGameEvent();
15473         return;
15474     }
15475
15476     EditGameEvent();
15477     if (gameMode != EditGame) return;
15478
15479     gameMode = EditPosition;
15480     ModeHighlight();
15481     SetGameInfo();
15482     CopyBoard(rightsBoard, nullBoard);
15483     if (currentMove > 0)
15484       CopyBoard(boards[0], boards[currentMove]);
15485     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15486       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15487
15488     blackPlaysFirst = !WhiteOnMove(currentMove);
15489     ResetClocks();
15490     currentMove = forwardMostMove = backwardMostMove = 0;
15491     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15492     DisplayMove(-1);
15493     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15494 }
15495
15496 void
15497 ExitAnalyzeMode ()
15498 {
15499     /* [DM] icsEngineAnalyze - possible call from other functions */
15500     if (appData.icsEngineAnalyze) {
15501         appData.icsEngineAnalyze = FALSE;
15502
15503         DisplayMessage("",_("Close ICS engine analyze..."));
15504     }
15505     if (first.analysisSupport && first.analyzing) {
15506       SendToBoth("exit\n");
15507       first.analyzing = second.analyzing = FALSE;
15508     }
15509     thinkOutput[0] = NULLCHAR;
15510 }
15511
15512 void
15513 EditPositionDone (Boolean fakeRights)
15514 {
15515     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15516
15517     startedFromSetupPosition = TRUE;
15518     InitChessProgram(&first, FALSE);
15519     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15520       int r, f;
15521       boards[0][EP_STATUS] = EP_NONE;
15522       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15523       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15524         if(rightsBoard[r][f]) {
15525           ChessSquare p = boards[0][r][f];
15526           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15527           else if(p == king) boards[0][CASTLING][2] = f;
15528           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15529           else rightsBoard[r][f] = 2; // mark for second pass
15530         }
15531       }
15532       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15533         if(rightsBoard[r][f] == 2) {
15534           ChessSquare p = boards[0][r][f];
15535           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15536           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15537         }
15538       }
15539     }
15540     SendToProgram("force\n", &first);
15541     if (blackPlaysFirst) {
15542         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15543         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15544         currentMove = forwardMostMove = backwardMostMove = 1;
15545         CopyBoard(boards[1], boards[0]);
15546     } else {
15547         currentMove = forwardMostMove = backwardMostMove = 0;
15548     }
15549     SendBoard(&first, forwardMostMove);
15550     if (appData.debugMode) {
15551         fprintf(debugFP, "EditPosDone\n");
15552     }
15553     DisplayTitle("");
15554     DisplayMessage("", "");
15555     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15556     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15557     gameMode = EditGame;
15558     ModeHighlight();
15559     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15560     ClearHighlights(); /* [AS] */
15561 }
15562
15563 /* Pause for `ms' milliseconds */
15564 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15565 void
15566 TimeDelay (long ms)
15567 {
15568     TimeMark m1, m2;
15569
15570     GetTimeMark(&m1);
15571     do {
15572         GetTimeMark(&m2);
15573     } while (SubtractTimeMarks(&m2, &m1) < ms);
15574 }
15575
15576 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15577 void
15578 SendMultiLineToICS (char *buf)
15579 {
15580     char temp[MSG_SIZ+1], *p;
15581     int len;
15582
15583     len = strlen(buf);
15584     if (len > MSG_SIZ)
15585       len = MSG_SIZ;
15586
15587     strncpy(temp, buf, len);
15588     temp[len] = 0;
15589
15590     p = temp;
15591     while (*p) {
15592         if (*p == '\n' || *p == '\r')
15593           *p = ' ';
15594         ++p;
15595     }
15596
15597     strcat(temp, "\n");
15598     SendToICS(temp);
15599     SendToPlayer(temp, strlen(temp));
15600 }
15601
15602 void
15603 SetWhiteToPlayEvent ()
15604 {
15605     if (gameMode == EditPosition) {
15606         blackPlaysFirst = FALSE;
15607         DisplayBothClocks();    /* works because currentMove is 0 */
15608     } else if (gameMode == IcsExamining) {
15609         SendToICS(ics_prefix);
15610         SendToICS("tomove white\n");
15611     }
15612 }
15613
15614 void
15615 SetBlackToPlayEvent ()
15616 {
15617     if (gameMode == EditPosition) {
15618         blackPlaysFirst = TRUE;
15619         currentMove = 1;        /* kludge */
15620         DisplayBothClocks();
15621         currentMove = 0;
15622     } else if (gameMode == IcsExamining) {
15623         SendToICS(ics_prefix);
15624         SendToICS("tomove black\n");
15625     }
15626 }
15627
15628 void
15629 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15630 {
15631     char buf[MSG_SIZ];
15632     ChessSquare piece = boards[0][y][x];
15633     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15634     static int lastVariant;
15635     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15636
15637     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15638
15639     switch (selection) {
15640       case ClearBoard:
15641         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15642         MarkTargetSquares(1);
15643         CopyBoard(currentBoard, boards[0]);
15644         CopyBoard(menuBoard, initialPosition);
15645         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15646             SendToICS(ics_prefix);
15647             SendToICS("bsetup clear\n");
15648         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15649             SendToICS(ics_prefix);
15650             SendToICS("clearboard\n");
15651         } else {
15652             int nonEmpty = 0;
15653             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15654                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15655                 for (y = 0; y < BOARD_HEIGHT; y++) {
15656                     if (gameMode == IcsExamining) {
15657                         if (boards[currentMove][y][x] != EmptySquare) {
15658                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15659                                     AAA + x, ONE + y);
15660                             SendToICS(buf);
15661                         }
15662                     } else if(boards[0][y][x] != DarkSquare) {
15663                         if(boards[0][y][x] != p) nonEmpty++;
15664                         boards[0][y][x] = p;
15665                     }
15666                 }
15667             }
15668             CopyBoard(rightsBoard, nullBoard);
15669             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15670                 int r, i;
15671                 for(r = 0; r < BOARD_HEIGHT; r++) {
15672                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15673                     ChessSquare p = menuBoard[r][x];
15674                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15675                   }
15676                 }
15677                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15678                 DisplayMessage("Clicking clock again restores position", "");
15679                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15680                 if(!nonEmpty) { // asked to clear an empty board
15681                     CopyBoard(boards[0], menuBoard);
15682                 } else
15683                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15684                     CopyBoard(boards[0], initialPosition);
15685                 } else
15686                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15687                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15688                     CopyBoard(boards[0], erasedBoard);
15689                 } else
15690                     CopyBoard(erasedBoard, currentBoard);
15691
15692                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15693                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15694             }
15695         }
15696         if (gameMode == EditPosition) {
15697             DrawPosition(FALSE, boards[0]);
15698         }
15699         break;
15700
15701       case WhitePlay:
15702         SetWhiteToPlayEvent();
15703         break;
15704
15705       case BlackPlay:
15706         SetBlackToPlayEvent();
15707         break;
15708
15709       case EmptySquare:
15710         if (gameMode == IcsExamining) {
15711             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15712             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15713             SendToICS(buf);
15714         } else {
15715             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15716                 if(x == BOARD_LEFT-2) {
15717                     if(y < handSize-1-gameInfo.holdingsSize) break;
15718                     boards[0][y][1] = 0;
15719                 } else
15720                 if(x == BOARD_RGHT+1) {
15721                     if(y >= gameInfo.holdingsSize) break;
15722                     boards[0][y][BOARD_WIDTH-2] = 0;
15723                 } else break;
15724             }
15725             boards[0][y][x] = EmptySquare;
15726             DrawPosition(FALSE, boards[0]);
15727         }
15728         break;
15729
15730       case PromotePiece:
15731         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15732            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15733             selection = (ChessSquare) (PROMOTED(piece));
15734         } else if(piece == EmptySquare) selection = WhiteSilver;
15735         else selection = (ChessSquare)((int)piece - 1);
15736         goto defaultlabel;
15737
15738       case DemotePiece:
15739         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15740            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15741             selection = (ChessSquare) (DEMOTED(piece));
15742         } else if(piece == EmptySquare) selection = BlackSilver;
15743         else selection = (ChessSquare)((int)piece + 1);
15744         goto defaultlabel;
15745
15746       case WhiteQueen:
15747       case BlackQueen:
15748         if(gameInfo.variant == VariantShatranj ||
15749            gameInfo.variant == VariantXiangqi  ||
15750            gameInfo.variant == VariantCourier  ||
15751            gameInfo.variant == VariantASEAN    ||
15752            gameInfo.variant == VariantMakruk     )
15753             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15754         goto defaultlabel;
15755
15756       case WhiteRook:
15757         baseRank = 0;
15758       case BlackRook:
15759         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15760         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15761         goto defaultlabel;
15762
15763       case WhiteKing:
15764         baseRank = 0;
15765       case BlackKing:
15766         if(gameInfo.variant == VariantXiangqi)
15767             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15768         if(gameInfo.variant == VariantKnightmate)
15769             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15770         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15771       default:
15772         defaultlabel:
15773         if (gameMode == IcsExamining) {
15774             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15775             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15776                      PieceToChar(selection), AAA + x, ONE + y);
15777             SendToICS(buf);
15778         } else {
15779             rightsBoard[y][x] = hasRights;
15780             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15781                 int n;
15782                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15783                     n = PieceToNumber(selection - BlackPawn);
15784                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15785                     boards[0][handSize-1-n][0] = selection;
15786                     boards[0][handSize-1-n][1]++;
15787                 } else
15788                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15789                     n = PieceToNumber(selection);
15790                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15791                     boards[0][n][BOARD_WIDTH-1] = selection;
15792                     boards[0][n][BOARD_WIDTH-2]++;
15793                 }
15794             } else
15795             boards[0][y][x] = selection;
15796             DrawPosition(TRUE, boards[0]);
15797             ClearHighlights();
15798             fromX = fromY = -1;
15799         }
15800         break;
15801     }
15802 }
15803
15804
15805 void
15806 DropMenuEvent (ChessSquare selection, int x, int y)
15807 {
15808     ChessMove moveType;
15809
15810     switch (gameMode) {
15811       case IcsPlayingWhite:
15812       case MachinePlaysBlack:
15813         if (!WhiteOnMove(currentMove)) {
15814             DisplayMoveError(_("It is Black's turn"));
15815             return;
15816         }
15817         moveType = WhiteDrop;
15818         break;
15819       case IcsPlayingBlack:
15820       case MachinePlaysWhite:
15821         if (WhiteOnMove(currentMove)) {
15822             DisplayMoveError(_("It is White's turn"));
15823             return;
15824         }
15825         moveType = BlackDrop;
15826         break;
15827       case EditGame:
15828         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15829         break;
15830       default:
15831         return;
15832     }
15833
15834     if (moveType == BlackDrop && selection < BlackPawn) {
15835       selection = (ChessSquare) ((int) selection
15836                                  + (int) BlackPawn - (int) WhitePawn);
15837     }
15838     if (boards[currentMove][y][x] != EmptySquare) {
15839         DisplayMoveError(_("That square is occupied"));
15840         return;
15841     }
15842
15843     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15844 }
15845
15846 void
15847 AcceptEvent ()
15848 {
15849     /* Accept a pending offer of any kind from opponent */
15850
15851     if (appData.icsActive) {
15852         SendToICS(ics_prefix);
15853         SendToICS("accept\n");
15854     } else if (cmailMsgLoaded) {
15855         if (currentMove == cmailOldMove &&
15856             commentList[cmailOldMove] != NULL &&
15857             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15858                    "Black offers a draw" : "White offers a draw")) {
15859             TruncateGame();
15860             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15861             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15862         } else {
15863             DisplayError(_("There is no pending offer on this move"), 0);
15864             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15865         }
15866     } else {
15867         /* Not used for offers from chess program */
15868     }
15869 }
15870
15871 void
15872 DeclineEvent ()
15873 {
15874     /* Decline a pending offer of any kind from opponent */
15875
15876     if (appData.icsActive) {
15877         SendToICS(ics_prefix);
15878         SendToICS("decline\n");
15879     } else if (cmailMsgLoaded) {
15880         if (currentMove == cmailOldMove &&
15881             commentList[cmailOldMove] != NULL &&
15882             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15883                    "Black offers a draw" : "White offers a draw")) {
15884 #ifdef NOTDEF
15885             AppendComment(cmailOldMove, "Draw declined", TRUE);
15886             DisplayComment(cmailOldMove - 1, "Draw declined");
15887 #endif /*NOTDEF*/
15888         } else {
15889             DisplayError(_("There is no pending offer on this move"), 0);
15890         }
15891     } else {
15892         /* Not used for offers from chess program */
15893     }
15894 }
15895
15896 void
15897 RematchEvent ()
15898 {
15899     /* Issue ICS rematch command */
15900     if (appData.icsActive) {
15901         SendToICS(ics_prefix);
15902         SendToICS("rematch\n");
15903     }
15904 }
15905
15906 void
15907 CallFlagEvent ()
15908 {
15909     /* Call your opponent's flag (claim a win on time) */
15910     if (appData.icsActive) {
15911         SendToICS(ics_prefix);
15912         SendToICS("flag\n");
15913     } else {
15914         switch (gameMode) {
15915           default:
15916             return;
15917           case MachinePlaysWhite:
15918             if (whiteFlag) {
15919                 if (blackFlag)
15920                   GameEnds(GameIsDrawn, "Both players ran out of time",
15921                            GE_PLAYER);
15922                 else
15923                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15924             } else {
15925                 DisplayError(_("Your opponent is not out of time"), 0);
15926             }
15927             break;
15928           case MachinePlaysBlack:
15929             if (blackFlag) {
15930                 if (whiteFlag)
15931                   GameEnds(GameIsDrawn, "Both players ran out of time",
15932                            GE_PLAYER);
15933                 else
15934                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15935             } else {
15936                 DisplayError(_("Your opponent is not out of time"), 0);
15937             }
15938             break;
15939         }
15940     }
15941 }
15942
15943 void
15944 ClockClick (int which)
15945 {       // [HGM] code moved to back-end from winboard.c
15946         if(which) { // black clock
15947           if (gameMode == EditPosition || gameMode == IcsExamining) {
15948             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15949             SetBlackToPlayEvent();
15950           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15951                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15952           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15953           } else if (shiftKey) {
15954             AdjustClock(which, -1);
15955           } else if (gameMode == IcsPlayingWhite ||
15956                      gameMode == MachinePlaysBlack) {
15957             CallFlagEvent();
15958           }
15959         } else { // white clock
15960           if (gameMode == EditPosition || gameMode == IcsExamining) {
15961             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15962             SetWhiteToPlayEvent();
15963           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15964                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15965           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15966           } else if (shiftKey) {
15967             AdjustClock(which, -1);
15968           } else if (gameMode == IcsPlayingBlack ||
15969                    gameMode == MachinePlaysWhite) {
15970             CallFlagEvent();
15971           }
15972         }
15973 }
15974
15975 void
15976 DrawEvent ()
15977 {
15978     /* Offer draw or accept pending draw offer from opponent */
15979
15980     if (appData.icsActive) {
15981         /* Note: tournament rules require draw offers to be
15982            made after you make your move but before you punch
15983            your clock.  Currently ICS doesn't let you do that;
15984            instead, you immediately punch your clock after making
15985            a move, but you can offer a draw at any time. */
15986
15987         SendToICS(ics_prefix);
15988         SendToICS("draw\n");
15989         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15990     } else if (cmailMsgLoaded) {
15991         if (currentMove == cmailOldMove &&
15992             commentList[cmailOldMove] != NULL &&
15993             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15994                    "Black offers a draw" : "White offers a draw")) {
15995             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15996             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15997         } else if (currentMove == cmailOldMove + 1) {
15998             char *offer = WhiteOnMove(cmailOldMove) ?
15999               "White offers a draw" : "Black offers a draw";
16000             AppendComment(currentMove, offer, TRUE);
16001             DisplayComment(currentMove - 1, offer);
16002             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
16003         } else {
16004             DisplayError(_("You must make your move before offering a draw"), 0);
16005             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
16006         }
16007     } else if (first.offeredDraw) {
16008         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
16009     } else {
16010         if (first.sendDrawOffers) {
16011             SendToProgram("draw\n", &first);
16012             userOfferedDraw = TRUE;
16013         }
16014     }
16015 }
16016
16017 void
16018 AdjournEvent ()
16019 {
16020     /* Offer Adjourn or accept pending Adjourn offer from opponent */
16021
16022     if (appData.icsActive) {
16023         SendToICS(ics_prefix);
16024         SendToICS("adjourn\n");
16025     } else {
16026         /* Currently GNU Chess doesn't offer or accept Adjourns */
16027     }
16028 }
16029
16030
16031 void
16032 AbortEvent ()
16033 {
16034     /* Offer Abort or accept pending Abort offer from opponent */
16035
16036     if (appData.icsActive) {
16037         SendToICS(ics_prefix);
16038         SendToICS("abort\n");
16039     } else {
16040         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
16041     }
16042 }
16043
16044 void
16045 ResignEvent ()
16046 {
16047     /* Resign.  You can do this even if it's not your turn. */
16048
16049     if (appData.icsActive) {
16050         SendToICS(ics_prefix);
16051         SendToICS("resign\n");
16052     } else {
16053         switch (gameMode) {
16054           case MachinePlaysWhite:
16055             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16056             break;
16057           case MachinePlaysBlack:
16058             GameEnds(BlackWins, "White resigns", GE_PLAYER);
16059             break;
16060           case EditGame:
16061             if (cmailMsgLoaded) {
16062                 TruncateGame();
16063                 if (WhiteOnMove(cmailOldMove)) {
16064                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
16065                 } else {
16066                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16067                 }
16068                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16069             }
16070             break;
16071           default:
16072             break;
16073         }
16074     }
16075 }
16076
16077
16078 void
16079 StopObservingEvent ()
16080 {
16081     /* Stop observing current games */
16082     SendToICS(ics_prefix);
16083     SendToICS("unobserve\n");
16084 }
16085
16086 void
16087 StopExaminingEvent ()
16088 {
16089     /* Stop observing current game */
16090     SendToICS(ics_prefix);
16091     SendToICS("unexamine\n");
16092 }
16093
16094 void
16095 ForwardInner (int target)
16096 {
16097     int limit; int oldSeekGraphUp = seekGraphUp;
16098
16099     if (appData.debugMode)
16100         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16101                 target, currentMove, forwardMostMove);
16102
16103     if (gameMode == EditPosition)
16104       return;
16105
16106     seekGraphUp = FALSE;
16107     MarkTargetSquares(1);
16108     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16109
16110     if (gameMode == PlayFromGameFile && !pausing)
16111       PauseEvent();
16112
16113     if (gameMode == IcsExamining && pausing)
16114       limit = pauseExamForwardMostMove;
16115     else
16116       limit = forwardMostMove;
16117
16118     if (target > limit) target = limit;
16119
16120     if (target > 0 && moveList[target - 1][0]) {
16121         int fromX, fromY, toX, toY;
16122         toX = moveList[target - 1][2] - AAA;
16123         toY = moveList[target - 1][3] - ONE;
16124         if (moveList[target - 1][1] == '@') {
16125             if (appData.highlightLastMove) {
16126                 SetHighlights(-1, -1, toX, toY);
16127             }
16128         } else {
16129             fromX = moveList[target - 1][0] - AAA;
16130             fromY = moveList[target - 1][1] - ONE;
16131             if (target == currentMove + 1) {
16132                 if(moveList[target - 1][4] == ';') { // multi-leg
16133                     killX = moveList[target - 1][5] - AAA;
16134                     killY = moveList[target - 1][6] - ONE;
16135                 }
16136                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16137                 killX = killY = -1;
16138             }
16139             if (appData.highlightLastMove) {
16140                 SetHighlights(fromX, fromY, toX, toY);
16141             }
16142         }
16143     }
16144     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16145         gameMode == Training || gameMode == PlayFromGameFile ||
16146         gameMode == AnalyzeFile) {
16147         while (currentMove < target) {
16148             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16149             SendMoveToProgram(currentMove++, &first);
16150         }
16151     } else {
16152         currentMove = target;
16153     }
16154
16155     if (gameMode == EditGame || gameMode == EndOfGame) {
16156         whiteTimeRemaining = timeRemaining[0][currentMove];
16157         blackTimeRemaining = timeRemaining[1][currentMove];
16158     }
16159     DisplayBothClocks();
16160     DisplayMove(currentMove - 1);
16161     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16162     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16163     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16164         DisplayComment(currentMove - 1, commentList[currentMove]);
16165     }
16166     ClearMap(); // [HGM] exclude: invalidate map
16167 }
16168
16169
16170 void
16171 ForwardEvent ()
16172 {
16173     if (gameMode == IcsExamining && !pausing) {
16174         SendToICS(ics_prefix);
16175         SendToICS("forward\n");
16176     } else {
16177         ForwardInner(currentMove + 1);
16178     }
16179 }
16180
16181 void
16182 ToEndEvent ()
16183 {
16184     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16185         /* to optimze, we temporarily turn off analysis mode while we feed
16186          * the remaining moves to the engine. Otherwise we get analysis output
16187          * after each move.
16188          */
16189         if (first.analysisSupport) {
16190           SendToProgram("exit\nforce\n", &first);
16191           first.analyzing = FALSE;
16192         }
16193     }
16194
16195     if (gameMode == IcsExamining && !pausing) {
16196         SendToICS(ics_prefix);
16197         SendToICS("forward 999999\n");
16198     } else {
16199         ForwardInner(forwardMostMove);
16200     }
16201
16202     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16203         /* we have fed all the moves, so reactivate analysis mode */
16204         SendToProgram("analyze\n", &first);
16205         first.analyzing = TRUE;
16206         /*first.maybeThinking = TRUE;*/
16207         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16208     }
16209 }
16210
16211 void
16212 BackwardInner (int target)
16213 {
16214     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16215
16216     if (appData.debugMode)
16217         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16218                 target, currentMove, forwardMostMove);
16219
16220     if (gameMode == EditPosition) return;
16221     seekGraphUp = FALSE;
16222     MarkTargetSquares(1);
16223     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16224     if (currentMove <= backwardMostMove) {
16225         ClearHighlights();
16226         DrawPosition(full_redraw, boards[currentMove]);
16227         return;
16228     }
16229     if (gameMode == PlayFromGameFile && !pausing)
16230       PauseEvent();
16231
16232     if (moveList[target][0]) {
16233         int fromX, fromY, toX, toY;
16234         toX = moveList[target][2] - AAA;
16235         toY = moveList[target][3] - ONE;
16236         if (moveList[target][1] == '@') {
16237             if (appData.highlightLastMove) {
16238                 SetHighlights(-1, -1, toX, toY);
16239             }
16240         } else {
16241             fromX = moveList[target][0] - AAA;
16242             fromY = moveList[target][1] - ONE;
16243             if (target == currentMove - 1) {
16244                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16245             }
16246             if (appData.highlightLastMove) {
16247                 SetHighlights(fromX, fromY, toX, toY);
16248             }
16249         }
16250     }
16251     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16252         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16253         while (currentMove > target) {
16254             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16255                 // null move cannot be undone. Reload program with move history before it.
16256                 int i;
16257                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16258                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16259                 }
16260                 SendBoard(&first, i);
16261               if(second.analyzing) SendBoard(&second, i);
16262                 for(currentMove=i; currentMove<target; currentMove++) {
16263                     SendMoveToProgram(currentMove, &first);
16264                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16265                 }
16266                 break;
16267             }
16268             SendToBoth("undo\n");
16269             currentMove--;
16270         }
16271     } else {
16272         currentMove = target;
16273     }
16274
16275     if (gameMode == EditGame || gameMode == EndOfGame) {
16276         whiteTimeRemaining = timeRemaining[0][currentMove];
16277         blackTimeRemaining = timeRemaining[1][currentMove];
16278     }
16279     DisplayBothClocks();
16280     DisplayMove(currentMove - 1);
16281     DrawPosition(full_redraw, boards[currentMove]);
16282     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16283     // [HGM] PV info: routine tests if comment empty
16284     DisplayComment(currentMove - 1, commentList[currentMove]);
16285     ClearMap(); // [HGM] exclude: invalidate map
16286 }
16287
16288 void
16289 BackwardEvent ()
16290 {
16291     if (gameMode == IcsExamining && !pausing) {
16292         SendToICS(ics_prefix);
16293         SendToICS("backward\n");
16294     } else {
16295         BackwardInner(currentMove - 1);
16296     }
16297 }
16298
16299 void
16300 ToStartEvent ()
16301 {
16302     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16303         /* to optimize, we temporarily turn off analysis mode while we undo
16304          * all the moves. Otherwise we get analysis output after each undo.
16305          */
16306         if (first.analysisSupport) {
16307           SendToProgram("exit\nforce\n", &first);
16308           first.analyzing = FALSE;
16309         }
16310     }
16311
16312     if (gameMode == IcsExamining && !pausing) {
16313         SendToICS(ics_prefix);
16314         SendToICS("backward 999999\n");
16315     } else {
16316         BackwardInner(backwardMostMove);
16317     }
16318
16319     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16320         /* we have fed all the moves, so reactivate analysis mode */
16321         SendToProgram("analyze\n", &first);
16322         first.analyzing = TRUE;
16323         /*first.maybeThinking = TRUE;*/
16324         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16325     }
16326 }
16327
16328 void
16329 ToNrEvent (int to)
16330 {
16331   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16332   if (to >= forwardMostMove) to = forwardMostMove;
16333   if (to <= backwardMostMove) to = backwardMostMove;
16334   if (to < currentMove) {
16335     BackwardInner(to);
16336   } else {
16337     ForwardInner(to);
16338   }
16339 }
16340
16341 void
16342 RevertEvent (Boolean annotate)
16343 {
16344     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16345         return;
16346     }
16347     if (gameMode != IcsExamining) {
16348         DisplayError(_("You are not examining a game"), 0);
16349         return;
16350     }
16351     if (pausing) {
16352         DisplayError(_("You can't revert while pausing"), 0);
16353         return;
16354     }
16355     SendToICS(ics_prefix);
16356     SendToICS("revert\n");
16357 }
16358
16359 void
16360 RetractMoveEvent ()
16361 {
16362     switch (gameMode) {
16363       case MachinePlaysWhite:
16364       case MachinePlaysBlack:
16365         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16366             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16367             return;
16368         }
16369         if (forwardMostMove < 2) return;
16370         currentMove = forwardMostMove = forwardMostMove - 2;
16371         whiteTimeRemaining = timeRemaining[0][currentMove];
16372         blackTimeRemaining = timeRemaining[1][currentMove];
16373         DisplayBothClocks();
16374         DisplayMove(currentMove - 1);
16375         ClearHighlights();/*!! could figure this out*/
16376         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16377         SendToProgram("remove\n", &first);
16378         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16379         break;
16380
16381       case BeginningOfGame:
16382       default:
16383         break;
16384
16385       case IcsPlayingWhite:
16386       case IcsPlayingBlack:
16387         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16388             SendToICS(ics_prefix);
16389             SendToICS("takeback 2\n");
16390         } else {
16391             SendToICS(ics_prefix);
16392             SendToICS("takeback 1\n");
16393         }
16394         break;
16395     }
16396 }
16397
16398 void
16399 MoveNowEvent ()
16400 {
16401     ChessProgramState *cps;
16402
16403     switch (gameMode) {
16404       case MachinePlaysWhite:
16405         if (!WhiteOnMove(forwardMostMove)) {
16406             DisplayError(_("It is your turn"), 0);
16407             return;
16408         }
16409         cps = &first;
16410         break;
16411       case MachinePlaysBlack:
16412         if (WhiteOnMove(forwardMostMove)) {
16413             DisplayError(_("It is your turn"), 0);
16414             return;
16415         }
16416         cps = &first;
16417         break;
16418       case TwoMachinesPlay:
16419         if (WhiteOnMove(forwardMostMove) ==
16420             (first.twoMachinesColor[0] == 'w')) {
16421             cps = &first;
16422         } else {
16423             cps = &second;
16424         }
16425         break;
16426       case BeginningOfGame:
16427       default:
16428         return;
16429     }
16430     SendToProgram("?\n", cps);
16431 }
16432
16433 void
16434 TruncateGameEvent ()
16435 {
16436     EditGameEvent();
16437     if (gameMode != EditGame) return;
16438     TruncateGame();
16439 }
16440
16441 void
16442 TruncateGame ()
16443 {
16444     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16445     if (forwardMostMove > currentMove) {
16446         if (gameInfo.resultDetails != NULL) {
16447             free(gameInfo.resultDetails);
16448             gameInfo.resultDetails = NULL;
16449             gameInfo.result = GameUnfinished;
16450         }
16451         forwardMostMove = currentMove;
16452         HistorySet(parseList, backwardMostMove, forwardMostMove,
16453                    currentMove-1);
16454     }
16455 }
16456
16457 void
16458 HintEvent ()
16459 {
16460     if (appData.noChessProgram) return;
16461     switch (gameMode) {
16462       case MachinePlaysWhite:
16463         if (WhiteOnMove(forwardMostMove)) {
16464             DisplayError(_("Wait until your turn."), 0);
16465             return;
16466         }
16467         break;
16468       case BeginningOfGame:
16469       case MachinePlaysBlack:
16470         if (!WhiteOnMove(forwardMostMove)) {
16471             DisplayError(_("Wait until your turn."), 0);
16472             return;
16473         }
16474         break;
16475       default:
16476         DisplayError(_("No hint available"), 0);
16477         return;
16478     }
16479     SendToProgram("hint\n", &first);
16480     hintRequested = TRUE;
16481 }
16482
16483 int
16484 SaveSelected (FILE *g, int dummy, char *dummy2)
16485 {
16486     ListGame * lg = (ListGame *) gameList.head;
16487     int nItem, cnt=0;
16488     FILE *f;
16489
16490     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16491         DisplayError(_("Game list not loaded or empty"), 0);
16492         return 0;
16493     }
16494
16495     creatingBook = TRUE; // suppresses stuff during load game
16496
16497     /* Get list size */
16498     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16499         if(lg->position >= 0) { // selected?
16500             LoadGame(f, nItem, "", TRUE);
16501             SaveGamePGN2(g); // leaves g open
16502             cnt++; DoEvents();
16503         }
16504         lg = (ListGame *) lg->node.succ;
16505     }
16506
16507     fclose(g);
16508     creatingBook = FALSE;
16509
16510     return cnt;
16511 }
16512
16513 void
16514 CreateBookEvent ()
16515 {
16516     ListGame * lg = (ListGame *) gameList.head;
16517     FILE *f, *g;
16518     int nItem;
16519     static int secondTime = FALSE;
16520
16521     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16522         DisplayError(_("Game list not loaded or empty"), 0);
16523         return;
16524     }
16525
16526     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16527         fclose(g);
16528         secondTime++;
16529         DisplayNote(_("Book file exists! Try again for overwrite."));
16530         return;
16531     }
16532
16533     creatingBook = TRUE;
16534     secondTime = FALSE;
16535
16536     /* Get list size */
16537     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16538         if(lg->position >= 0) {
16539             LoadGame(f, nItem, "", TRUE);
16540             AddGameToBook(TRUE);
16541             DoEvents();
16542         }
16543         lg = (ListGame *) lg->node.succ;
16544     }
16545
16546     creatingBook = FALSE;
16547     FlushBook();
16548 }
16549
16550 void
16551 BookEvent ()
16552 {
16553     if (appData.noChessProgram) return;
16554     switch (gameMode) {
16555       case MachinePlaysWhite:
16556         if (WhiteOnMove(forwardMostMove)) {
16557             DisplayError(_("Wait until your turn."), 0);
16558             return;
16559         }
16560         break;
16561       case BeginningOfGame:
16562       case MachinePlaysBlack:
16563         if (!WhiteOnMove(forwardMostMove)) {
16564             DisplayError(_("Wait until your turn."), 0);
16565             return;
16566         }
16567         break;
16568       case EditPosition:
16569         EditPositionDone(TRUE);
16570         break;
16571       case TwoMachinesPlay:
16572         return;
16573       default:
16574         break;
16575     }
16576     SendToProgram("bk\n", &first);
16577     bookOutput[0] = NULLCHAR;
16578     bookRequested = TRUE;
16579 }
16580
16581 void
16582 AboutGameEvent ()
16583 {
16584     char *tags = PGNTags(&gameInfo);
16585     TagsPopUp(tags, CmailMsg());
16586     free(tags);
16587 }
16588
16589 /* end button procedures */
16590
16591 void
16592 PrintPosition (FILE *fp, int move)
16593 {
16594     int i, j;
16595
16596     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16597         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16598             char c = PieceToChar(boards[move][i][j]);
16599             fputc(c == '?' ? '.' : c, fp);
16600             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16601         }
16602     }
16603     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16604       fprintf(fp, "white to play\n");
16605     else
16606       fprintf(fp, "black to play\n");
16607 }
16608
16609 void
16610 PrintOpponents (FILE *fp)
16611 {
16612     if (gameInfo.white != NULL) {
16613         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16614     } else {
16615         fprintf(fp, "\n");
16616     }
16617 }
16618
16619 /* Find last component of program's own name, using some heuristics */
16620 void
16621 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16622 {
16623     char *p, *q, c;
16624     int local = (strcmp(host, "localhost") == 0);
16625     while (!local && (p = strchr(prog, ';')) != NULL) {
16626         p++;
16627         while (*p == ' ') p++;
16628         prog = p;
16629     }
16630     if (*prog == '"' || *prog == '\'') {
16631         q = strchr(prog + 1, *prog);
16632     } else {
16633         q = strchr(prog, ' ');
16634     }
16635     if (q == NULL) q = prog + strlen(prog);
16636     p = q;
16637     while (p >= prog && *p != '/' && *p != '\\') p--;
16638     p++;
16639     if(p == prog && *p == '"') p++;
16640     c = *q; *q = 0;
16641     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16642     memcpy(buf, p, q - p);
16643     buf[q - p] = NULLCHAR;
16644     if (!local) {
16645         strcat(buf, "@");
16646         strcat(buf, host);
16647     }
16648 }
16649
16650 char *
16651 TimeControlTagValue ()
16652 {
16653     char buf[MSG_SIZ];
16654     if (!appData.clockMode) {
16655       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16656     } else if (movesPerSession > 0) {
16657       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16658     } else if (timeIncrement == 0) {
16659       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16660     } else {
16661       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16662     }
16663     return StrSave(buf);
16664 }
16665
16666 void
16667 SetGameInfo ()
16668 {
16669     /* This routine is used only for certain modes */
16670     VariantClass v = gameInfo.variant;
16671     ChessMove r = GameUnfinished;
16672     char *p = NULL;
16673
16674     if(keepInfo) return;
16675
16676     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16677         r = gameInfo.result;
16678         p = gameInfo.resultDetails;
16679         gameInfo.resultDetails = NULL;
16680     }
16681     ClearGameInfo(&gameInfo);
16682     gameInfo.variant = v;
16683
16684     switch (gameMode) {
16685       case MachinePlaysWhite:
16686         gameInfo.event = StrSave( appData.pgnEventHeader );
16687         gameInfo.site = StrSave(HostName());
16688         gameInfo.date = PGNDate();
16689         gameInfo.round = StrSave("-");
16690         gameInfo.white = StrSave(first.tidy);
16691         gameInfo.black = StrSave(UserName());
16692         gameInfo.timeControl = TimeControlTagValue();
16693         break;
16694
16695       case MachinePlaysBlack:
16696         gameInfo.event = StrSave( appData.pgnEventHeader );
16697         gameInfo.site = StrSave(HostName());
16698         gameInfo.date = PGNDate();
16699         gameInfo.round = StrSave("-");
16700         gameInfo.white = StrSave(UserName());
16701         gameInfo.black = StrSave(first.tidy);
16702         gameInfo.timeControl = TimeControlTagValue();
16703         break;
16704
16705       case TwoMachinesPlay:
16706         gameInfo.event = StrSave( appData.pgnEventHeader );
16707         gameInfo.site = StrSave(HostName());
16708         gameInfo.date = PGNDate();
16709         if (roundNr > 0) {
16710             char buf[MSG_SIZ];
16711             snprintf(buf, MSG_SIZ, "%d", roundNr);
16712             gameInfo.round = StrSave(buf);
16713         } else {
16714             gameInfo.round = StrSave("-");
16715         }
16716         if (first.twoMachinesColor[0] == 'w') {
16717             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16718             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16719         } else {
16720             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16721             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16722         }
16723         gameInfo.timeControl = TimeControlTagValue();
16724         break;
16725
16726       case EditGame:
16727         gameInfo.event = StrSave("Edited game");
16728         gameInfo.site = StrSave(HostName());
16729         gameInfo.date = PGNDate();
16730         gameInfo.round = StrSave("-");
16731         gameInfo.white = StrSave("-");
16732         gameInfo.black = StrSave("-");
16733         gameInfo.result = r;
16734         gameInfo.resultDetails = p;
16735         break;
16736
16737       case EditPosition:
16738         gameInfo.event = StrSave("Edited position");
16739         gameInfo.site = StrSave(HostName());
16740         gameInfo.date = PGNDate();
16741         gameInfo.round = StrSave("-");
16742         gameInfo.white = StrSave("-");
16743         gameInfo.black = StrSave("-");
16744         break;
16745
16746       case IcsPlayingWhite:
16747       case IcsPlayingBlack:
16748       case IcsObserving:
16749       case IcsExamining:
16750         break;
16751
16752       case PlayFromGameFile:
16753         gameInfo.event = StrSave("Game from non-PGN file");
16754         gameInfo.site = StrSave(HostName());
16755         gameInfo.date = PGNDate();
16756         gameInfo.round = StrSave("-");
16757         gameInfo.white = StrSave("?");
16758         gameInfo.black = StrSave("?");
16759         break;
16760
16761       default:
16762         break;
16763     }
16764 }
16765
16766 void
16767 ReplaceComment (int index, char *text)
16768 {
16769     int len;
16770     char *p;
16771     float score;
16772
16773     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16774        pvInfoList[index-1].depth == len &&
16775        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16776        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16777     while (*text == '\n') text++;
16778     len = strlen(text);
16779     while (len > 0 && text[len - 1] == '\n') len--;
16780
16781     if (commentList[index] != NULL)
16782       free(commentList[index]);
16783
16784     if (len == 0) {
16785         commentList[index] = NULL;
16786         return;
16787     }
16788   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16789       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16790       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16791     commentList[index] = (char *) malloc(len + 2);
16792     strncpy(commentList[index], text, len);
16793     commentList[index][len] = '\n';
16794     commentList[index][len + 1] = NULLCHAR;
16795   } else {
16796     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16797     char *p;
16798     commentList[index] = (char *) malloc(len + 7);
16799     safeStrCpy(commentList[index], "{\n", 3);
16800     safeStrCpy(commentList[index]+2, text, len+1);
16801     commentList[index][len+2] = NULLCHAR;
16802     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16803     strcat(commentList[index], "\n}\n");
16804   }
16805 }
16806
16807 void
16808 CrushCRs (char *text)
16809 {
16810   char *p = text;
16811   char *q = text;
16812   char ch;
16813
16814   do {
16815     ch = *p++;
16816     if (ch == '\r') continue;
16817     *q++ = ch;
16818   } while (ch != '\0');
16819 }
16820
16821 void
16822 AppendComment (int index, char *text, Boolean addBraces)
16823 /* addBraces  tells if we should add {} */
16824 {
16825     int oldlen, len;
16826     char *old;
16827
16828 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16829     if(addBraces == 3) addBraces = 0; else // force appending literally
16830     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16831
16832     CrushCRs(text);
16833     while (*text == '\n') text++;
16834     len = strlen(text);
16835     while (len > 0 && text[len - 1] == '\n') len--;
16836     text[len] = NULLCHAR;
16837
16838     if (len == 0) return;
16839
16840     if (commentList[index] != NULL) {
16841       Boolean addClosingBrace = addBraces;
16842         old = commentList[index];
16843         oldlen = strlen(old);
16844         while(commentList[index][oldlen-1] ==  '\n')
16845           commentList[index][--oldlen] = NULLCHAR;
16846         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16847         safeStrCpy(commentList[index], old, oldlen + len + 6);
16848         free(old);
16849         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16850         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16851           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16852           while (*text == '\n') { text++; len--; }
16853           commentList[index][--oldlen] = NULLCHAR;
16854       }
16855         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16856         else          strcat(commentList[index], "\n");
16857         strcat(commentList[index], text);
16858         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16859         else          strcat(commentList[index], "\n");
16860     } else {
16861         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16862         if(addBraces)
16863           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16864         else commentList[index][0] = NULLCHAR;
16865         strcat(commentList[index], text);
16866         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16867         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16868     }
16869 }
16870
16871 static char *
16872 FindStr (char * text, char * sub_text)
16873 {
16874     char * result = strstr( text, sub_text );
16875
16876     if( result != NULL ) {
16877         result += strlen( sub_text );
16878     }
16879
16880     return result;
16881 }
16882
16883 /* [AS] Try to extract PV info from PGN comment */
16884 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16885 char *
16886 GetInfoFromComment (int index, char * text)
16887 {
16888     char * sep = text, *p;
16889
16890     if( text != NULL && index > 0 ) {
16891         int score = 0;
16892         int depth = 0;
16893         int time = -1, sec = 0, deci;
16894         char * s_eval = FindStr( text, "[%eval " );
16895         char * s_emt = FindStr( text, "[%emt " );
16896 #if 0
16897         if( s_eval != NULL || s_emt != NULL ) {
16898 #else
16899         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16900 #endif
16901             /* New style */
16902             char delim;
16903
16904             if( s_eval != NULL ) {
16905                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16906                     return text;
16907                 }
16908
16909                 if( delim != ']' ) {
16910                     return text;
16911                 }
16912             }
16913
16914             if( s_emt != NULL ) {
16915             }
16916                 return text;
16917         }
16918         else {
16919             /* We expect something like: [+|-]nnn.nn/dd */
16920             int score_lo = 0;
16921
16922             if(*text != '{') return text; // [HGM] braces: must be normal comment
16923
16924             sep = strchr( text, '/' );
16925             if( sep == NULL || sep < (text+4) ) {
16926                 return text;
16927             }
16928
16929             p = text;
16930             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16931             if(p[1] == '(') { // comment starts with PV
16932                p = strchr(p, ')'); // locate end of PV
16933                if(p == NULL || sep < p+5) return text;
16934                // at this point we have something like "{(.*) +0.23/6 ..."
16935                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16936                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16937                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16938             }
16939             time = -1; sec = -1; deci = -1;
16940             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16941                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16942                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16943                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16944                 return text;
16945             }
16946
16947             if( score_lo < 0 || score_lo >= 100 ) {
16948                 return text;
16949             }
16950
16951             if(sec >= 0) time = 600*time + 10*sec; else
16952             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16953
16954             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16955
16956             /* [HGM] PV time: now locate end of PV info */
16957             while( *++sep >= '0' && *sep <= '9'); // strip depth
16958             if(time >= 0)
16959             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16960             if(sec >= 0)
16961             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16962             if(deci >= 0)
16963             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16964             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16965         }
16966
16967         if( depth <= 0 ) {
16968             return text;
16969         }
16970
16971         if( time < 0 ) {
16972             time = -1;
16973         }
16974
16975         pvInfoList[index-1].depth = depth;
16976         pvInfoList[index-1].score = score;
16977         pvInfoList[index-1].time  = 10*time; // centi-sec
16978         if(*sep == '}') *sep = 0; else *--sep = '{';
16979         if(p != text) {
16980             while(*p++ = *sep++)
16981                                 ;
16982             sep = text;
16983         } // squeeze out space between PV and comment, and return both
16984     }
16985     return sep;
16986 }
16987
16988 void
16989 SendToProgram (char *message, ChessProgramState *cps)
16990 {
16991     int count, outCount, error;
16992     char buf[MSG_SIZ];
16993
16994     if (cps->pr == NoProc) return;
16995     Attention(cps);
16996
16997     if (appData.debugMode) {
16998         TimeMark now;
16999         GetTimeMark(&now);
17000         fprintf(debugFP, "%ld >%-6s: %s",
17001                 SubtractTimeMarks(&now, &programStartTime),
17002                 cps->which, message);
17003         if(serverFP)
17004             fprintf(serverFP, "%ld >%-6s: %s",
17005                 SubtractTimeMarks(&now, &programStartTime),
17006                 cps->which, message), fflush(serverFP);
17007     }
17008
17009     count = strlen(message);
17010     outCount = OutputToProcess(cps->pr, message, count, &error);
17011     if (outCount < count && !exiting
17012                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
17013       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
17014       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
17015         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17016             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17017                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17018                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17019                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17020             } else {
17021                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17022                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17023                 gameInfo.result = res;
17024             }
17025             gameInfo.resultDetails = StrSave(buf);
17026         }
17027         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17028         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17029     }
17030 }
17031
17032 void
17033 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
17034 {
17035     char *end_str;
17036     char buf[MSG_SIZ];
17037     ChessProgramState *cps = (ChessProgramState *)closure;
17038
17039     if (isr != cps->isr) return; /* Killed intentionally */
17040     if (count <= 0) {
17041         if (count == 0) {
17042             RemoveInputSource(cps->isr);
17043             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
17044                     _(cps->which), cps->program);
17045             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
17046             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17047                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17048                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17049                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17050                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17051                 } else {
17052                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17053                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17054                     gameInfo.result = res;
17055                 }
17056                 gameInfo.resultDetails = StrSave(buf);
17057             }
17058             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17059             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
17060         } else {
17061             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
17062                     _(cps->which), cps->program);
17063             RemoveInputSource(cps->isr);
17064
17065             /* [AS] Program is misbehaving badly... kill it */
17066             if( count == -2 ) {
17067                 DestroyChildProcess( cps->pr, 9 );
17068                 cps->pr = NoProc;
17069             }
17070
17071             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17072         }
17073         return;
17074     }
17075
17076     if ((end_str = strchr(message, '\r')) != NULL)
17077       *end_str = NULLCHAR;
17078     if ((end_str = strchr(message, '\n')) != NULL)
17079       *end_str = NULLCHAR;
17080
17081     if (appData.debugMode) {
17082         TimeMark now; int print = 1;
17083         char *quote = ""; char c; int i;
17084
17085         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17086                 char start = message[0];
17087                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17088                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17089                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17090                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17091                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17092                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17093                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17094                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17095                    sscanf(message, "hint: %c", &c)!=1 &&
17096                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17097                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17098                     print = (appData.engineComments >= 2);
17099                 }
17100                 message[0] = start; // restore original message
17101         }
17102         if(print) {
17103                 GetTimeMark(&now);
17104                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17105                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17106                         quote,
17107                         message);
17108                 if(serverFP)
17109                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17110                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17111                         quote,
17112                         message), fflush(serverFP);
17113         }
17114     }
17115
17116     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17117     if (appData.icsEngineAnalyze) {
17118         if (strstr(message, "whisper") != NULL ||
17119              strstr(message, "kibitz") != NULL ||
17120             strstr(message, "tellics") != NULL) return;
17121     }
17122
17123     HandleMachineMove(message, cps);
17124 }
17125
17126
17127 void
17128 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17129 {
17130     char buf[MSG_SIZ];
17131     int seconds;
17132
17133     if( timeControl_2 > 0 ) {
17134         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17135             tc = timeControl_2;
17136         }
17137     }
17138     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17139     inc /= cps->timeOdds;
17140     st  /= cps->timeOdds;
17141
17142     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17143
17144     if (st > 0) {
17145       /* Set exact time per move, normally using st command */
17146       if (cps->stKludge) {
17147         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17148         seconds = st % 60;
17149         if (seconds == 0) {
17150           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17151         } else {
17152           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17153         }
17154       } else {
17155         snprintf(buf, MSG_SIZ, "st %d\n", st);
17156       }
17157     } else {
17158       /* Set conventional or incremental time control, using level command */
17159       if (seconds == 0) {
17160         /* Note old gnuchess bug -- minutes:seconds used to not work.
17161            Fixed in later versions, but still avoid :seconds
17162            when seconds is 0. */
17163         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17164       } else {
17165         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17166                  seconds, inc/1000.);
17167       }
17168     }
17169     SendToProgram(buf, cps);
17170
17171     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17172     /* Orthogonally, limit search to given depth */
17173     if (sd > 0) {
17174       if (cps->sdKludge) {
17175         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17176       } else {
17177         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17178       }
17179       SendToProgram(buf, cps);
17180     }
17181
17182     if(cps->nps >= 0) { /* [HGM] nps */
17183         if(cps->supportsNPS == FALSE)
17184           cps->nps = -1; // don't use if engine explicitly says not supported!
17185         else {
17186           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17187           SendToProgram(buf, cps);
17188         }
17189     }
17190 }
17191
17192 ChessProgramState *
17193 WhitePlayer ()
17194 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17195 {
17196     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17197        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17198         return &second;
17199     return &first;
17200 }
17201
17202 void
17203 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17204 {
17205     char message[MSG_SIZ];
17206     long time, otime;
17207
17208     /* Note: this routine must be called when the clocks are stopped
17209        or when they have *just* been set or switched; otherwise
17210        it will be off by the time since the current tick started.
17211     */
17212     if (machineWhite) {
17213         time = whiteTimeRemaining / 10;
17214         otime = blackTimeRemaining / 10;
17215     } else {
17216         time = blackTimeRemaining / 10;
17217         otime = whiteTimeRemaining / 10;
17218     }
17219     /* [HGM] translate opponent's time by time-odds factor */
17220     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17221
17222     if (time <= 0) time = 1;
17223     if (otime <= 0) otime = 1;
17224
17225     snprintf(message, MSG_SIZ, "time %ld\n", time);
17226     SendToProgram(message, cps);
17227
17228     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17229     SendToProgram(message, cps);
17230 }
17231
17232 char *
17233 EngineDefinedVariant (ChessProgramState *cps, int n)
17234 {   // return name of n-th unknown variant that engine supports
17235     static char buf[MSG_SIZ];
17236     char *p, *s = cps->variants;
17237     if(!s) return NULL;
17238     do { // parse string from variants feature
17239       VariantClass v;
17240         p = strchr(s, ',');
17241         if(p) *p = NULLCHAR;
17242       v = StringToVariant(s);
17243       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17244         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17245             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17246                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17247                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17248                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17249             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17250         }
17251         if(p) *p++ = ',';
17252         if(n < 0) return buf;
17253     } while(s = p);
17254     return NULL;
17255 }
17256
17257 int
17258 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17259 {
17260   char buf[MSG_SIZ];
17261   int len = strlen(name);
17262   int val;
17263
17264   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17265     (*p) += len + 1;
17266     sscanf(*p, "%d", &val);
17267     *loc = (val != 0);
17268     while (**p && **p != ' ')
17269       (*p)++;
17270     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17271     SendToProgram(buf, cps);
17272     return TRUE;
17273   }
17274   return FALSE;
17275 }
17276
17277 int
17278 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17279 {
17280   char buf[MSG_SIZ];
17281   int len = strlen(name);
17282   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17283     (*p) += len + 1;
17284     sscanf(*p, "%d", loc);
17285     while (**p && **p != ' ') (*p)++;
17286     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17287     SendToProgram(buf, cps);
17288     return TRUE;
17289   }
17290   return FALSE;
17291 }
17292
17293 int
17294 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17295 {
17296   char buf[MSG_SIZ];
17297   int len = strlen(name);
17298   if (strncmp((*p), name, len) == 0
17299       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17300     (*p) += len + 2;
17301     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
17302     FREE(*loc); *loc = malloc(len);
17303     strncpy(*loc, *p, len);
17304     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17305     while (**p && **p != '\"') (*p)++;
17306     if (**p == '\"') (*p)++;
17307     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17308     SendToProgram(buf, cps);
17309     return TRUE;
17310   }
17311   return FALSE;
17312 }
17313
17314 int
17315 ParseOption (Option *opt, ChessProgramState *cps)
17316 // [HGM] options: process the string that defines an engine option, and determine
17317 // name, type, default value, and allowed value range
17318 {
17319         char *p, *q, buf[MSG_SIZ];
17320         int n, min = (-1)<<31, max = 1<<31, def;
17321
17322         opt->target = &opt->value;   // OK for spin/slider and checkbox
17323         if(p = strstr(opt->name, " -spin ")) {
17324             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17325             if(max < min) max = min; // enforce consistency
17326             if(def < min) def = min;
17327             if(def > max) def = max;
17328             opt->value = def;
17329             opt->min = min;
17330             opt->max = max;
17331             opt->type = Spin;
17332         } else if((p = strstr(opt->name, " -slider "))) {
17333             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17334             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17335             if(max < min) max = min; // enforce consistency
17336             if(def < min) def = min;
17337             if(def > max) def = max;
17338             opt->value = def;
17339             opt->min = min;
17340             opt->max = max;
17341             opt->type = Spin; // Slider;
17342         } else if((p = strstr(opt->name, " -string "))) {
17343             opt->textValue = p+9;
17344             opt->type = TextBox;
17345             opt->target = &opt->textValue;
17346         } else if((p = strstr(opt->name, " -file "))) {
17347             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17348             opt->target = opt->textValue = p+7;
17349             opt->type = FileName; // FileName;
17350             opt->target = &opt->textValue;
17351         } else if((p = strstr(opt->name, " -path "))) {
17352             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17353             opt->target = opt->textValue = p+7;
17354             opt->type = PathName; // PathName;
17355             opt->target = &opt->textValue;
17356         } else if(p = strstr(opt->name, " -check ")) {
17357             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17358             opt->value = (def != 0);
17359             opt->type = CheckBox;
17360         } else if(p = strstr(opt->name, " -combo ")) {
17361             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17362             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17363             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17364             opt->value = n = 0;
17365             while(q = StrStr(q, " /// ")) {
17366                 n++; *q = 0;    // count choices, and null-terminate each of them
17367                 q += 5;
17368                 if(*q == '*') { // remember default, which is marked with * prefix
17369                     q++;
17370                     opt->value = n;
17371                 }
17372                 cps->comboList[cps->comboCnt++] = q;
17373             }
17374             cps->comboList[cps->comboCnt++] = NULL;
17375             opt->max = n + 1;
17376             opt->type = ComboBox;
17377         } else if(p = strstr(opt->name, " -button")) {
17378             opt->type = Button;
17379         } else if(p = strstr(opt->name, " -save")) {
17380             opt->type = SaveButton;
17381         } else return FALSE;
17382         *p = 0; // terminate option name
17383         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17384         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17385         // now look if the command-line options define a setting for this engine option.
17386         if(cps->optionSettings && cps->optionSettings[0])
17387             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17388         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17389           snprintf(buf, MSG_SIZ, "option %s", p);
17390                 if(p = strstr(buf, ",")) *p = 0;
17391                 if(q = strchr(buf, '=')) switch(opt->type) {
17392                     case ComboBox:
17393                         for(n=0; n<opt->max; n++)
17394                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17395                         break;
17396                     case TextBox:
17397                     case FileName:
17398                     case PathName:
17399                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17400                         break;
17401                     case Spin:
17402                     case CheckBox:
17403                         opt->value = atoi(q+1);
17404                     default:
17405                         break;
17406                 }
17407                 strcat(buf, "\n");
17408                 SendToProgram(buf, cps);
17409         }
17410         return TRUE;
17411 }
17412
17413 void
17414 FeatureDone (ChessProgramState *cps, int val)
17415 {
17416   DelayedEventCallback cb = GetDelayedEvent();
17417   if ((cb == InitBackEnd3 && cps == &first) ||
17418       (cb == SettingsMenuIfReady && cps == &second) ||
17419       (cb == LoadEngine) || (cb == StartSecond) ||
17420       (cb == TwoMachinesEventIfReady)) {
17421     CancelDelayedEvent();
17422     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17423   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17424   cps->initDone = val;
17425   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17426 }
17427
17428 /* Parse feature command from engine */
17429 void
17430 ParseFeatures (char *args, ChessProgramState *cps)
17431 {
17432   char *p = args;
17433   char *q = NULL;
17434   int val;
17435   char buf[MSG_SIZ];
17436
17437   for (;;) {
17438     while (*p == ' ') p++;
17439     if (*p == NULLCHAR) return;
17440
17441     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17442     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17443     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17444     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17445     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17446     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17447     if (BoolFeature(&p, "reuse", &val, cps)) {
17448       /* Engine can disable reuse, but can't enable it if user said no */
17449       if (!val) cps->reuse = FALSE;
17450       continue;
17451     }
17452     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17453     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17454       if (gameMode == TwoMachinesPlay) {
17455         DisplayTwoMachinesTitle();
17456       } else {
17457         DisplayTitle("");
17458       }
17459       continue;
17460     }
17461     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17462     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17463     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17464     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17465     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17466     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17467     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17468     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17469     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17470     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17471     if (IntFeature(&p, "done", &val, cps)) {
17472       FeatureDone(cps, val);
17473       continue;
17474     }
17475     /* Added by Tord: */
17476     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17477     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17478     /* End of additions by Tord */
17479
17480     /* [HGM] added features: */
17481     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17482     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17483     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17484     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17485     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17486     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17487     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17488     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17489         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17490         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17491         FREE(cps->option[cps->nrOptions].name);
17492         cps->option[cps->nrOptions].name = q; q = NULL;
17493         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17494           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17495             SendToProgram(buf, cps);
17496             continue;
17497         }
17498         if(cps->nrOptions >= MAX_OPTIONS) {
17499             cps->nrOptions--;
17500             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17501             DisplayError(buf, 0);
17502         }
17503         continue;
17504     }
17505     /* End of additions by HGM */
17506
17507     /* unknown feature: complain and skip */
17508     q = p;
17509     while (*q && *q != '=') q++;
17510     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17511     SendToProgram(buf, cps);
17512     p = q;
17513     if (*p == '=') {
17514       p++;
17515       if (*p == '\"') {
17516         p++;
17517         while (*p && *p != '\"') p++;
17518         if (*p == '\"') p++;
17519       } else {
17520         while (*p && *p != ' ') p++;
17521       }
17522     }
17523   }
17524
17525 }
17526
17527 void
17528 PeriodicUpdatesEvent (int newState)
17529 {
17530     if (newState == appData.periodicUpdates)
17531       return;
17532
17533     appData.periodicUpdates=newState;
17534
17535     /* Display type changes, so update it now */
17536 //    DisplayAnalysis();
17537
17538     /* Get the ball rolling again... */
17539     if (newState) {
17540         AnalysisPeriodicEvent(1);
17541         StartAnalysisClock();
17542     }
17543 }
17544
17545 void
17546 PonderNextMoveEvent (int newState)
17547 {
17548     if (newState == appData.ponderNextMove) return;
17549     if (gameMode == EditPosition) EditPositionDone(TRUE);
17550     if (newState) {
17551         SendToProgram("hard\n", &first);
17552         if (gameMode == TwoMachinesPlay) {
17553             SendToProgram("hard\n", &second);
17554         }
17555     } else {
17556         SendToProgram("easy\n", &first);
17557         thinkOutput[0] = NULLCHAR;
17558         if (gameMode == TwoMachinesPlay) {
17559             SendToProgram("easy\n", &second);
17560         }
17561     }
17562     appData.ponderNextMove = newState;
17563 }
17564
17565 void
17566 NewSettingEvent (int option, int *feature, char *command, int value)
17567 {
17568     char buf[MSG_SIZ];
17569
17570     if (gameMode == EditPosition) EditPositionDone(TRUE);
17571     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17572     if(feature == NULL || *feature) SendToProgram(buf, &first);
17573     if (gameMode == TwoMachinesPlay) {
17574         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17575     }
17576 }
17577
17578 void
17579 ShowThinkingEvent ()
17580 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17581 {
17582     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17583     int newState = appData.showThinking
17584         // [HGM] thinking: other features now need thinking output as well
17585         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17586
17587     if (oldState == newState) return;
17588     oldState = newState;
17589     if (gameMode == EditPosition) EditPositionDone(TRUE);
17590     if (oldState) {
17591         SendToProgram("post\n", &first);
17592         if (gameMode == TwoMachinesPlay) {
17593             SendToProgram("post\n", &second);
17594         }
17595     } else {
17596         SendToProgram("nopost\n", &first);
17597         thinkOutput[0] = NULLCHAR;
17598         if (gameMode == TwoMachinesPlay) {
17599             SendToProgram("nopost\n", &second);
17600         }
17601     }
17602 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17603 }
17604
17605 void
17606 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17607 {
17608   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17609   if (pr == NoProc) return;
17610   AskQuestion(title, question, replyPrefix, pr);
17611 }
17612
17613 void
17614 TypeInEvent (char firstChar)
17615 {
17616     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17617         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17618         gameMode == AnalyzeMode || gameMode == EditGame ||
17619         gameMode == EditPosition || gameMode == IcsExamining ||
17620         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17621         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17622                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17623                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17624         gameMode == Training) PopUpMoveDialog(firstChar);
17625 }
17626
17627 void
17628 TypeInDoneEvent (char *move)
17629 {
17630         Board board;
17631         int n, fromX, fromY, toX, toY;
17632         char promoChar;
17633         ChessMove moveType;
17634
17635         // [HGM] FENedit
17636         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17637                 EditPositionPasteFEN(move);
17638                 return;
17639         }
17640         // [HGM] movenum: allow move number to be typed in any mode
17641         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17642           ToNrEvent(2*n-1);
17643           return;
17644         }
17645         // undocumented kludge: allow command-line option to be typed in!
17646         // (potentially fatal, and does not implement the effect of the option.)
17647         // should only be used for options that are values on which future decisions will be made,
17648         // and definitely not on options that would be used during initialization.
17649         if(strstr(move, "!!! -") == move) {
17650             ParseArgsFromString(move+4);
17651             return;
17652         }
17653
17654       if (gameMode != EditGame && currentMove != forwardMostMove &&
17655         gameMode != Training) {
17656         DisplayMoveError(_("Displayed move is not current"));
17657       } else {
17658         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17659           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17660         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17661         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17662           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17663           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17664         } else {
17665           DisplayMoveError(_("Could not parse move"));
17666         }
17667       }
17668 }
17669
17670 void
17671 DisplayMove (int moveNumber)
17672 {
17673     char message[MSG_SIZ];
17674     char res[MSG_SIZ];
17675     char cpThinkOutput[MSG_SIZ];
17676
17677     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17678
17679     if (moveNumber == forwardMostMove - 1 ||
17680         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17681
17682         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17683
17684         if (strchr(cpThinkOutput, '\n')) {
17685             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17686         }
17687     } else {
17688         *cpThinkOutput = NULLCHAR;
17689     }
17690
17691     /* [AS] Hide thinking from human user */
17692     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17693         *cpThinkOutput = NULLCHAR;
17694         if( thinkOutput[0] != NULLCHAR ) {
17695             int i;
17696
17697             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17698                 cpThinkOutput[i] = '.';
17699             }
17700             cpThinkOutput[i] = NULLCHAR;
17701             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17702         }
17703     }
17704
17705     if (moveNumber == forwardMostMove - 1 &&
17706         gameInfo.resultDetails != NULL) {
17707         if (gameInfo.resultDetails[0] == NULLCHAR) {
17708           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17709         } else {
17710           snprintf(res, MSG_SIZ, " {%s} %s",
17711                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17712         }
17713     } else {
17714         res[0] = NULLCHAR;
17715     }
17716
17717     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17718         DisplayMessage(res, cpThinkOutput);
17719     } else {
17720       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17721                 WhiteOnMove(moveNumber) ? " " : ".. ",
17722                 parseList[moveNumber], res);
17723         DisplayMessage(message, cpThinkOutput);
17724     }
17725 }
17726
17727 void
17728 DisplayComment (int moveNumber, char *text)
17729 {
17730     char title[MSG_SIZ];
17731
17732     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17733       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17734     } else {
17735       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17736               WhiteOnMove(moveNumber) ? " " : ".. ",
17737               parseList[moveNumber]);
17738     }
17739     if (text != NULL && (appData.autoDisplayComment || commentUp))
17740         CommentPopUp(title, text);
17741 }
17742
17743 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17744  * might be busy thinking or pondering.  It can be omitted if your
17745  * gnuchess is configured to stop thinking immediately on any user
17746  * input.  However, that gnuchess feature depends on the FIONREAD
17747  * ioctl, which does not work properly on some flavors of Unix.
17748  */
17749 void
17750 Attention (ChessProgramState *cps)
17751 {
17752 #if ATTENTION
17753     if (!cps->useSigint) return;
17754     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17755     switch (gameMode) {
17756       case MachinePlaysWhite:
17757       case MachinePlaysBlack:
17758       case TwoMachinesPlay:
17759       case IcsPlayingWhite:
17760       case IcsPlayingBlack:
17761       case AnalyzeMode:
17762       case AnalyzeFile:
17763         /* Skip if we know it isn't thinking */
17764         if (!cps->maybeThinking) return;
17765         if (appData.debugMode)
17766           fprintf(debugFP, "Interrupting %s\n", cps->which);
17767         InterruptChildProcess(cps->pr);
17768         cps->maybeThinking = FALSE;
17769         break;
17770       default:
17771         break;
17772     }
17773 #endif /*ATTENTION*/
17774 }
17775
17776 int
17777 CheckFlags ()
17778 {
17779     if (whiteTimeRemaining <= 0) {
17780         if (!whiteFlag) {
17781             whiteFlag = TRUE;
17782             if (appData.icsActive) {
17783                 if (appData.autoCallFlag &&
17784                     gameMode == IcsPlayingBlack && !blackFlag) {
17785                   SendToICS(ics_prefix);
17786                   SendToICS("flag\n");
17787                 }
17788             } else {
17789                 if (blackFlag) {
17790                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17791                 } else {
17792                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17793                     if (appData.autoCallFlag) {
17794                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17795                         return TRUE;
17796                     }
17797                 }
17798             }
17799         }
17800     }
17801     if (blackTimeRemaining <= 0) {
17802         if (!blackFlag) {
17803             blackFlag = TRUE;
17804             if (appData.icsActive) {
17805                 if (appData.autoCallFlag &&
17806                     gameMode == IcsPlayingWhite && !whiteFlag) {
17807                   SendToICS(ics_prefix);
17808                   SendToICS("flag\n");
17809                 }
17810             } else {
17811                 if (whiteFlag) {
17812                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17813                 } else {
17814                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17815                     if (appData.autoCallFlag) {
17816                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17817                         return TRUE;
17818                     }
17819                 }
17820             }
17821         }
17822     }
17823     return FALSE;
17824 }
17825
17826 void
17827 CheckTimeControl ()
17828 {
17829     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17830         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17831
17832     /*
17833      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17834      */
17835     if ( !WhiteOnMove(forwardMostMove) ) {
17836         /* White made time control */
17837         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17838         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17839         /* [HGM] time odds: correct new time quota for time odds! */
17840                                             / WhitePlayer()->timeOdds;
17841         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17842     } else {
17843         lastBlack -= blackTimeRemaining;
17844         /* Black made time control */
17845         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17846                                             / WhitePlayer()->other->timeOdds;
17847         lastWhite = whiteTimeRemaining;
17848     }
17849 }
17850
17851 void
17852 DisplayBothClocks ()
17853 {
17854     int wom = gameMode == EditPosition ?
17855       !blackPlaysFirst : WhiteOnMove(currentMove);
17856     DisplayWhiteClock(whiteTimeRemaining, wom);
17857     DisplayBlackClock(blackTimeRemaining, !wom);
17858 }
17859
17860
17861 /* Timekeeping seems to be a portability nightmare.  I think everyone
17862    has ftime(), but I'm really not sure, so I'm including some ifdefs
17863    to use other calls if you don't.  Clocks will be less accurate if
17864    you have neither ftime nor gettimeofday.
17865 */
17866
17867 /* VS 2008 requires the #include outside of the function */
17868 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17869 #include <sys/timeb.h>
17870 #endif
17871
17872 /* Get the current time as a TimeMark */
17873 void
17874 GetTimeMark (TimeMark *tm)
17875 {
17876 #if HAVE_GETTIMEOFDAY
17877
17878     struct timeval timeVal;
17879     struct timezone timeZone;
17880
17881     gettimeofday(&timeVal, &timeZone);
17882     tm->sec = (long) timeVal.tv_sec;
17883     tm->ms = (int) (timeVal.tv_usec / 1000L);
17884
17885 #else /*!HAVE_GETTIMEOFDAY*/
17886 #if HAVE_FTIME
17887
17888 // include <sys/timeb.h> / moved to just above start of function
17889     struct timeb timeB;
17890
17891     ftime(&timeB);
17892     tm->sec = (long) timeB.time;
17893     tm->ms = (int) timeB.millitm;
17894
17895 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17896     tm->sec = (long) time(NULL);
17897     tm->ms = 0;
17898 #endif
17899 #endif
17900 }
17901
17902 /* Return the difference in milliseconds between two
17903    time marks.  We assume the difference will fit in a long!
17904 */
17905 long
17906 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17907 {
17908     return 1000L*(tm2->sec - tm1->sec) +
17909            (long) (tm2->ms - tm1->ms);
17910 }
17911
17912
17913 /*
17914  * Code to manage the game clocks.
17915  *
17916  * In tournament play, black starts the clock and then white makes a move.
17917  * We give the human user a slight advantage if he is playing white---the
17918  * clocks don't run until he makes his first move, so it takes zero time.
17919  * Also, we don't account for network lag, so we could get out of sync
17920  * with GNU Chess's clock -- but then, referees are always right.
17921  */
17922
17923 static TimeMark tickStartTM;
17924 static long intendedTickLength;
17925
17926 long
17927 NextTickLength (long timeRemaining)
17928 {
17929     long nominalTickLength, nextTickLength;
17930
17931     if (timeRemaining > 0L && timeRemaining <= 10000L)
17932       nominalTickLength = 100L;
17933     else
17934       nominalTickLength = 1000L;
17935     nextTickLength = timeRemaining % nominalTickLength;
17936     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17937
17938     return nextTickLength;
17939 }
17940
17941 /* Adjust clock one minute up or down */
17942 void
17943 AdjustClock (Boolean which, int dir)
17944 {
17945     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17946     if(which) blackTimeRemaining += 60000*dir;
17947     else      whiteTimeRemaining += 60000*dir;
17948     DisplayBothClocks();
17949     adjustedClock = TRUE;
17950 }
17951
17952 /* Stop clocks and reset to a fresh time control */
17953 void
17954 ResetClocks ()
17955 {
17956     (void) StopClockTimer();
17957     if (appData.icsActive) {
17958         whiteTimeRemaining = blackTimeRemaining = 0;
17959     } else if (searchTime) {
17960         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17961         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17962     } else { /* [HGM] correct new time quote for time odds */
17963         whiteTC = blackTC = fullTimeControlString;
17964         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17965         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17966     }
17967     if (whiteFlag || blackFlag) {
17968         DisplayTitle("");
17969         whiteFlag = blackFlag = FALSE;
17970     }
17971     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17972     DisplayBothClocks();
17973     adjustedClock = FALSE;
17974 }
17975
17976 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17977
17978 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17979
17980 /* Decrement running clock by amount of time that has passed */
17981 void
17982 DecrementClocks ()
17983 {
17984     long tRemaining;
17985     long lastTickLength, fudge;
17986     TimeMark now;
17987
17988     if (!appData.clockMode) return;
17989     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17990
17991     GetTimeMark(&now);
17992
17993     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17994
17995     /* Fudge if we woke up a little too soon */
17996     fudge = intendedTickLength - lastTickLength;
17997     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17998
17999     if (WhiteOnMove(forwardMostMove)) {
18000         if(whiteNPS >= 0) lastTickLength = 0;
18001          tRemaining = whiteTimeRemaining -= lastTickLength;
18002         if( tRemaining < 0 && !appData.icsActive) {
18003             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
18004             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
18005                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
18006                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
18007             }
18008         }
18009         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
18010         DisplayWhiteClock(whiteTimeRemaining - fudge,
18011                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18012         timeSuffix = 0;
18013     } else {
18014         if(blackNPS >= 0) lastTickLength = 0;
18015          tRemaining = blackTimeRemaining -= lastTickLength;
18016         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
18017             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
18018             if(suddenDeath) {
18019                 blackStartMove = forwardMostMove;
18020                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
18021             }
18022         }
18023         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
18024         DisplayBlackClock(blackTimeRemaining - fudge,
18025                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18026         timeSuffix = 0;
18027     }
18028     if (CheckFlags()) return;
18029
18030     if(twoBoards) { // count down secondary board's clocks as well
18031         activePartnerTime -= lastTickLength;
18032         partnerUp = 1;
18033         if(activePartner == 'W')
18034             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
18035         else
18036             DisplayBlackClock(activePartnerTime, TRUE);
18037         partnerUp = 0;
18038     }
18039
18040     tickStartTM = now;
18041     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
18042     StartClockTimer(intendedTickLength);
18043
18044     /* if the time remaining has fallen below the alarm threshold, sound the
18045      * alarm. if the alarm has sounded and (due to a takeback or time control
18046      * with increment) the time remaining has increased to a level above the
18047      * threshold, reset the alarm so it can sound again.
18048      */
18049
18050     if (appData.icsActive && appData.icsAlarm) {
18051
18052         /* make sure we are dealing with the user's clock */
18053         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
18054                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
18055            )) return;
18056
18057         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
18058             alarmSounded = FALSE;
18059         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
18060             PlayAlarmSound();
18061             alarmSounded = TRUE;
18062         }
18063     }
18064 }
18065
18066
18067 /* A player has just moved, so stop the previously running
18068    clock and (if in clock mode) start the other one.
18069    We redisplay both clocks in case we're in ICS mode, because
18070    ICS gives us an update to both clocks after every move.
18071    Note that this routine is called *after* forwardMostMove
18072    is updated, so the last fractional tick must be subtracted
18073    from the color that is *not* on move now.
18074 */
18075 void
18076 SwitchClocks (int newMoveNr)
18077 {
18078     long lastTickLength;
18079     TimeMark now;
18080     int flagged = FALSE;
18081
18082     GetTimeMark(&now);
18083
18084     if (StopClockTimer() && appData.clockMode) {
18085         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18086         if (!WhiteOnMove(forwardMostMove)) {
18087             if(blackNPS >= 0) lastTickLength = 0;
18088             blackTimeRemaining -= lastTickLength;
18089            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18090 //         if(pvInfoList[forwardMostMove].time == -1)
18091                  pvInfoList[forwardMostMove].time =               // use GUI time
18092                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18093         } else {
18094            if(whiteNPS >= 0) lastTickLength = 0;
18095            whiteTimeRemaining -= lastTickLength;
18096            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18097 //         if(pvInfoList[forwardMostMove].time == -1)
18098                  pvInfoList[forwardMostMove].time =
18099                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18100         }
18101         flagged = CheckFlags();
18102     }
18103     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18104     CheckTimeControl();
18105
18106     if (flagged || !appData.clockMode) return;
18107
18108     switch (gameMode) {
18109       case MachinePlaysBlack:
18110       case MachinePlaysWhite:
18111       case BeginningOfGame:
18112         if (pausing) return;
18113         break;
18114
18115       case EditGame:
18116       case PlayFromGameFile:
18117       case IcsExamining:
18118         return;
18119
18120       default:
18121         break;
18122     }
18123
18124     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18125         if(WhiteOnMove(forwardMostMove))
18126              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18127         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18128     }
18129
18130     tickStartTM = now;
18131     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18132       whiteTimeRemaining : blackTimeRemaining);
18133     StartClockTimer(intendedTickLength);
18134 }
18135
18136
18137 /* Stop both clocks */
18138 void
18139 StopClocks ()
18140 {
18141     long lastTickLength;
18142     TimeMark now;
18143
18144     if (!StopClockTimer()) return;
18145     if (!appData.clockMode) return;
18146
18147     GetTimeMark(&now);
18148
18149     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18150     if (WhiteOnMove(forwardMostMove)) {
18151         if(whiteNPS >= 0) lastTickLength = 0;
18152         whiteTimeRemaining -= lastTickLength;
18153         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18154     } else {
18155         if(blackNPS >= 0) lastTickLength = 0;
18156         blackTimeRemaining -= lastTickLength;
18157         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18158     }
18159     CheckFlags();
18160 }
18161
18162 /* Start clock of player on move.  Time may have been reset, so
18163    if clock is already running, stop and restart it. */
18164 void
18165 StartClocks ()
18166 {
18167     (void) StopClockTimer(); /* in case it was running already */
18168     DisplayBothClocks();
18169     if (CheckFlags()) return;
18170
18171     if (!appData.clockMode) return;
18172     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18173
18174     GetTimeMark(&tickStartTM);
18175     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18176       whiteTimeRemaining : blackTimeRemaining);
18177
18178    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18179     whiteNPS = blackNPS = -1;
18180     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18181        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18182         whiteNPS = first.nps;
18183     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18184        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18185         blackNPS = first.nps;
18186     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18187         whiteNPS = second.nps;
18188     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18189         blackNPS = second.nps;
18190     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18191
18192     StartClockTimer(intendedTickLength);
18193 }
18194
18195 char *
18196 TimeString (long ms)
18197 {
18198     long second, minute, hour, day;
18199     char *sign = "";
18200     static char buf[40], moveTime[8];
18201
18202     if (ms > 0 && ms <= 9900) {
18203       /* convert milliseconds to tenths, rounding up */
18204       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18205
18206       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18207       return buf;
18208     }
18209
18210     /* convert milliseconds to seconds, rounding up */
18211     /* use floating point to avoid strangeness of integer division
18212        with negative dividends on many machines */
18213     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18214
18215     if (second < 0) {
18216         sign = "-";
18217         second = -second;
18218     }
18219
18220     day = second / (60 * 60 * 24);
18221     second = second % (60 * 60 * 24);
18222     hour = second / (60 * 60);
18223     second = second % (60 * 60);
18224     minute = second / 60;
18225     second = second % 60;
18226
18227     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18228     else *moveTime = NULLCHAR;
18229
18230     if (day > 0)
18231       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18232               sign, day, hour, minute, second, moveTime);
18233     else if (hour > 0)
18234       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18235     else
18236       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18237
18238     return buf;
18239 }
18240
18241
18242 /*
18243  * This is necessary because some C libraries aren't ANSI C compliant yet.
18244  */
18245 char *
18246 StrStr (char *string, char *match)
18247 {
18248     int i, length;
18249
18250     length = strlen(match);
18251
18252     for (i = strlen(string) - length; i >= 0; i--, string++)
18253       if (!strncmp(match, string, length))
18254         return string;
18255
18256     return NULL;
18257 }
18258
18259 char *
18260 StrCaseStr (char *string, char *match)
18261 {
18262     int i, j, length;
18263
18264     length = strlen(match);
18265
18266     for (i = strlen(string) - length; i >= 0; i--, string++) {
18267         for (j = 0; j < length; j++) {
18268             if (ToLower(match[j]) != ToLower(string[j]))
18269               break;
18270         }
18271         if (j == length) return string;
18272     }
18273
18274     return NULL;
18275 }
18276
18277 #ifndef _amigados
18278 int
18279 StrCaseCmp (char *s1, char *s2)
18280 {
18281     char c1, c2;
18282
18283     for (;;) {
18284         c1 = ToLower(*s1++);
18285         c2 = ToLower(*s2++);
18286         if (c1 > c2) return 1;
18287         if (c1 < c2) return -1;
18288         if (c1 == NULLCHAR) return 0;
18289     }
18290 }
18291
18292
18293 int
18294 ToLower (int c)
18295 {
18296     return isupper(c) ? tolower(c) : c;
18297 }
18298
18299
18300 int
18301 ToUpper (int c)
18302 {
18303     return islower(c) ? toupper(c) : c;
18304 }
18305 #endif /* !_amigados    */
18306
18307 char *
18308 StrSave (char *s)
18309 {
18310   char *ret;
18311
18312   if ((ret = (char *) malloc(strlen(s) + 1)))
18313     {
18314       safeStrCpy(ret, s, strlen(s)+1);
18315     }
18316   return ret;
18317 }
18318
18319 char *
18320 StrSavePtr (char *s, char **savePtr)
18321 {
18322     if (*savePtr) {
18323         free(*savePtr);
18324     }
18325     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18326       safeStrCpy(*savePtr, s, strlen(s)+1);
18327     }
18328     return(*savePtr);
18329 }
18330
18331 char *
18332 PGNDate ()
18333 {
18334     time_t clock;
18335     struct tm *tm;
18336     char buf[MSG_SIZ];
18337
18338     clock = time((time_t *)NULL);
18339     tm = localtime(&clock);
18340     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18341             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18342     return StrSave(buf);
18343 }
18344
18345
18346 char *
18347 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18348 {
18349     int i, j, fromX, fromY, toX, toY;
18350     int whiteToPlay, haveRights = nrCastlingRights;
18351     char buf[MSG_SIZ];
18352     char *p, *q;
18353     int emptycount;
18354     ChessSquare piece;
18355
18356     whiteToPlay = (gameMode == EditPosition) ?
18357       !blackPlaysFirst : (move % 2 == 0);
18358     p = buf;
18359
18360     /* Piece placement data */
18361     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18362         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18363         emptycount = 0;
18364         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18365             if (boards[move][i][j] == EmptySquare) {
18366                 emptycount++;
18367             } else { ChessSquare piece = boards[move][i][j];
18368                 if (emptycount > 0) {
18369                     if(emptycount<10) /* [HGM] can be >= 10 */
18370                         *p++ = '0' + emptycount;
18371                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18372                     emptycount = 0;
18373                 }
18374                 if(PieceToChar(piece) == '+') {
18375                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18376                     *p++ = '+';
18377                     piece = (ChessSquare)(CHUDEMOTED(piece));
18378                 }
18379                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18380                 if(*p = PieceSuffix(piece)) p++;
18381                 if(p[-1] == '~') {
18382                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18383                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18384                     *p++ = '~';
18385                 }
18386             }
18387         }
18388         if (emptycount > 0) {
18389             if(emptycount<10) /* [HGM] can be >= 10 */
18390                 *p++ = '0' + emptycount;
18391             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18392             emptycount = 0;
18393         }
18394         *p++ = '/';
18395     }
18396     *(p - 1) = ' ';
18397
18398     /* [HGM] print Crazyhouse or Shogi holdings */
18399     if( gameInfo.holdingsWidth ) {
18400         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18401         q = p;
18402         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18403             piece = boards[move][i][BOARD_WIDTH-1];
18404             if( piece != EmptySquare )
18405               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18406                   *p++ = PieceToChar(piece);
18407         }
18408         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18409             piece = boards[move][handSize-i-1][0];
18410             if( piece != EmptySquare )
18411               for(j=0; j<(int) boards[move][handSize-i-1][1]; j++)
18412                   *p++ = PieceToChar(piece);
18413         }
18414
18415         if( q == p ) *p++ = '-';
18416         *p++ = ']';
18417         *p++ = ' ';
18418     }
18419
18420     /* Active color */
18421     *p++ = whiteToPlay ? 'w' : 'b';
18422     *p++ = ' ';
18423
18424   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18425     haveRights = 0; q = p;
18426     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18427       piece = boards[move][0][i];
18428       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18429         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18430       }
18431     }
18432     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18433       piece = boards[move][BOARD_HEIGHT-1][i];
18434       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18435         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18436       }
18437     }
18438     if(p == q) *p++ = '-';
18439     *p++ = ' ';
18440   }
18441
18442   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18443     while(*p++ = *q++)
18444                       ;
18445     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18446   } else {
18447   if(haveRights) {
18448      int handW=0, handB=0;
18449      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18450         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18451         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18452      }
18453      q = p;
18454      if(appData.fischerCastling) {
18455         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18456            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18457                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18458         } else {
18459        /* [HGM] write directly from rights */
18460            if(boards[move][CASTLING][2] != NoRights &&
18461               boards[move][CASTLING][0] != NoRights   )
18462                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18463            if(boards[move][CASTLING][2] != NoRights &&
18464               boards[move][CASTLING][1] != NoRights   )
18465                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18466         }
18467         if(handB) {
18468            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18469                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18470         } else {
18471            if(boards[move][CASTLING][5] != NoRights &&
18472               boards[move][CASTLING][3] != NoRights   )
18473                 *p++ = boards[move][CASTLING][3] + AAA;
18474            if(boards[move][CASTLING][5] != NoRights &&
18475               boards[move][CASTLING][4] != NoRights   )
18476                 *p++ = boards[move][CASTLING][4] + AAA;
18477         }
18478      } else {
18479
18480         /* [HGM] write true castling rights */
18481         if( nrCastlingRights == 6 ) {
18482             int q, k=0;
18483             if(boards[move][CASTLING][0] != NoRights &&
18484                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18485             q = (boards[move][CASTLING][1] != NoRights &&
18486                  boards[move][CASTLING][2] != NoRights  );
18487             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18488                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18489                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18490                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18491             }
18492             if(q) *p++ = 'Q';
18493             k = 0;
18494             if(boards[move][CASTLING][3] != NoRights &&
18495                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18496             q = (boards[move][CASTLING][4] != NoRights &&
18497                  boards[move][CASTLING][5] != NoRights  );
18498             if(handB) {
18499                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18500                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18501                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18502             }
18503             if(q) *p++ = 'q';
18504         }
18505      }
18506      if (q == p) *p++ = '-'; /* No castling rights */
18507      *p++ = ' ';
18508   }
18509
18510   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18511      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18512      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18513     /* En passant target square */
18514     if (move > backwardMostMove) {
18515         fromX = moveList[move - 1][0] - AAA;
18516         fromY = moveList[move - 1][1] - ONE;
18517         toX = moveList[move - 1][2] - AAA;
18518         toY = moveList[move - 1][3] - ONE;
18519         if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18520             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18521             /* 2-square pawn move just happened */
18522             *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18523             *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18524             if(gameInfo.variant == VariantBerolina) {
18525                 *p++ = toX + AAA;
18526                 *p++ = toY + ONE;
18527             }
18528         } else {
18529             *p++ = '-';
18530         }
18531     } else if(move == backwardMostMove) {
18532         // [HGM] perhaps we should always do it like this, and forget the above?
18533         if((signed char)boards[move][EP_STATUS] >= 0) {
18534             *p++ = boards[move][EP_STATUS] + AAA;
18535             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18536         } else {
18537             *p++ = '-';
18538         }
18539     } else {
18540         *p++ = '-';
18541     }
18542     *p++ = ' ';
18543   }
18544   }
18545
18546     i = boards[move][CHECK_COUNT];
18547     if(i) {
18548         sprintf(p, "%d+%d ", i&255, i>>8);
18549         while(*p) p++;
18550     }
18551
18552     if(moveCounts)
18553     {   int i = 0, j=move;
18554
18555         /* [HGM] find reversible plies */
18556         if (appData.debugMode) { int k;
18557             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18558             for(k=backwardMostMove; k<=forwardMostMove; k++)
18559                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18560
18561         }
18562
18563         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18564         if( j == backwardMostMove ) i += initialRulePlies;
18565         sprintf(p, "%d ", i);
18566         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18567
18568         /* Fullmove number */
18569         sprintf(p, "%d", (move / 2) + 1);
18570     } else *--p = NULLCHAR;
18571
18572     return StrSave(buf);
18573 }
18574
18575 Boolean
18576 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18577 {
18578     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18579     char *p, c;
18580     int emptycount, virgin[BOARD_FILES];
18581     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18582
18583     p = fen;
18584
18585     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18586
18587     /* Piece placement data */
18588     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18589         j = 0;
18590         for (;;) {
18591             if (*p == '/' || *p == ' ' || *p == '[' ) {
18592                 if(j > w) w = j;
18593                 emptycount = gameInfo.boardWidth - j;
18594                 while (emptycount--)
18595                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18596                 if (*p == '/') p++;
18597                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18598                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18599                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18600                     }
18601                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18602                 }
18603                 break;
18604 #if(BOARD_FILES >= 10)*0
18605             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18606                 p++; emptycount=10;
18607                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18608                 while (emptycount--)
18609                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18610 #endif
18611             } else if (*p == '*') {
18612                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18613             } else if (isdigit(*p)) {
18614                 emptycount = *p++ - '0';
18615                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18616                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18617                 while (emptycount--)
18618                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18619             } else if (*p == '<') {
18620                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18621                 else if (i != 0 || !shuffle) return FALSE;
18622                 p++;
18623             } else if (shuffle && *p == '>') {
18624                 p++; // for now ignore closing shuffle range, and assume rank-end
18625             } else if (*p == '?') {
18626                 if (j >= gameInfo.boardWidth) return FALSE;
18627                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18628                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18629             } else if (*p == '+' || isalpha(*p)) {
18630                 char *q, *s = SUFFIXES;
18631                 if (j >= gameInfo.boardWidth) return FALSE;
18632                 if(*p=='+') {
18633                     char c = *++p;
18634                     if(q = strchr(s, p[1])) p++;
18635                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18636                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18637                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18638                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18639                 } else {
18640                     char c = *p++;
18641                     if(q = strchr(s, *p)) p++;
18642                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18643                 }
18644
18645                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18646                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18647                     piece = (ChessSquare) (PROMOTED(piece));
18648                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18649                     p++;
18650                 }
18651                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18652                 if(piece == king) wKingRank = i;
18653                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18654             } else {
18655                 return FALSE;
18656             }
18657         }
18658     }
18659     while (*p == '/' || *p == ' ') p++;
18660
18661     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18662
18663     /* [HGM] by default clear Crazyhouse holdings, if present */
18664     if(gameInfo.holdingsWidth) {
18665        for(i=0; i<handSize; i++) {
18666            board[i][0]             = EmptySquare; /* black holdings */
18667            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18668            board[i][1]             = (ChessSquare) 0; /* black counts */
18669            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18670        }
18671     }
18672
18673     /* [HGM] look for Crazyhouse holdings here */
18674     while(*p==' ') p++;
18675     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18676         int swap=0, wcnt=0, bcnt=0;
18677         if(*p == '[') p++;
18678         if(*p == '<') swap++, p++;
18679         if(*p == '-' ) p++; /* empty holdings */ else {
18680             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18681             /* if we would allow FEN reading to set board size, we would   */
18682             /* have to add holdings and shift the board read so far here   */
18683             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18684                 p++;
18685                 if((int) piece >= (int) BlackPawn ) {
18686                     i = (int)piece - (int)BlackPawn;
18687                     i = PieceToNumber((ChessSquare)i);
18688                     if( i >= gameInfo.holdingsSize ) return FALSE;
18689                     board[handSize-1-i][0] = piece; /* black holdings */
18690                     board[handSize-1-i][1]++;       /* black counts   */
18691                     bcnt++;
18692                 } else {
18693                     i = (int)piece - (int)WhitePawn;
18694                     i = PieceToNumber((ChessSquare)i);
18695                     if( i >= gameInfo.holdingsSize ) return FALSE;
18696                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18697                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18698                     wcnt++;
18699                 }
18700             }
18701             if(subst) { // substitute back-rank question marks by holdings pieces
18702                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18703                     int k, m, n = bcnt + 1;
18704                     if(board[0][j] == ClearBoard) {
18705                         if(!wcnt) return FALSE;
18706                         n = rand() % wcnt;
18707                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18708                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18709                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18710                             break;
18711                         }
18712                     }
18713                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18714                         if(!bcnt) return FALSE;
18715                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18716                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[handSize-1-k][1]) < 0) {
18717                             board[BOARD_HEIGHT-1][j] = board[handSize-1-k][0]; bcnt--;
18718                             if(--board[handSize-1-k][1] == 0) board[handSize-1-k][0] = EmptySquare;
18719                             break;
18720                         }
18721                     }
18722                 }
18723                 subst = 0;
18724             }
18725         }
18726         if(*p == ']') p++;
18727     }
18728
18729     if(subst) return FALSE; // substitution requested, but no holdings
18730
18731     while(*p == ' ') p++;
18732
18733     /* Active color */
18734     c = *p++;
18735     if(appData.colorNickNames) {
18736       if( c == appData.colorNickNames[0] ) c = 'w'; else
18737       if( c == appData.colorNickNames[1] ) c = 'b';
18738     }
18739     switch (c) {
18740       case 'w':
18741         *blackPlaysFirst = FALSE;
18742         break;
18743       case 'b':
18744         *blackPlaysFirst = TRUE;
18745         break;
18746       default:
18747         return FALSE;
18748     }
18749
18750     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18751     /* return the extra info in global variiables             */
18752
18753     while(*p==' ') p++;
18754
18755     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18756         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18757         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18758     }
18759
18760     /* set defaults in case FEN is incomplete */
18761     board[EP_STATUS] = EP_UNKNOWN;
18762     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18763     for(i=0; i<nrCastlingRights; i++ ) {
18764         board[CASTLING][i] =
18765             appData.fischerCastling ? NoRights : initialRights[i];
18766     }   /* assume possible unless obviously impossible */
18767     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18768     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18769     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18770                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18771     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18772     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18773     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18774                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18775     FENrulePlies = 0;
18776
18777     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18778       char *q = p;
18779       int w=0, b=0;
18780       while(isalpha(*p)) {
18781         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18782         if(islower(*p)) b |= 1 << (*p++ - 'a');
18783       }
18784       if(*p == '-') p++;
18785       if(p != q) {
18786         board[TOUCHED_W] = ~w;
18787         board[TOUCHED_B] = ~b;
18788         while(*p == ' ') p++;
18789       }
18790     } else
18791
18792     if(nrCastlingRights) {
18793       int fischer = 0;
18794       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18795       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18796           /* castling indicator present, so default becomes no castlings */
18797           for(i=0; i<nrCastlingRights; i++ ) {
18798                  board[CASTLING][i] = NoRights;
18799           }
18800       }
18801       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18802              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18803              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18804              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18805         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18806
18807         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18808             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18809             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18810         }
18811         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18812             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18813         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18814                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18815         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18816                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18817         switch(c) {
18818           case'K':
18819               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18820               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18821               board[CASTLING][2] = whiteKingFile;
18822               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18823               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18824               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18825               break;
18826           case'Q':
18827               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18828               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18829               board[CASTLING][2] = whiteKingFile;
18830               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18831               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18832               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18833               break;
18834           case'k':
18835               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18836               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18837               board[CASTLING][5] = blackKingFile;
18838               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18839               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18840               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18841               break;
18842           case'q':
18843               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18844               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18845               board[CASTLING][5] = blackKingFile;
18846               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18847               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18848               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18849           case '-':
18850               break;
18851           default: /* FRC castlings */
18852               if(c >= 'a') { /* black rights */
18853                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18854                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18855                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18856                   if(i == BOARD_RGHT) break;
18857                   board[CASTLING][5] = i;
18858                   c -= AAA;
18859                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18860                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18861                   if(c > i)
18862                       board[CASTLING][3] = c;
18863                   else
18864                       board[CASTLING][4] = c;
18865               } else { /* white rights */
18866                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18867                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18868                     if(board[0][i] == WhiteKing) break;
18869                   if(i == BOARD_RGHT) break;
18870                   board[CASTLING][2] = i;
18871                   c -= AAA - 'a' + 'A';
18872                   if(board[0][c] >= WhiteKing) break;
18873                   if(c > i)
18874                       board[CASTLING][0] = c;
18875                   else
18876                       board[CASTLING][1] = c;
18877               }
18878         }
18879       }
18880       for(i=0; i<nrCastlingRights; i++)
18881         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18882       if(gameInfo.variant == VariantSChess)
18883         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18884       if(fischer && shuffle) appData.fischerCastling = TRUE;
18885     if (appData.debugMode) {
18886         fprintf(debugFP, "FEN castling rights:");
18887         for(i=0; i<nrCastlingRights; i++)
18888         fprintf(debugFP, " %d", board[CASTLING][i]);
18889         fprintf(debugFP, "\n");
18890     }
18891
18892       while(*p==' ') p++;
18893     }
18894
18895     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18896
18897     /* read e.p. field in games that know e.p. capture */
18898     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18899        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18900        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18901       if(*p=='-') {
18902         p++; board[EP_STATUS] = EP_NONE;
18903       } else {
18904          int d, r, c = *p - AAA;
18905
18906          if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18907              p++;
18908              board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18909              if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18910              d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18911              if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18912              board[LAST_TO] = 256*(r + d) + c;
18913              c = *p++ - AAA;
18914              if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18915                  if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18916                  board[LAST_TO] = 256*r + c;
18917                  if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18918              }
18919          }
18920       }
18921     }
18922
18923     while(*p == ' ') p++;
18924
18925     board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18926     if(sscanf(p, "%d+%d", &i, &j) == 2) {
18927         board[CHECK_COUNT] = i + 256*j;
18928         while(*p && *p != ' ') p++;
18929     }
18930
18931     c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18932     if(c > 0) {
18933         FENrulePlies = i; /* 50-move ply counter */
18934         /* (The move number is still ignored)    */
18935         if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18936     }
18937
18938     return TRUE;
18939 }
18940
18941 void
18942 EditPositionPasteFEN (char *fen)
18943 {
18944   if (fen != NULL) {
18945     Board initial_position;
18946
18947     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18948       DisplayError(_("Bad FEN position in clipboard"), 0);
18949       return ;
18950     } else {
18951       int savedBlackPlaysFirst = blackPlaysFirst;
18952       EditPositionEvent();
18953       blackPlaysFirst = savedBlackPlaysFirst;
18954       CopyBoard(boards[0], initial_position);
18955       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18956       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18957       DisplayBothClocks();
18958       DrawPosition(FALSE, boards[currentMove]);
18959     }
18960   }
18961 }
18962
18963 static char cseq[12] = "\\   ";
18964
18965 Boolean
18966 set_cont_sequence (char *new_seq)
18967 {
18968     int len;
18969     Boolean ret;
18970
18971     // handle bad attempts to set the sequence
18972         if (!new_seq)
18973                 return 0; // acceptable error - no debug
18974
18975     len = strlen(new_seq);
18976     ret = (len > 0) && (len < sizeof(cseq));
18977     if (ret)
18978       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18979     else if (appData.debugMode)
18980       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18981     return ret;
18982 }
18983
18984 /*
18985     reformat a source message so words don't cross the width boundary.  internal
18986     newlines are not removed.  returns the wrapped size (no null character unless
18987     included in source message).  If dest is NULL, only calculate the size required
18988     for the dest buffer.  lp argument indicats line position upon entry, and it's
18989     passed back upon exit.
18990 */
18991 int
18992 wrap (char *dest, char *src, int count, int width, int *lp)
18993 {
18994     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18995
18996     cseq_len = strlen(cseq);
18997     old_line = line = *lp;
18998     ansi = len = clen = 0;
18999
19000     for (i=0; i < count; i++)
19001     {
19002         if (src[i] == '\033')
19003             ansi = 1;
19004
19005         // if we hit the width, back up
19006         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
19007         {
19008             // store i & len in case the word is too long
19009             old_i = i, old_len = len;
19010
19011             // find the end of the last word
19012             while (i && src[i] != ' ' && src[i] != '\n')
19013             {
19014                 i--;
19015                 len--;
19016             }
19017
19018             // word too long?  restore i & len before splitting it
19019             if ((old_i-i+clen) >= width)
19020             {
19021                 i = old_i;
19022                 len = old_len;
19023             }
19024
19025             // extra space?
19026             if (i && src[i-1] == ' ')
19027                 len--;
19028
19029             if (src[i] != ' ' && src[i] != '\n')
19030             {
19031                 i--;
19032                 if (len)
19033                     len--;
19034             }
19035
19036             // now append the newline and continuation sequence
19037             if (dest)
19038                 dest[len] = '\n';
19039             len++;
19040             if (dest)
19041                 strncpy(dest+len, cseq, cseq_len);
19042             len += cseq_len;
19043             line = cseq_len;
19044             clen = cseq_len;
19045             continue;
19046         }
19047
19048         if (dest)
19049             dest[len] = src[i];
19050         len++;
19051         if (!ansi)
19052             line++;
19053         if (src[i] == '\n')
19054             line = 0;
19055         if (src[i] == 'm')
19056             ansi = 0;
19057     }
19058     if (dest && appData.debugMode)
19059     {
19060         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
19061             count, width, line, len, *lp);
19062         show_bytes(debugFP, src, count);
19063         fprintf(debugFP, "\ndest: ");
19064         show_bytes(debugFP, dest, len);
19065         fprintf(debugFP, "\n");
19066     }
19067     *lp = dest ? line : old_line;
19068
19069     return len;
19070 }
19071
19072 // [HGM] vari: routines for shelving variations
19073 Boolean modeRestore = FALSE;
19074
19075 void
19076 PushInner (int firstMove, int lastMove)
19077 {
19078         int i, j, nrMoves = lastMove - firstMove;
19079
19080         // push current tail of game on stack
19081         savedResult[storedGames] = gameInfo.result;
19082         savedDetails[storedGames] = gameInfo.resultDetails;
19083         gameInfo.resultDetails = NULL;
19084         savedFirst[storedGames] = firstMove;
19085         savedLast [storedGames] = lastMove;
19086         savedFramePtr[storedGames] = framePtr;
19087         framePtr -= nrMoves; // reserve space for the boards
19088         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19089             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19090             for(j=0; j<MOVE_LEN; j++)
19091                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19092             for(j=0; j<2*MOVE_LEN; j++)
19093                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19094             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19095             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19096             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19097             pvInfoList[firstMove+i-1].depth = 0;
19098             commentList[framePtr+i] = commentList[firstMove+i];
19099             commentList[firstMove+i] = NULL;
19100         }
19101
19102         storedGames++;
19103         forwardMostMove = firstMove; // truncate game so we can start variation
19104 }
19105
19106 void
19107 PushTail (int firstMove, int lastMove)
19108 {
19109         if(appData.icsActive) { // only in local mode
19110                 forwardMostMove = currentMove; // mimic old ICS behavior
19111                 return;
19112         }
19113         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19114
19115         PushInner(firstMove, lastMove);
19116         if(storedGames == 1) GreyRevert(FALSE);
19117         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19118 }
19119
19120 void
19121 PopInner (Boolean annotate)
19122 {
19123         int i, j, nrMoves;
19124         char buf[8000], moveBuf[20];
19125
19126         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19127         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19128         nrMoves = savedLast[storedGames] - currentMove;
19129         if(annotate) {
19130                 int cnt = 10;
19131                 if(!WhiteOnMove(currentMove))
19132                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19133                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19134                 for(i=currentMove; i<forwardMostMove; i++) {
19135                         if(WhiteOnMove(i))
19136                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19137                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19138                         strcat(buf, moveBuf);
19139                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19140                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19141                 }
19142                 strcat(buf, ")");
19143         }
19144         for(i=1; i<=nrMoves; i++) { // copy last variation back
19145             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19146             for(j=0; j<MOVE_LEN; j++)
19147                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19148             for(j=0; j<2*MOVE_LEN; j++)
19149                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19150             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19151             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19152             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19153             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19154             commentList[currentMove+i] = commentList[framePtr+i];
19155             commentList[framePtr+i] = NULL;
19156         }
19157         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19158         framePtr = savedFramePtr[storedGames];
19159         gameInfo.result = savedResult[storedGames];
19160         if(gameInfo.resultDetails != NULL) {
19161             free(gameInfo.resultDetails);
19162       }
19163         gameInfo.resultDetails = savedDetails[storedGames];
19164         forwardMostMove = currentMove + nrMoves;
19165 }
19166
19167 Boolean
19168 PopTail (Boolean annotate)
19169 {
19170         if(appData.icsActive) return FALSE; // only in local mode
19171         if(!storedGames) return FALSE; // sanity
19172         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19173
19174         PopInner(annotate);
19175         if(currentMove < forwardMostMove) ForwardEvent(); else
19176         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19177
19178         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19179         return TRUE;
19180 }
19181
19182 void
19183 CleanupTail ()
19184 {       // remove all shelved variations
19185         int i;
19186         for(i=0; i<storedGames; i++) {
19187             if(savedDetails[i])
19188                 free(savedDetails[i]);
19189             savedDetails[i] = NULL;
19190         }
19191         for(i=framePtr; i<MAX_MOVES; i++) {
19192                 if(commentList[i]) free(commentList[i]);
19193                 commentList[i] = NULL;
19194         }
19195         framePtr = MAX_MOVES-1;
19196         storedGames = 0;
19197 }
19198
19199 void
19200 LoadVariation (int index, char *text)
19201 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19202         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19203         int level = 0, move;
19204
19205         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19206         // first find outermost bracketing variation
19207         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19208             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19209                 if(*p == '{') wait = '}'; else
19210                 if(*p == '[') wait = ']'; else
19211                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19212                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19213             }
19214             if(*p == wait) wait = NULLCHAR; // closing ]} found
19215             p++;
19216         }
19217         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19218         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19219         end[1] = NULLCHAR; // clip off comment beyond variation
19220         ToNrEvent(currentMove-1);
19221         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19222         // kludge: use ParsePV() to append variation to game
19223         move = currentMove;
19224         ParsePV(start, TRUE, TRUE);
19225         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19226         ClearPremoveHighlights();
19227         CommentPopDown();
19228         ToNrEvent(currentMove+1);
19229 }
19230
19231 int transparency[2];
19232
19233 void
19234 LoadTheme ()
19235 {
19236 #define BUF_SIZ (2*MSG_SIZ)
19237     char *p, *q, buf[BUF_SIZ];
19238     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19239         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19240         ParseArgsFromString(buf);
19241         ActivateTheme(TRUE); // also redo colors
19242         return;
19243     }
19244     p = nickName;
19245     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19246     {
19247         int len;
19248         q = appData.themeNames;
19249         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19250       if(appData.useBitmaps) {
19251         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19252                 Shorten(appData.liteBackTextureFile));
19253         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19254                 Shorten(appData.darkBackTextureFile),
19255                 appData.liteBackTextureMode,
19256                 appData.darkBackTextureMode );
19257       } else {
19258         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19259       }
19260       if(!appData.useBitmaps || transparency[0]) {
19261         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19262       }
19263       if(!appData.useBitmaps || transparency[1]) {
19264         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19265       }
19266       if(appData.useBorder) {
19267         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19268                 appData.border);
19269       } else {
19270         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19271       }
19272       if(appData.useFont) {
19273         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19274                 appData.renderPiecesWithFont,
19275                 appData.fontToPieceTable,
19276                 Col2Text(9),    // appData.fontBackColorWhite
19277                 Col2Text(10) ); // appData.fontForeColorBlack
19278       } else {
19279         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19280         if(appData.pieceDirectory[0]) {
19281           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19282           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19283             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19284         }
19285         if(!appData.pieceDirectory[0] || !appData.trueColors)
19286           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19287                 Col2Text(0),   // whitePieceColor
19288                 Col2Text(1) ); // blackPieceColor
19289       }
19290       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19291                 Col2Text(4),   // highlightSquareColor
19292                 Col2Text(5) ); // premoveHighlightColor
19293         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19294         if(insert != q) insert[-1] = NULLCHAR;
19295         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19296         if(q)   free(q);
19297     }
19298     ActivateTheme(FALSE);
19299 }