47b443bc4698f268f821884085c70ac0a79aa7a6
[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
7585 void
7586 LeftClick (ClickType clickType, int xPix, int yPix)
7587 {
7588     int x, y;
7589     static Boolean saveAnimate;
7590     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7591     char promoChoice = NULLCHAR;
7592     ChessSquare piece;
7593     static TimeMark lastClickTime, prevClickTime;
7594
7595     if(flashing) return;
7596
7597   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7598     x = EventToSquare(xPix, BOARD_WIDTH);
7599     y = EventToSquare(yPix, BOARD_HEIGHT);
7600     if (!flipView && y >= 0) {
7601         y = BOARD_HEIGHT - 1 - y;
7602     }
7603     if (flipView && x >= 0) {
7604         x = BOARD_WIDTH - 1 - x;
7605     }
7606
7607     // map clicks in offsetted holdings back to true coords (or switch the offset)
7608     if(x == BOARD_RGHT+1) {
7609         if(handOffsets & 1) {
7610             if(y == 0) { handOffsets &= ~1; DrawPosition(TRUE, boards[currentMove]); return; }
7611             y += handSize - BOARD_HEIGHT;
7612         } else if(y == BOARD_HEIGHT-1) { handOffsets |= 1; DrawPosition(TRUE, boards[currentMove]); return; }
7613     }
7614     if(x == BOARD_LEFT-2) {
7615         if(!(handOffsets & 2)) {
7616             if(y == 0) { handOffsets |= 2; DrawPosition(TRUE, boards[currentMove]); return; }
7617             y += handSize - BOARD_HEIGHT;
7618         } else if(y == BOARD_HEIGHT-1) { handOffsets &= ~2; DrawPosition(TRUE, boards[currentMove]); return; }
7619     }
7620
7621     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7622         static int dummy;
7623         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7624         right = TRUE;
7625         return;
7626     }
7627
7628     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7629
7630     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7631
7632     if (clickType == Press) ErrorPopDown();
7633     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7634
7635     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7636         defaultPromoChoice = promoSweep;
7637         promoSweep = EmptySquare;   // terminate sweep
7638         promoDefaultAltered = TRUE;
7639         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7640     }
7641
7642     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7643         if(clickType == Release) return; // ignore upclick of click-click destination
7644         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7645         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7646         if(gameInfo.holdingsWidth &&
7647                 (WhiteOnMove(currentMove)
7648                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7649                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7650             // click in right holdings, for determining promotion piece
7651             ChessSquare p = boards[currentMove][y][x];
7652             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7653             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7654             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7655                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7656                 fromX = fromY = -1;
7657                 return;
7658             }
7659         }
7660         DrawPosition(FALSE, boards[currentMove]);
7661         return;
7662     }
7663
7664     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7665     if(clickType == Press
7666             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7667               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7668               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7669         return;
7670
7671     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7672         // could be static click on premove from-square: abort premove
7673         gotPremove = 0;
7674         ClearPremoveHighlights();
7675     }
7676
7677     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7678         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7679
7680     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7681         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7682                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7683         defaultPromoChoice = DefaultPromoChoice(side);
7684     }
7685
7686     autoQueen = appData.alwaysPromoteToQueen;
7687
7688     if (fromX == -1) {
7689       int originalY = y;
7690       gatingPiece = EmptySquare;
7691       if (clickType != Press) {
7692         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7693             DragPieceEnd(xPix, yPix); dragging = 0;
7694             DrawPosition(FALSE, NULL);
7695         }
7696         return;
7697       }
7698       doubleClick = FALSE;
7699       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7700         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7701       }
7702       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7703       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7704          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7705          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7706             /* First square */
7707             if (OKToStartUserMove(fromX, fromY)) {
7708                 second = 0;
7709                 ReportClick("lift", x, y);
7710                 MarkTargetSquares(0);
7711                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7712                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7713                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7714                     promoSweep = defaultPromoChoice;
7715                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7716                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7717                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7718                 }
7719                 if (appData.highlightDragging) {
7720                     SetHighlights(fromX, fromY, -1, -1);
7721                 } else {
7722                     ClearHighlights();
7723                 }
7724             } else fromX = fromY = -1;
7725             return;
7726         }
7727     }
7728
7729     /* fromX != -1 */
7730     if (clickType == Press && gameMode != EditPosition) {
7731         ChessSquare fromP;
7732         ChessSquare toP;
7733         int frc;
7734
7735         // ignore off-board to clicks
7736         if(y < 0 || x < 0) return;
7737
7738         /* Check if clicking again on the same color piece */
7739         fromP = boards[currentMove][fromY][fromX];
7740         toP = boards[currentMove][y][x];
7741         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7742         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7743             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7744            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7745              WhitePawn <= toP && toP <= WhiteKing &&
7746              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7747              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7748             (BlackPawn <= fromP && fromP <= BlackKing &&
7749              BlackPawn <= toP && toP <= BlackKing &&
7750              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7751              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7752             /* Clicked again on same color piece -- changed his mind */
7753             second = (x == fromX && y == fromY);
7754             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7755             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7756                 second = FALSE; // first double-click rather than scond click
7757                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7758             }
7759             promoDefaultAltered = FALSE;
7760            if(!second) MarkTargetSquares(1);
7761            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7762             if (appData.highlightDragging) {
7763                 SetHighlights(x, y, -1, -1);
7764             } else {
7765                 ClearHighlights();
7766             }
7767             if (OKToStartUserMove(x, y)) {
7768                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7769                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7770                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7771                  gatingPiece = boards[currentMove][fromY][fromX];
7772                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7773                 fromX = x;
7774                 fromY = y; dragging = 1;
7775                 if(!second) ReportClick("lift", x, y);
7776                 MarkTargetSquares(0);
7777                 DragPieceBegin(xPix, yPix, FALSE);
7778                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7779                     promoSweep = defaultPromoChoice;
7780                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7781                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7782                 }
7783             }
7784            }
7785            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7786            second = FALSE;
7787         }
7788         // ignore clicks on holdings
7789         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7790     }
7791
7792     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7793         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7794         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7795         return;
7796     }
7797
7798     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7799         DragPieceEnd(xPix, yPix); dragging = 0;
7800         if(clearFlag) {
7801             // a deferred attempt to click-click move an empty square on top of a piece
7802             boards[currentMove][y][x] = EmptySquare;
7803             ClearHighlights();
7804             DrawPosition(FALSE, boards[currentMove]);
7805             fromX = fromY = -1; clearFlag = 0;
7806             return;
7807         }
7808         if (appData.animateDragging) {
7809             /* Undo animation damage if any */
7810             DrawPosition(FALSE, NULL);
7811         }
7812         if (second) {
7813             /* Second up/down in same square; just abort move */
7814             second = 0;
7815             fromX = fromY = -1;
7816             gatingPiece = EmptySquare;
7817             ClearHighlights();
7818             gotPremove = 0;
7819             ClearPremoveHighlights();
7820             MarkTargetSquares(-1);
7821             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7822         } else {
7823             /* First upclick in same square; start click-click mode */
7824             SetHighlights(x, y, -1, -1);
7825         }
7826         return;
7827     }
7828
7829     clearFlag = 0;
7830
7831     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7832        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7833         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7834         DisplayMessage(_("only marked squares are legal"),"");
7835         DrawPosition(TRUE, NULL);
7836         return; // ignore to-click
7837     }
7838
7839     /* we now have a different from- and (possibly off-board) to-square */
7840     /* Completed move */
7841     if(!sweepSelecting) {
7842         toX = x;
7843         toY = y;
7844     }
7845
7846     piece = boards[currentMove][fromY][fromX];
7847
7848     saveAnimate = appData.animate;
7849     if (clickType == Press) {
7850         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7851         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7852             // must be Edit Position mode with empty-square selected
7853             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7854             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7855             return;
7856         }
7857         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7858             return;
7859         }
7860         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7861             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7862         } else
7863         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7864         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7865           if(appData.sweepSelect) {
7866             promoSweep = defaultPromoChoice;
7867             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7868             selectFlag = 0; lastX = xPix; lastY = yPix;
7869             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7870             saveFlash = appData.flashCount; appData.flashCount = 0;
7871             Sweep(0); // Pawn that is going to promote: preview promotion piece
7872             sweepSelecting = 1;
7873             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7874             MarkTargetSquares(1);
7875           }
7876           return; // promo popup appears on up-click
7877         }
7878         /* Finish clickclick move */
7879         if (appData.animate || appData.highlightLastMove) {
7880             SetHighlights(fromX, fromY, toX, toY);
7881         } else {
7882             ClearHighlights();
7883         }
7884         MarkTargetSquares(1);
7885     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7886         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7887         *promoRestrict = 0; appData.flashCount = saveFlash;
7888         if (appData.animate || appData.highlightLastMove) {
7889             SetHighlights(fromX, fromY, toX, toY);
7890         } else {
7891             ClearHighlights();
7892         }
7893         MarkTargetSquares(1);
7894     } else {
7895 #if 0
7896 // [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
7897         /* Finish drag move */
7898         if (appData.highlightLastMove) {
7899             SetHighlights(fromX, fromY, toX, toY);
7900         } else {
7901             ClearHighlights();
7902         }
7903 #endif
7904         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7905           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7906         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7907         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7908           dragging *= 2;            // flag button-less dragging if we are dragging
7909           MarkTargetSquares(1);
7910           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7911           else {
7912             kill2X = killX; kill2Y = killY;
7913             killX = x; killY = y;     // remember this square as intermediate
7914             ReportClick("put", x, y); // and inform engine
7915             ReportClick("lift", x, y);
7916             MarkTargetSquares(0);
7917             return;
7918           }
7919         }
7920         DragPieceEnd(xPix, yPix); dragging = 0;
7921         /* Don't animate move and drag both */
7922         appData.animate = FALSE;
7923         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7924     }
7925
7926     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7927     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7928         ChessSquare piece = boards[currentMove][fromY][fromX];
7929         if(gameMode == EditPosition && piece != EmptySquare &&
7930            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7931             int n;
7932
7933             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7934                 n = PieceToNumber(piece - (int)BlackPawn);
7935                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7936                 boards[currentMove][handSize-1 - n][0] = piece;
7937                 boards[currentMove][handSize-1 - n][1]++;
7938             } else
7939             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7940                 n = PieceToNumber(piece);
7941                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7942                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7943                 boards[currentMove][n][BOARD_WIDTH-2]++;
7944             }
7945             boards[currentMove][fromY][fromX] = EmptySquare;
7946         }
7947         ClearHighlights();
7948         fromX = fromY = -1;
7949         MarkTargetSquares(1);
7950         DrawPosition(TRUE, boards[currentMove]);
7951         return;
7952     }
7953
7954     // off-board moves should not be highlighted
7955     if(x < 0 || y < 0) {
7956         ClearHighlights();
7957         DrawPosition(FALSE, NULL);
7958     } else ReportClick("put", x, y);
7959
7960     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7961  }
7962
7963     if(legal[toY][toX] == 2) { // highlight-induced promotion
7964         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7965         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7966     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7967       if(!*promoRestrict) {           // but has not done that yet
7968         deferChoice = TRUE;           // set up retry for when it does
7969         return;                       // and wait for that
7970       }
7971       promoChoice = ToLower(*promoRestrict); // force engine's choice
7972       deferChoice = FALSE;
7973     }
7974
7975     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7976         SetHighlights(fromX, fromY, toX, toY);
7977         MarkTargetSquares(1);
7978         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7979             // [HGM] super: promotion to captured piece selected from holdings
7980             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7981             promotionChoice = TRUE;
7982             // kludge follows to temporarily execute move on display, without promoting yet
7983             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7984             boards[currentMove][toY][toX] = p;
7985             DrawPosition(FALSE, boards[currentMove]);
7986             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7987             boards[currentMove][toY][toX] = q;
7988             DisplayMessage("Click in holdings to choose piece", "");
7989             return;
7990         }
7991         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7992         PromotionPopUp(promoChoice);
7993     } else {
7994         int oldMove = currentMove;
7995         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7996         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7997         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7998         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7999         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
8000            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
8001             DrawPosition(TRUE, boards[currentMove]);
8002         else DrawPosition(FALSE, NULL);
8003         fromX = fromY = -1;
8004         flashing = 0;
8005     }
8006     appData.animate = saveAnimate;
8007     if (appData.animate || appData.animateDragging) {
8008         /* Undo animation damage if needed */
8009 //      DrawPosition(FALSE, NULL);
8010     }
8011 }
8012
8013 int
8014 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
8015 {   // front-end-free part taken out of PieceMenuPopup
8016     int whichMenu; int xSqr, ySqr;
8017
8018     if(seekGraphUp) { // [HGM] seekgraph
8019         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
8020         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
8021         return -2;
8022     }
8023
8024     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
8025          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
8026         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
8027         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
8028         if(action == Press)   {
8029             originalFlip = flipView;
8030             flipView = !flipView; // temporarily flip board to see game from partners perspective
8031             DrawPosition(TRUE, partnerBoard);
8032             DisplayMessage(partnerStatus, "");
8033             partnerUp = TRUE;
8034         } else if(action == Release) {
8035             flipView = originalFlip;
8036             DrawPosition(TRUE, boards[currentMove]);
8037             partnerUp = FALSE;
8038         }
8039         return -2;
8040     }
8041
8042     xSqr = EventToSquare(x, BOARD_WIDTH);
8043     ySqr = EventToSquare(y, BOARD_HEIGHT);
8044     if (action == Release) {
8045         if(pieceSweep != EmptySquare) {
8046             EditPositionMenuEvent(pieceSweep, toX, toY);
8047             pieceSweep = EmptySquare;
8048         } else UnLoadPV(); // [HGM] pv
8049     }
8050     if (action != Press) return -2; // return code to be ignored
8051     switch (gameMode) {
8052       case IcsExamining:
8053         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8054       case EditPosition:
8055         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8056         if (xSqr < 0 || ySqr < 0) return -1;
8057         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8058         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8059         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8060         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8061         NextPiece(0);
8062         return 2; // grab
8063       case IcsObserving:
8064         if(!appData.icsEngineAnalyze) return -1;
8065       case IcsPlayingWhite:
8066       case IcsPlayingBlack:
8067         if(!appData.zippyPlay) goto noZip;
8068       case AnalyzeMode:
8069       case AnalyzeFile:
8070       case MachinePlaysWhite:
8071       case MachinePlaysBlack:
8072       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8073         if (!appData.dropMenu) {
8074           LoadPV(x, y);
8075           return 2; // flag front-end to grab mouse events
8076         }
8077         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8078            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8079       case EditGame:
8080       noZip:
8081         if (xSqr < 0 || ySqr < 0) return -1;
8082         if (!appData.dropMenu || appData.testLegality &&
8083             gameInfo.variant != VariantBughouse &&
8084             gameInfo.variant != VariantCrazyhouse) return -1;
8085         whichMenu = 1; // drop menu
8086         break;
8087       default:
8088         return -1;
8089     }
8090
8091     if (((*fromX = xSqr) < 0) ||
8092         ((*fromY = ySqr) < 0)) {
8093         *fromX = *fromY = -1;
8094         return -1;
8095     }
8096     if (flipView)
8097       *fromX = BOARD_WIDTH - 1 - *fromX;
8098     else
8099       *fromY = BOARD_HEIGHT - 1 - *fromY;
8100
8101     return whichMenu;
8102 }
8103
8104 void
8105 Wheel (int dir, int x, int y)
8106 {
8107     if(gameMode == EditPosition) {
8108         int xSqr = EventToSquare(x, BOARD_WIDTH);
8109         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8110         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8111         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8112         do {
8113             boards[currentMove][ySqr][xSqr] += dir;
8114             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8115             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8116         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8117         DrawPosition(FALSE, boards[currentMove]);
8118     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8119 }
8120
8121 void
8122 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8123 {
8124 //    char * hint = lastHint;
8125     FrontEndProgramStats stats;
8126
8127     stats.which = cps == &first ? 0 : 1;
8128     stats.depth = cpstats->depth;
8129     stats.nodes = cpstats->nodes;
8130     stats.score = cpstats->score;
8131     stats.time = cpstats->time;
8132     stats.pv = cpstats->movelist;
8133     stats.hint = lastHint;
8134     stats.an_move_index = 0;
8135     stats.an_move_count = 0;
8136
8137     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8138         stats.hint = cpstats->move_name;
8139         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8140         stats.an_move_count = cpstats->nr_moves;
8141     }
8142
8143     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
8144
8145     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8146         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8147
8148     SetProgramStats( &stats );
8149 }
8150
8151 void
8152 ClearEngineOutputPane (int which)
8153 {
8154     static FrontEndProgramStats dummyStats;
8155     dummyStats.which = which;
8156     dummyStats.pv = "#";
8157     SetProgramStats( &dummyStats );
8158 }
8159
8160 #define MAXPLAYERS 500
8161
8162 char *
8163 TourneyStandings (int display)
8164 {
8165     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8166     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8167     char result, *p, *names[MAXPLAYERS];
8168
8169     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8170         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8171     names[0] = p = strdup(appData.participants);
8172     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8173
8174     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8175
8176     while(result = appData.results[nr]) {
8177         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8178         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8179         wScore = bScore = 0;
8180         switch(result) {
8181           case '+': wScore = 2; break;
8182           case '-': bScore = 2; break;
8183           case '=': wScore = bScore = 1; break;
8184           case ' ':
8185           case '*': return strdup("busy"); // tourney not finished
8186         }
8187         score[w] += wScore;
8188         score[b] += bScore;
8189         games[w]++;
8190         games[b]++;
8191         nr++;
8192     }
8193     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8194     for(w=0; w<nPlayers; w++) {
8195         bScore = -1;
8196         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8197         ranking[w] = b; points[w] = bScore; score[b] = -2;
8198     }
8199     p = malloc(nPlayers*34+1);
8200     for(w=0; w<nPlayers && w<display; w++)
8201         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8202     free(names[0]);
8203     return p;
8204 }
8205
8206 void
8207 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8208 {       // count all piece types
8209         int p, f, r;
8210         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8211         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8212         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8213                 p = board[r][f];
8214                 pCnt[p]++;
8215                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8216                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8217                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8218                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8219                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8220                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8221         }
8222 }
8223
8224 int
8225 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8226 {
8227         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8228         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8229
8230         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8231         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8232         if(myPawns == 2 && nMine == 3) // KPP
8233             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8234         if(myPawns == 1 && nMine == 2) // KP
8235             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8236         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8237             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8238         if(myPawns) return FALSE;
8239         if(pCnt[WhiteRook+side])
8240             return pCnt[BlackRook-side] ||
8241                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8242                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8243                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8244         if(pCnt[WhiteCannon+side]) {
8245             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8246             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8247         }
8248         if(pCnt[WhiteKnight+side])
8249             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8250         return FALSE;
8251 }
8252
8253 int
8254 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8255 {
8256         VariantClass v = gameInfo.variant;
8257
8258         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8259         if(v == VariantShatranj) return TRUE; // always winnable through baring
8260         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8261         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8262
8263         if(v == VariantXiangqi) {
8264                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8265
8266                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8267                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8268                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8269                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8270                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8271                 if(stale) // we have at least one last-rank P plus perhaps C
8272                     return majors // KPKX
8273                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8274                 else // KCA*E*
8275                     return pCnt[WhiteFerz+side] // KCAK
8276                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8277                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8278                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8279
8280         } else if(v == VariantKnightmate) {
8281                 if(nMine == 1) return FALSE;
8282                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8283         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8284                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8285
8286                 if(nMine == 1) return FALSE; // bare King
8287                 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
8288                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8289                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8290                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8291                 if(pCnt[WhiteKnight+side])
8292                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8293                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8294                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8295                 if(nBishops)
8296                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8297                 if(pCnt[WhiteAlfil+side])
8298                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8299                 if(pCnt[WhiteWazir+side])
8300                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8301         }
8302
8303         return TRUE;
8304 }
8305
8306 int
8307 CompareWithRights (Board b1, Board b2)
8308 {
8309     int rights = 0;
8310     if(!CompareBoards(b1, b2)) return FALSE;
8311     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8312     /* compare castling rights */
8313     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8314            rights++; /* King lost rights, while rook still had them */
8315     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8316         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8317            rights++; /* but at least one rook lost them */
8318     }
8319     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8320            rights++;
8321     if( b1[CASTLING][5] != NoRights ) {
8322         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8323            rights++;
8324     }
8325     return rights == 0;
8326 }
8327
8328 int
8329 Adjudicate (ChessProgramState *cps)
8330 {       // [HGM] some adjudications useful with buggy engines
8331         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8332         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8333         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8334         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8335         int k, drop, count = 0; static int bare = 1;
8336         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8337         Boolean canAdjudicate = !appData.icsActive;
8338
8339         // most tests only when we understand the game, i.e. legality-checking on
8340             if( appData.testLegality )
8341             {   /* [HGM] Some more adjudications for obstinate engines */
8342                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8343                 static int moveCount = 6;
8344                 ChessMove result;
8345                 char *reason = NULL;
8346
8347                 /* Count what is on board. */
8348                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8349
8350                 /* Some material-based adjudications that have to be made before stalemate test */
8351                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8352                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8353                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8354                      if(canAdjudicate && appData.checkMates) {
8355                          if(engineOpponent)
8356                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8357                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8358                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8359                          return 1;
8360                      }
8361                 }
8362
8363                 /* Bare King in Shatranj (loses) or Losers (wins) */
8364                 if( nrW == 1 || nrB == 1) {
8365                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8366                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8367                      if(canAdjudicate && appData.checkMates) {
8368                          if(engineOpponent)
8369                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8370                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8371                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8372                          return 1;
8373                      }
8374                   } else
8375                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8376                   {    /* bare King */
8377                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8378                         if(canAdjudicate && appData.checkMates) {
8379                             /* but only adjudicate if adjudication enabled */
8380                             if(engineOpponent)
8381                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8382                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8383                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8384                             return 1;
8385                         }
8386                   }
8387                 } else bare = 1;
8388
8389
8390             // don't wait for engine to announce game end if we can judge ourselves
8391             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8392               case MT_CHECK:
8393                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8394                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8395                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8396                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8397                             checkCnt++;
8398                         if(checkCnt >= 2) {
8399                             reason = "Xboard adjudication: 3rd check";
8400                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8401                             break;
8402                         }
8403                     }
8404                 }
8405               case MT_NONE:
8406               default:
8407                 break;
8408               case MT_STEALMATE:
8409               case MT_STALEMATE:
8410               case MT_STAINMATE:
8411                 reason = "Xboard adjudication: Stalemate";
8412                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8413                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8414                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8415                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8416                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8417                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8418                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8419                                                                         EP_CHECKMATE : EP_WINS);
8420                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8421                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8422                 }
8423                 break;
8424               case MT_CHECKMATE:
8425                 reason = "Xboard adjudication: Checkmate";
8426                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8427                 if(gameInfo.variant == VariantShogi) {
8428                     if(forwardMostMove > backwardMostMove
8429                        && moveList[forwardMostMove-1][1] == '@'
8430                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8431                         reason = "XBoard adjudication: pawn-drop mate";
8432                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8433                     }
8434                 }
8435                 break;
8436             }
8437
8438                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8439                     case EP_STALEMATE:
8440                         result = GameIsDrawn; break;
8441                     case EP_CHECKMATE:
8442                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8443                     case EP_WINS:
8444                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8445                     default:
8446                         result = EndOfFile;
8447                 }
8448                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8449                     if(engineOpponent)
8450                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8451                     GameEnds( result, reason, GE_XBOARD );
8452                     return 1;
8453                 }
8454
8455                 /* Next absolutely insufficient mating material. */
8456                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8457                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8458                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8459
8460                      /* always flag draws, for judging claims */
8461                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8462
8463                      if(canAdjudicate && appData.materialDraws) {
8464                          /* but only adjudicate them if adjudication enabled */
8465                          if(engineOpponent) {
8466                            SendToProgram("force\n", engineOpponent); // suppress reply
8467                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8468                          }
8469                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8470                          return 1;
8471                      }
8472                 }
8473
8474                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8475                 if(gameInfo.variant == VariantXiangqi ?
8476                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8477                  : nrW + nrB == 4 &&
8478                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8479                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8480                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8481                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8482                    ) ) {
8483                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8484                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8485                           if(engineOpponent) {
8486                             SendToProgram("force\n", engineOpponent); // suppress reply
8487                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8488                           }
8489                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8490                           return 1;
8491                      }
8492                 } else moveCount = 6;
8493             }
8494
8495         // Repetition draws and 50-move rule can be applied independently of legality testing
8496
8497                 /* Check for rep-draws */
8498                 count = 0;
8499                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8500                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8501                 for(k = forwardMostMove-2;
8502                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8503                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8504                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8505                     k-=2)
8506                 {   int rights=0;
8507                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8508                         /* compare castling rights */
8509                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8510                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8511                                 rights++; /* King lost rights, while rook still had them */
8512                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8513                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8514                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8515                                    rights++; /* but at least one rook lost them */
8516                         }
8517                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8518                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8519                                 rights++;
8520                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8521                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8522                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8523                                    rights++;
8524                         }
8525                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8526                             && appData.drawRepeats > 1) {
8527                              /* adjudicate after user-specified nr of repeats */
8528                              int result = GameIsDrawn;
8529                              char *details = "XBoard adjudication: repetition draw";
8530                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8531                                 // [HGM] xiangqi: check for forbidden perpetuals
8532                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8533                                 for(m=forwardMostMove; m>k; m-=2) {
8534                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8535                                         ourPerpetual = 0; // the current mover did not always check
8536                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8537                                         hisPerpetual = 0; // the opponent did not always check
8538                                 }
8539                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8540                                                                         ourPerpetual, hisPerpetual);
8541                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8542                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8543                                     details = "Xboard adjudication: perpetual checking";
8544                                 } else
8545                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8546                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8547                                 } else
8548                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8549                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8550                                         result = BlackWins;
8551                                         details = "Xboard adjudication: repetition";
8552                                     }
8553                                 } else // it must be XQ
8554                                 // Now check for perpetual chases
8555                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8556                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8557                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8558                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8559                                         static char resdet[MSG_SIZ];
8560                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8561                                         details = resdet;
8562                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8563                                     } else
8564                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8565                                         break; // Abort repetition-checking loop.
8566                                 }
8567                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8568                              }
8569                              if(engineOpponent) {
8570                                SendToProgram("force\n", engineOpponent); // suppress reply
8571                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8572                              }
8573                              GameEnds( result, details, GE_XBOARD );
8574                              return 1;
8575                         }
8576                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8577                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8578                     }
8579                 }
8580
8581                 /* Now we test for 50-move draws. Determine ply count */
8582                 count = forwardMostMove;
8583                 /* look for last irreversble move */
8584                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8585                     count--;
8586                 /* if we hit starting position, add initial plies */
8587                 if( count == backwardMostMove )
8588                     count -= initialRulePlies;
8589                 count = forwardMostMove - count;
8590                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8591                         // adjust reversible move counter for checks in Xiangqi
8592                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8593                         if(i < backwardMostMove) i = backwardMostMove;
8594                         while(i <= forwardMostMove) {
8595                                 lastCheck = inCheck; // check evasion does not count
8596                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8597                                 if(inCheck || lastCheck) count--; // check does not count
8598                                 i++;
8599                         }
8600                 }
8601                 if( count >= 100)
8602                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8603                          /* this is used to judge if draw claims are legal */
8604                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8605                          if(engineOpponent) {
8606                            SendToProgram("force\n", engineOpponent); // suppress reply
8607                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8608                          }
8609                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8610                          return 1;
8611                 }
8612
8613                 /* if draw offer is pending, treat it as a draw claim
8614                  * when draw condition present, to allow engines a way to
8615                  * claim draws before making their move to avoid a race
8616                  * condition occurring after their move
8617                  */
8618                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8619                          char *p = NULL;
8620                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8621                              p = "Draw claim: 50-move rule";
8622                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8623                              p = "Draw claim: 3-fold repetition";
8624                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8625                              p = "Draw claim: insufficient mating material";
8626                          if( p != NULL && canAdjudicate) {
8627                              if(engineOpponent) {
8628                                SendToProgram("force\n", engineOpponent); // suppress reply
8629                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8630                              }
8631                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8632                              return 1;
8633                          }
8634                 }
8635
8636                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
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, "Xboard adjudication: long game", GE_XBOARD );
8642                     return 1;
8643                 }
8644         return 0;
8645 }
8646
8647 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8648 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8649 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8650
8651 static int
8652 BitbaseProbe ()
8653 {
8654     int pieces[10], squares[10], cnt=0, r, f, res;
8655     static int loaded;
8656     static PPROBE_EGBB probeBB;
8657     if(!appData.testLegality) return 10;
8658     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8659     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8660     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8661     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8662         ChessSquare piece = boards[forwardMostMove][r][f];
8663         int black = (piece >= BlackPawn);
8664         int type = piece - black*BlackPawn;
8665         if(piece == EmptySquare) continue;
8666         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8667         if(type == WhiteKing) type = WhiteQueen + 1;
8668         type = egbbCode[type];
8669         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8670         pieces[cnt] = type + black*6;
8671         if(++cnt > 5) return 11;
8672     }
8673     pieces[cnt] = squares[cnt] = 0;
8674     // probe EGBB
8675     if(loaded == 2) return 13; // loading failed before
8676     if(loaded == 0) {
8677         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8678         HMODULE lib;
8679         PLOAD_EGBB loadBB;
8680         loaded = 2; // prepare for failure
8681         if(!path) return 13; // no egbb installed
8682         strncpy(buf, path + 8, MSG_SIZ);
8683         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8684         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8685         lib = LoadLibrary(buf);
8686         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8687         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8688         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8689         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8690         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8691         loaded = 1; // success!
8692     }
8693     res = probeBB(forwardMostMove & 1, pieces, squares);
8694     return res > 0 ? 1 : res < 0 ? -1 : 0;
8695 }
8696
8697 char *
8698 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8699 {   // [HGM] book: this routine intercepts moves to simulate book replies
8700     char *bookHit = NULL;
8701
8702     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8703         char buf[MSG_SIZ];
8704         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8705         SendToProgram(buf, cps);
8706     }
8707     //first determine if the incoming move brings opponent into his book
8708     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8709         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8710     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8711     if(bookHit != NULL && !cps->bookSuspend) {
8712         // make sure opponent is not going to reply after receiving move to book position
8713         SendToProgram("force\n", cps);
8714         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8715     }
8716     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8717     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8718     // now arrange restart after book miss
8719     if(bookHit) {
8720         // after a book hit we never send 'go', and the code after the call to this routine
8721         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8722         char buf[MSG_SIZ], *move = bookHit;
8723         if(cps->useSAN) {
8724             int fromX, fromY, toX, toY;
8725             char promoChar;
8726             ChessMove moveType;
8727             move = buf + 30;
8728             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8729                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8730                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8731                                     PosFlags(forwardMostMove),
8732                                     fromY, fromX, toY, toX, promoChar, move);
8733             } else {
8734                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8735                 bookHit = NULL;
8736             }
8737         }
8738         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8739         SendToProgram(buf, cps);
8740         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8741     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8742         SendToProgram("go\n", cps);
8743         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8744     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8745         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8746             SendToProgram("go\n", cps);
8747         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8748     }
8749     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8750 }
8751
8752 int
8753 LoadError (char *errmess, ChessProgramState *cps)
8754 {   // unloads engine and switches back to -ncp mode if it was first
8755     if(cps->initDone) return FALSE;
8756     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8757     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8758     cps->pr = NoProc;
8759     if(cps == &first) {
8760         appData.noChessProgram = TRUE;
8761         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8762         gameMode = BeginningOfGame; ModeHighlight();
8763         SetNCPMode();
8764     }
8765     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8766     DisplayMessage("", ""); // erase waiting message
8767     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8768     return TRUE;
8769 }
8770
8771 char *savedMessage;
8772 ChessProgramState *savedState;
8773 void
8774 DeferredBookMove (void)
8775 {
8776         if(savedState->lastPing != savedState->lastPong)
8777                     ScheduleDelayedEvent(DeferredBookMove, 10);
8778         else
8779         HandleMachineMove(savedMessage, savedState);
8780 }
8781
8782 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8783 static ChessProgramState *stalledEngine;
8784 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8785
8786 void
8787 HandleMachineMove (char *message, ChessProgramState *cps)
8788 {
8789     static char firstLeg[20], legs;
8790     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8791     char realname[MSG_SIZ];
8792     int fromX, fromY, toX, toY;
8793     ChessMove moveType;
8794     char promoChar, roar;
8795     char *p, *pv=buf1;
8796     int oldError;
8797     char *bookHit;
8798
8799     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8800         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8801         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8802             DisplayError(_("Invalid pairing from pairing engine"), 0);
8803             return;
8804         }
8805         pairingReceived = 1;
8806         NextMatchGame();
8807         return; // Skim the pairing messages here.
8808     }
8809
8810     oldError = cps->userError; cps->userError = 0;
8811
8812 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8813     /*
8814      * Kludge to ignore BEL characters
8815      */
8816     while (*message == '\007') message++;
8817
8818     /*
8819      * [HGM] engine debug message: ignore lines starting with '#' character
8820      */
8821     if(cps->debug && *message == '#') return;
8822
8823     /*
8824      * Look for book output
8825      */
8826     if (cps == &first && bookRequested) {
8827         if (message[0] == '\t' || message[0] == ' ') {
8828             /* Part of the book output is here; append it */
8829             strcat(bookOutput, message);
8830             strcat(bookOutput, "  \n");
8831             return;
8832         } else if (bookOutput[0] != NULLCHAR) {
8833             /* All of book output has arrived; display it */
8834             char *p = bookOutput;
8835             while (*p != NULLCHAR) {
8836                 if (*p == '\t') *p = ' ';
8837                 p++;
8838             }
8839             DisplayInformation(bookOutput);
8840             bookRequested = FALSE;
8841             /* Fall through to parse the current output */
8842         }
8843     }
8844
8845     /*
8846      * Look for machine move.
8847      */
8848     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8849         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8850     {
8851         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8852             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8853             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8854             stalledEngine = cps;
8855             if(appData.ponderNextMove) { // bring opponent out of ponder
8856                 if(gameMode == TwoMachinesPlay) {
8857                     if(cps->other->pause)
8858                         PauseEngine(cps->other);
8859                     else
8860                         SendToProgram("easy\n", cps->other);
8861                 }
8862             }
8863             StopClocks();
8864             return;
8865         }
8866
8867       if(cps->usePing) {
8868
8869         /* This method is only useful on engines that support ping */
8870         if(abortEngineThink) {
8871             if (appData.debugMode) {
8872                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8873             }
8874             SendToProgram("undo\n", cps);
8875             return;
8876         }
8877
8878         if (cps->lastPing != cps->lastPong) {
8879             /* Extra move from before last new; ignore */
8880             if (appData.debugMode) {
8881                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8882             }
8883           return;
8884         }
8885
8886       } else {
8887
8888         int machineWhite = FALSE;
8889
8890         switch (gameMode) {
8891           case BeginningOfGame:
8892             /* Extra move from before last reset; ignore */
8893             if (appData.debugMode) {
8894                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8895             }
8896             return;
8897
8898           case EndOfGame:
8899           case IcsIdle:
8900           default:
8901             /* Extra move after we tried to stop.  The mode test is
8902                not a reliable way of detecting this problem, but it's
8903                the best we can do on engines that don't support ping.
8904             */
8905             if (appData.debugMode) {
8906                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8907                         cps->which, gameMode);
8908             }
8909             SendToProgram("undo\n", cps);
8910             return;
8911
8912           case MachinePlaysWhite:
8913           case IcsPlayingWhite:
8914             machineWhite = TRUE;
8915             break;
8916
8917           case MachinePlaysBlack:
8918           case IcsPlayingBlack:
8919             machineWhite = FALSE;
8920             break;
8921
8922           case TwoMachinesPlay:
8923             machineWhite = (cps->twoMachinesColor[0] == 'w');
8924             break;
8925         }
8926         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8927             if (appData.debugMode) {
8928                 fprintf(debugFP,
8929                         "Ignoring move out of turn by %s, gameMode %d"
8930                         ", forwardMost %d\n",
8931                         cps->which, gameMode, forwardMostMove);
8932             }
8933             return;
8934         }
8935       }
8936
8937         if(cps->alphaRank) AlphaRank(machineMove, 4);
8938
8939         // [HGM] lion: (some very limited) support for Alien protocol
8940         killX = killY = kill2X = kill2Y = -1;
8941         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8942             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8943             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8944             return;
8945         }
8946         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8947             char *q = strchr(p+1, ',');            // second comma?
8948             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8949             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8950             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8951         }
8952         if(firstLeg[0]) { // there was a previous leg;
8953             // only support case where same piece makes two step
8954             char buf[20], *p = machineMove+1, *q = buf+1, f;
8955             safeStrCpy(buf, machineMove, 20);
8956             while(isdigit(*q)) q++; // find start of to-square
8957             safeStrCpy(machineMove, firstLeg, 20);
8958             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8959             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
8960             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)
8961             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8962             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8963             firstLeg[0] = NULLCHAR; legs = 0;
8964         }
8965
8966         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8967                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8968             /* Machine move could not be parsed; ignore it. */
8969           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8970                     machineMove, _(cps->which));
8971             DisplayMoveError(buf1);
8972             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8973                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8974             if (gameMode == TwoMachinesPlay) {
8975               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8976                        buf1, GE_XBOARD);
8977             }
8978             return;
8979         }
8980
8981         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8982         /* So we have to redo legality test with true e.p. status here,  */
8983         /* to make sure an illegal e.p. capture does not slip through,   */
8984         /* to cause a forfeit on a justified illegal-move complaint      */
8985         /* of the opponent.                                              */
8986         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8987            ChessMove moveType;
8988            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8989                              fromY, fromX, toY, toX, promoChar);
8990             if(moveType == IllegalMove) {
8991               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8992                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8993                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8994                            buf1, GE_XBOARD);
8995                 return;
8996            } else if(!appData.fischerCastling)
8997            /* [HGM] Kludge to handle engines that send FRC-style castling
8998               when they shouldn't (like TSCP-Gothic) */
8999            switch(moveType) {
9000              case WhiteASideCastleFR:
9001              case BlackASideCastleFR:
9002                toX+=2;
9003                currentMoveString[2]++;
9004                break;
9005              case WhiteHSideCastleFR:
9006              case BlackHSideCastleFR:
9007                toX--;
9008                currentMoveString[2]--;
9009                break;
9010              default: ; // nothing to do, but suppresses warning of pedantic compilers
9011            }
9012         }
9013         hintRequested = FALSE;
9014         lastHint[0] = NULLCHAR;
9015         bookRequested = FALSE;
9016         /* Program may be pondering now */
9017         cps->maybeThinking = TRUE;
9018         if (cps->sendTime == 2) cps->sendTime = 1;
9019         if (cps->offeredDraw) cps->offeredDraw--;
9020
9021         /* [AS] Save move info*/
9022         pvInfoList[ forwardMostMove ].score = programStats.score;
9023         pvInfoList[ forwardMostMove ].depth = programStats.depth;
9024         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
9025
9026         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
9027
9028         /* Test suites abort the 'game' after one move */
9029         if(*appData.finger) {
9030            static FILE *f;
9031            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
9032            if(!f) f = fopen(appData.finger, "w");
9033            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
9034            else { DisplayFatalError("Bad output file", errno, 0); return; }
9035            free(fen);
9036            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9037         }
9038         if(appData.epd) {
9039            if(solvingTime >= 0) {
9040               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
9041               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
9042            } else {
9043               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
9044               if(solvingTime == -2) second.matchWins++;
9045            }
9046            OutputKibitz(2, buf1);
9047            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9048         }
9049
9050         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9051         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9052             int count = 0;
9053
9054             while( count < adjudicateLossPlies ) {
9055                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9056
9057                 if( count & 1 ) {
9058                     score = -score; /* Flip score for winning side */
9059                 }
9060
9061                 if( score > appData.adjudicateLossThreshold ) {
9062                     break;
9063                 }
9064
9065                 count++;
9066             }
9067
9068             if( count >= adjudicateLossPlies ) {
9069                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9070
9071                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9072                     "Xboard adjudication",
9073                     GE_XBOARD );
9074
9075                 return;
9076             }
9077         }
9078
9079         if(Adjudicate(cps)) {
9080             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9081             return; // [HGM] adjudicate: for all automatic game ends
9082         }
9083
9084 #if ZIPPY
9085         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9086             first.initDone) {
9087           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9088                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9089                 SendToICS("draw ");
9090                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9091           }
9092           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9093           ics_user_moved = 1;
9094           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9095                 char buf[3*MSG_SIZ];
9096
9097                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9098                         programStats.score / 100.,
9099                         programStats.depth,
9100                         programStats.time / 100.,
9101                         (unsigned int)programStats.nodes,
9102                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9103                         programStats.movelist);
9104                 SendToICS(buf);
9105           }
9106         }
9107 #endif
9108
9109         /* [AS] Clear stats for next move */
9110         ClearProgramStats();
9111         thinkOutput[0] = NULLCHAR;
9112         hiddenThinkOutputState = 0;
9113
9114         bookHit = NULL;
9115         if (gameMode == TwoMachinesPlay) {
9116             /* [HGM] relaying draw offers moved to after reception of move */
9117             /* and interpreting offer as claim if it brings draw condition */
9118             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9119                 SendToProgram("draw\n", cps->other);
9120             }
9121             if (cps->other->sendTime) {
9122                 SendTimeRemaining(cps->other,
9123                                   cps->other->twoMachinesColor[0] == 'w');
9124             }
9125             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9126             if (firstMove && !bookHit) {
9127                 firstMove = FALSE;
9128                 if (cps->other->useColors) {
9129                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9130                 }
9131                 SendToProgram("go\n", cps->other);
9132             }
9133             cps->other->maybeThinking = TRUE;
9134         }
9135
9136         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9137
9138         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9139
9140         if (!pausing && appData.ringBellAfterMoves) {
9141             if(!roar) RingBell();
9142         }
9143
9144         /*
9145          * Reenable menu items that were disabled while
9146          * machine was thinking
9147          */
9148         if (gameMode != TwoMachinesPlay)
9149             SetUserThinkingEnables();
9150
9151         // [HGM] book: after book hit opponent has received move and is now in force mode
9152         // force the book reply into it, and then fake that it outputted this move by jumping
9153         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9154         if(bookHit) {
9155                 static char bookMove[MSG_SIZ]; // a bit generous?
9156
9157                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9158                 strcat(bookMove, bookHit);
9159                 message = bookMove;
9160                 cps = cps->other;
9161                 programStats.nodes = programStats.depth = programStats.time =
9162                 programStats.score = programStats.got_only_move = 0;
9163                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9164
9165                 if(cps->lastPing != cps->lastPong) {
9166                     savedMessage = message; // args for deferred call
9167                     savedState = cps;
9168                     ScheduleDelayedEvent(DeferredBookMove, 10);
9169                     return;
9170                 }
9171                 goto FakeBookMove;
9172         }
9173
9174         return;
9175     }
9176
9177     /* Set special modes for chess engines.  Later something general
9178      *  could be added here; for now there is just one kludge feature,
9179      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9180      *  when "xboard" is given as an interactive command.
9181      */
9182     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9183         cps->useSigint = FALSE;
9184         cps->useSigterm = FALSE;
9185     }
9186     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9187       ParseFeatures(message+8, cps);
9188       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9189     }
9190
9191     if (!strncmp(message, "setup ", 6) && 
9192         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9193           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9194                                         ) { // [HGM] allow first engine to define opening position
9195       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9196       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9197       *buf = NULLCHAR;
9198       if(sscanf(message, "setup (%s", buf) == 1) {
9199         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9200         ASSIGN(appData.pieceToCharTable, buf);
9201       }
9202       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9203       if(dummy >= 3) {
9204         while(message[s] && message[s++] != ' ');
9205         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9206            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9207 //          if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9208             if(hand > h) handSize = hand; else handSize = h;
9209             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9210             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9211           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9212           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9213           startedFromSetupPosition = FALSE;
9214         }
9215       }
9216       if(startedFromSetupPosition) return;
9217       ParseFEN(boards[0], &dummy, message+s, FALSE);
9218       DrawPosition(TRUE, boards[0]);
9219       CopyBoard(initialPosition, boards[0]);
9220       startedFromSetupPosition = TRUE;
9221       return;
9222     }
9223     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9224       ChessSquare piece = WhitePawn;
9225       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9226       if(*p == '+') promoted++, ID = *++p;
9227       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9228       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9229       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9230       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9231       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9232       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9233       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9234       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9235                                                && gameInfo.variant != VariantGreat
9236                                                && gameInfo.variant != VariantFairy    ) return;
9237       if(piece < EmptySquare) {
9238         pieceDefs = TRUE;
9239         ASSIGN(pieceDesc[piece], buf1);
9240         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9241       }
9242       return;
9243     }
9244     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9245       if(deferChoice) {
9246         LeftClick(Press, 0, 0); // finish the click that was interrupted
9247       } else if(promoSweep != EmptySquare) {
9248         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9249         if(strlen(promoRestrict) > 1) Sweep(0);
9250       }
9251       return;
9252     }
9253     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9254      * want this, I was asked to put it in, and obliged.
9255      */
9256     if (!strncmp(message, "setboard ", 9)) {
9257         Board initial_position;
9258
9259         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9260
9261         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9262             DisplayError(_("Bad FEN received from engine"), 0);
9263             return ;
9264         } else {
9265            Reset(TRUE, FALSE);
9266            CopyBoard(boards[0], initial_position);
9267            initialRulePlies = FENrulePlies;
9268            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9269            else gameMode = MachinePlaysBlack;
9270            DrawPosition(FALSE, boards[currentMove]);
9271         }
9272         return;
9273     }
9274
9275     /*
9276      * Look for communication commands
9277      */
9278     if (!strncmp(message, "telluser ", 9)) {
9279         if(message[9] == '\\' && message[10] == '\\')
9280             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9281         PlayTellSound();
9282         DisplayNote(message + 9);
9283         return;
9284     }
9285     if (!strncmp(message, "tellusererror ", 14)) {
9286         cps->userError = 1;
9287         if(message[14] == '\\' && message[15] == '\\')
9288             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9289         PlayTellSound();
9290         DisplayError(message + 14, 0);
9291         return;
9292     }
9293     if (!strncmp(message, "tellopponent ", 13)) {
9294       if (appData.icsActive) {
9295         if (loggedOn) {
9296           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9297           SendToICS(buf1);
9298         }
9299       } else {
9300         DisplayNote(message + 13);
9301       }
9302       return;
9303     }
9304     if (!strncmp(message, "tellothers ", 11)) {
9305       if (appData.icsActive) {
9306         if (loggedOn) {
9307           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9308           SendToICS(buf1);
9309         }
9310       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9311       return;
9312     }
9313     if (!strncmp(message, "tellall ", 8)) {
9314       if (appData.icsActive) {
9315         if (loggedOn) {
9316           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9317           SendToICS(buf1);
9318         }
9319       } else {
9320         DisplayNote(message + 8);
9321       }
9322       return;
9323     }
9324     if (strncmp(message, "warning", 7) == 0) {
9325         /* Undocumented feature, use tellusererror in new code */
9326         DisplayError(message, 0);
9327         return;
9328     }
9329     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9330         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9331         strcat(realname, " query");
9332         AskQuestion(realname, buf2, buf1, cps->pr);
9333         return;
9334     }
9335     /* Commands from the engine directly to ICS.  We don't allow these to be
9336      *  sent until we are logged on. Crafty kibitzes have been known to
9337      *  interfere with the login process.
9338      */
9339     if (loggedOn) {
9340         if (!strncmp(message, "tellics ", 8)) {
9341             SendToICS(message + 8);
9342             SendToICS("\n");
9343             return;
9344         }
9345         if (!strncmp(message, "tellicsnoalias ", 15)) {
9346             SendToICS(ics_prefix);
9347             SendToICS(message + 15);
9348             SendToICS("\n");
9349             return;
9350         }
9351         /* The following are for backward compatibility only */
9352         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9353             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9354             SendToICS(ics_prefix);
9355             SendToICS(message);
9356             SendToICS("\n");
9357             return;
9358         }
9359     }
9360     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9361         if(initPing == cps->lastPong) {
9362             if(gameInfo.variant == VariantUnknown) {
9363                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9364                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9365                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9366             }
9367             initPing = -1;
9368         }
9369         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9370             abortEngineThink = FALSE;
9371             DisplayMessage("", "");
9372             ThawUI();
9373         }
9374         return;
9375     }
9376     if(!strncmp(message, "highlight ", 10)) {
9377         if(appData.testLegality && !*engineVariant && appData.markers) return;
9378         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9379         return;
9380     }
9381     if(!strncmp(message, "click ", 6)) {
9382         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9383         if(appData.testLegality || !appData.oneClick) return;
9384         sscanf(message+6, "%c%d%c", &f, &y, &c);
9385         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9386         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9387         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9388         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9389         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9390         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9391             LeftClick(Release, lastLeftX, lastLeftY);
9392         controlKey  = (c == ',');
9393         LeftClick(Press, x, y);
9394         LeftClick(Release, x, y);
9395         first.highlight = f;
9396         return;
9397     }
9398     /*
9399      * If the move is illegal, cancel it and redraw the board.
9400      * Also deal with other error cases.  Matching is rather loose
9401      * here to accommodate engines written before the spec.
9402      */
9403     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9404         strncmp(message, "Error", 5) == 0) {
9405         if (StrStr(message, "name") ||
9406             StrStr(message, "rating") || StrStr(message, "?") ||
9407             StrStr(message, "result") || StrStr(message, "board") ||
9408             StrStr(message, "bk") || StrStr(message, "computer") ||
9409             StrStr(message, "variant") || StrStr(message, "hint") ||
9410             StrStr(message, "random") || StrStr(message, "depth") ||
9411             StrStr(message, "accepted")) {
9412             return;
9413         }
9414         if (StrStr(message, "protover")) {
9415           /* Program is responding to input, so it's apparently done
9416              initializing, and this error message indicates it is
9417              protocol version 1.  So we don't need to wait any longer
9418              for it to initialize and send feature commands. */
9419           FeatureDone(cps, 1);
9420           cps->protocolVersion = 1;
9421           return;
9422         }
9423         cps->maybeThinking = FALSE;
9424
9425         if (StrStr(message, "draw")) {
9426             /* Program doesn't have "draw" command */
9427             cps->sendDrawOffers = 0;
9428             return;
9429         }
9430         if (cps->sendTime != 1 &&
9431             (StrStr(message, "time") || StrStr(message, "otim"))) {
9432           /* Program apparently doesn't have "time" or "otim" command */
9433           cps->sendTime = 0;
9434           return;
9435         }
9436         if (StrStr(message, "analyze")) {
9437             cps->analysisSupport = FALSE;
9438             cps->analyzing = FALSE;
9439 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9440             EditGameEvent(); // [HGM] try to preserve loaded game
9441             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9442             DisplayError(buf2, 0);
9443             return;
9444         }
9445         if (StrStr(message, "(no matching move)st")) {
9446           /* Special kludge for GNU Chess 4 only */
9447           cps->stKludge = TRUE;
9448           SendTimeControl(cps, movesPerSession, timeControl,
9449                           timeIncrement, appData.searchDepth,
9450                           searchTime);
9451           return;
9452         }
9453         if (StrStr(message, "(no matching move)sd")) {
9454           /* Special kludge for GNU Chess 4 only */
9455           cps->sdKludge = TRUE;
9456           SendTimeControl(cps, movesPerSession, timeControl,
9457                           timeIncrement, appData.searchDepth,
9458                           searchTime);
9459           return;
9460         }
9461         if (!StrStr(message, "llegal")) {
9462             return;
9463         }
9464         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9465             gameMode == IcsIdle) return;
9466         if (forwardMostMove <= backwardMostMove) return;
9467         if (pausing) PauseEvent();
9468       if(appData.forceIllegal) {
9469             // [HGM] illegal: machine refused move; force position after move into it
9470           SendToProgram("force\n", cps);
9471           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9472                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9473                 // when black is to move, while there might be nothing on a2 or black
9474                 // might already have the move. So send the board as if white has the move.
9475                 // But first we must change the stm of the engine, as it refused the last move
9476                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9477                 if(WhiteOnMove(forwardMostMove)) {
9478                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9479                     SendBoard(cps, forwardMostMove); // kludgeless board
9480                 } else {
9481                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9482                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9483                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9484                 }
9485           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9486             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9487                  gameMode == TwoMachinesPlay)
9488               SendToProgram("go\n", cps);
9489             return;
9490       } else
9491         if (gameMode == PlayFromGameFile) {
9492             /* Stop reading this game file */
9493             gameMode = EditGame;
9494             ModeHighlight();
9495         }
9496         /* [HGM] illegal-move claim should forfeit game when Xboard */
9497         /* only passes fully legal moves                            */
9498         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9499             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9500                                 "False illegal-move claim", GE_XBOARD );
9501             return; // do not take back move we tested as valid
9502         }
9503         currentMove = forwardMostMove-1;
9504         DisplayMove(currentMove-1); /* before DisplayMoveError */
9505         SwitchClocks(forwardMostMove-1); // [HGM] race
9506         DisplayBothClocks();
9507         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9508                 parseList[currentMove], _(cps->which));
9509         DisplayMoveError(buf1);
9510         DrawPosition(FALSE, boards[currentMove]);
9511
9512         SetUserThinkingEnables();
9513         return;
9514     }
9515     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9516         /* Program has a broken "time" command that
9517            outputs a string not ending in newline.
9518            Don't use it. */
9519         cps->sendTime = 0;
9520     }
9521     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9522         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9523             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9524     }
9525
9526     /*
9527      * If chess program startup fails, exit with an error message.
9528      * Attempts to recover here are futile. [HGM] Well, we try anyway
9529      */
9530     if ((StrStr(message, "unknown host") != NULL)
9531         || (StrStr(message, "No remote directory") != NULL)
9532         || (StrStr(message, "not found") != NULL)
9533         || (StrStr(message, "No such file") != NULL)
9534         || (StrStr(message, "can't alloc") != NULL)
9535         || (StrStr(message, "Permission denied") != NULL)) {
9536
9537         cps->maybeThinking = FALSE;
9538         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9539                 _(cps->which), cps->program, cps->host, message);
9540         RemoveInputSource(cps->isr);
9541         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9542             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9543             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9544         }
9545         return;
9546     }
9547
9548     /*
9549      * Look for hint output
9550      */
9551     if (sscanf(message, "Hint: %s", buf1) == 1) {
9552         if (cps == &first && hintRequested) {
9553             hintRequested = FALSE;
9554             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9555                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9556                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9557                                     PosFlags(forwardMostMove),
9558                                     fromY, fromX, toY, toX, promoChar, buf1);
9559                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9560                 DisplayInformation(buf2);
9561             } else {
9562                 /* Hint move could not be parsed!? */
9563               snprintf(buf2, sizeof(buf2),
9564                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9565                         buf1, _(cps->which));
9566                 DisplayError(buf2, 0);
9567             }
9568         } else {
9569           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9570         }
9571         return;
9572     }
9573
9574     /*
9575      * Ignore other messages if game is not in progress
9576      */
9577     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9578         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9579
9580     /*
9581      * look for win, lose, draw, or draw offer
9582      */
9583     if (strncmp(message, "1-0", 3) == 0) {
9584         char *p, *q, *r = "";
9585         p = strchr(message, '{');
9586         if (p) {
9587             q = strchr(p, '}');
9588             if (q) {
9589                 *q = NULLCHAR;
9590                 r = p + 1;
9591             }
9592         }
9593         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9594         return;
9595     } else if (strncmp(message, "0-1", 3) == 0) {
9596         char *p, *q, *r = "";
9597         p = strchr(message, '{');
9598         if (p) {
9599             q = strchr(p, '}');
9600             if (q) {
9601                 *q = NULLCHAR;
9602                 r = p + 1;
9603             }
9604         }
9605         /* Kludge for Arasan 4.1 bug */
9606         if (strcmp(r, "Black resigns") == 0) {
9607             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9608             return;
9609         }
9610         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9611         return;
9612     } else if (strncmp(message, "1/2", 3) == 0) {
9613         char *p, *q, *r = "";
9614         p = strchr(message, '{');
9615         if (p) {
9616             q = strchr(p, '}');
9617             if (q) {
9618                 *q = NULLCHAR;
9619                 r = p + 1;
9620             }
9621         }
9622
9623         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9624         return;
9625
9626     } else if (strncmp(message, "White resign", 12) == 0) {
9627         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9628         return;
9629     } else if (strncmp(message, "Black resign", 12) == 0) {
9630         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9631         return;
9632     } else if (strncmp(message, "White matches", 13) == 0 ||
9633                strncmp(message, "Black matches", 13) == 0   ) {
9634         /* [HGM] ignore GNUShogi noises */
9635         return;
9636     } else if (strncmp(message, "White", 5) == 0 &&
9637                message[5] != '(' &&
9638                StrStr(message, "Black") == NULL) {
9639         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9640         return;
9641     } else if (strncmp(message, "Black", 5) == 0 &&
9642                message[5] != '(') {
9643         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9644         return;
9645     } else if (strcmp(message, "resign") == 0 ||
9646                strcmp(message, "computer resigns") == 0) {
9647         switch (gameMode) {
9648           case MachinePlaysBlack:
9649           case IcsPlayingBlack:
9650             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9651             break;
9652           case MachinePlaysWhite:
9653           case IcsPlayingWhite:
9654             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9655             break;
9656           case TwoMachinesPlay:
9657             if (cps->twoMachinesColor[0] == 'w')
9658               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9659             else
9660               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9661             break;
9662           default:
9663             /* can't happen */
9664             break;
9665         }
9666         return;
9667     } else if (strncmp(message, "opponent mates", 14) == 0) {
9668         switch (gameMode) {
9669           case MachinePlaysBlack:
9670           case IcsPlayingBlack:
9671             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9672             break;
9673           case MachinePlaysWhite:
9674           case IcsPlayingWhite:
9675             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9676             break;
9677           case TwoMachinesPlay:
9678             if (cps->twoMachinesColor[0] == 'w')
9679               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9680             else
9681               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9682             break;
9683           default:
9684             /* can't happen */
9685             break;
9686         }
9687         return;
9688     } else if (strncmp(message, "computer mates", 14) == 0) {
9689         switch (gameMode) {
9690           case MachinePlaysBlack:
9691           case IcsPlayingBlack:
9692             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9693             break;
9694           case MachinePlaysWhite:
9695           case IcsPlayingWhite:
9696             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9697             break;
9698           case TwoMachinesPlay:
9699             if (cps->twoMachinesColor[0] == 'w')
9700               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9701             else
9702               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9703             break;
9704           default:
9705             /* can't happen */
9706             break;
9707         }
9708         return;
9709     } else if (strncmp(message, "checkmate", 9) == 0) {
9710         if (WhiteOnMove(forwardMostMove)) {
9711             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9712         } else {
9713             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9714         }
9715         return;
9716     } else if (strstr(message, "Draw") != NULL ||
9717                strstr(message, "game is a draw") != NULL) {
9718         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9719         return;
9720     } else if (strstr(message, "offer") != NULL &&
9721                strstr(message, "draw") != NULL) {
9722 #if ZIPPY
9723         if (appData.zippyPlay && first.initDone) {
9724             /* Relay offer to ICS */
9725             SendToICS(ics_prefix);
9726             SendToICS("draw\n");
9727         }
9728 #endif
9729         cps->offeredDraw = 2; /* valid until this engine moves twice */
9730         if (gameMode == TwoMachinesPlay) {
9731             if (cps->other->offeredDraw) {
9732                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9733             /* [HGM] in two-machine mode we delay relaying draw offer      */
9734             /* until after we also have move, to see if it is really claim */
9735             }
9736         } else if (gameMode == MachinePlaysWhite ||
9737                    gameMode == MachinePlaysBlack) {
9738           if (userOfferedDraw) {
9739             DisplayInformation(_("Machine accepts your draw offer"));
9740             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9741           } else {
9742             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9743           }
9744         }
9745     }
9746
9747
9748     /*
9749      * Look for thinking output
9750      */
9751     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9752           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9753                                 ) {
9754         int plylev, mvleft, mvtot, curscore, time;
9755         char mvname[MOVE_LEN];
9756         u64 nodes; // [DM]
9757         char plyext;
9758         int ignore = FALSE;
9759         int prefixHint = FALSE;
9760         mvname[0] = NULLCHAR;
9761
9762         switch (gameMode) {
9763           case MachinePlaysBlack:
9764           case IcsPlayingBlack:
9765             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9766             break;
9767           case MachinePlaysWhite:
9768           case IcsPlayingWhite:
9769             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9770             break;
9771           case AnalyzeMode:
9772           case AnalyzeFile:
9773             break;
9774           case IcsObserving: /* [DM] icsEngineAnalyze */
9775             if (!appData.icsEngineAnalyze) ignore = TRUE;
9776             break;
9777           case TwoMachinesPlay:
9778             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9779                 ignore = TRUE;
9780             }
9781             break;
9782           default:
9783             ignore = TRUE;
9784             break;
9785         }
9786
9787         if (!ignore) {
9788             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9789             int solved = 0;
9790             buf1[0] = NULLCHAR;
9791             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9792                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9793                 char score_buf[MSG_SIZ];
9794
9795                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9796                     nodes += u64Const(0x100000000);
9797
9798                 if (plyext != ' ' && plyext != '\t') {
9799                     time *= 100;
9800                 }
9801
9802                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9803                 if( cps->scoreIsAbsolute &&
9804                     ( gameMode == MachinePlaysBlack ||
9805                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9806                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9807                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9808                      !WhiteOnMove(currentMove)
9809                     ) )
9810                 {
9811                     curscore = -curscore;
9812                 }
9813
9814                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9815
9816                 if(*bestMove) { // rememer time best EPD move was first found
9817                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9818                     ChessMove mt; char *p = bestMove;
9819                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9820                     solved = 0;
9821                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9822                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9823                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9824                             solved = 1;
9825                             break;
9826                         }
9827                         while(*p && *p != ' ') p++;
9828                         while(*p == ' ') p++;
9829                     }
9830                     if(!solved) solvingTime = -1;
9831                 }
9832                 if(*avoidMove && !solved) {
9833                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9834                     ChessMove mt; char *p = avoidMove, solved = 1;
9835                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9836                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9837                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9838                             solved = 0; solvingTime = -2;
9839                             break;
9840                         }
9841                         while(*p && *p != ' ') p++;
9842                         while(*p == ' ') p++;
9843                     }
9844                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9845                 }
9846
9847                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9848                         char buf[MSG_SIZ];
9849                         FILE *f;
9850                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9851                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9852                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9853                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9854                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9855                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9856                                 fclose(f);
9857                         }
9858                         else
9859                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9860                           DisplayError(_("failed writing PV"), 0);
9861                 }
9862
9863                 tempStats.depth = plylev;
9864                 tempStats.nodes = nodes;
9865                 tempStats.time = time;
9866                 tempStats.score = curscore;
9867                 tempStats.got_only_move = 0;
9868
9869                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9870                         int ticklen;
9871
9872                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9873                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9874                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9875                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9876                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9877                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9878                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9879                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9880                 }
9881
9882                 /* Buffer overflow protection */
9883                 if (pv[0] != NULLCHAR) {
9884                     if (strlen(pv) >= sizeof(tempStats.movelist)
9885                         && appData.debugMode) {
9886                         fprintf(debugFP,
9887                                 "PV is too long; using the first %u bytes.\n",
9888                                 (unsigned) sizeof(tempStats.movelist) - 1);
9889                     }
9890
9891                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9892                 } else {
9893                     sprintf(tempStats.movelist, " no PV\n");
9894                 }
9895
9896                 if (tempStats.seen_stat) {
9897                     tempStats.ok_to_send = 1;
9898                 }
9899
9900                 if (strchr(tempStats.movelist, '(') != NULL) {
9901                     tempStats.line_is_book = 1;
9902                     tempStats.nr_moves = 0;
9903                     tempStats.moves_left = 0;
9904                 } else {
9905                     tempStats.line_is_book = 0;
9906                 }
9907
9908                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9909                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9910
9911                 SendProgramStatsToFrontend( cps, &tempStats );
9912
9913                 /*
9914                     [AS] Protect the thinkOutput buffer from overflow... this
9915                     is only useful if buf1 hasn't overflowed first!
9916                 */
9917                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9918                 if(curscore >= MATE_SCORE) 
9919                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9920                 else if(curscore <= -MATE_SCORE) 
9921                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9922                 else
9923                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9924                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9925                          plylev,
9926                          (gameMode == TwoMachinesPlay ?
9927                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9928                          score_buf,
9929                          prefixHint ? lastHint : "",
9930                          prefixHint ? " " : "" );
9931
9932                 if( buf1[0] != NULLCHAR ) {
9933                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9934
9935                     if( strlen(pv) > max_len ) {
9936                         if( appData.debugMode) {
9937                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9938                         }
9939                         pv[max_len+1] = '\0';
9940                     }
9941
9942                     strcat( thinkOutput, pv);
9943                 }
9944
9945                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9946                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9947                     DisplayMove(currentMove - 1);
9948                 }
9949                 return;
9950
9951             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9952                 /* crafty (9.25+) says "(only move) <move>"
9953                  * if there is only 1 legal move
9954                  */
9955                 sscanf(p, "(only move) %s", buf1);
9956                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9957                 sprintf(programStats.movelist, "%s (only move)", buf1);
9958                 programStats.depth = 1;
9959                 programStats.nr_moves = 1;
9960                 programStats.moves_left = 1;
9961                 programStats.nodes = 1;
9962                 programStats.time = 1;
9963                 programStats.got_only_move = 1;
9964
9965                 /* Not really, but we also use this member to
9966                    mean "line isn't going to change" (Crafty
9967                    isn't searching, so stats won't change) */
9968                 programStats.line_is_book = 1;
9969
9970                 SendProgramStatsToFrontend( cps, &programStats );
9971
9972                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9973                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9974                     DisplayMove(currentMove - 1);
9975                 }
9976                 return;
9977             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9978                               &time, &nodes, &plylev, &mvleft,
9979                               &mvtot, mvname) >= 5) {
9980                 /* The stat01: line is from Crafty (9.29+) in response
9981                    to the "." command */
9982                 programStats.seen_stat = 1;
9983                 cps->maybeThinking = TRUE;
9984
9985                 if (programStats.got_only_move || !appData.periodicUpdates)
9986                   return;
9987
9988                 programStats.depth = plylev;
9989                 programStats.time = time;
9990                 programStats.nodes = nodes;
9991                 programStats.moves_left = mvleft;
9992                 programStats.nr_moves = mvtot;
9993                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9994                 programStats.ok_to_send = 1;
9995                 programStats.movelist[0] = '\0';
9996
9997                 SendProgramStatsToFrontend( cps, &programStats );
9998
9999                 return;
10000
10001             } else if (strncmp(message,"++",2) == 0) {
10002                 /* Crafty 9.29+ outputs this */
10003                 programStats.got_fail = 2;
10004                 return;
10005
10006             } else if (strncmp(message,"--",2) == 0) {
10007                 /* Crafty 9.29+ outputs this */
10008                 programStats.got_fail = 1;
10009                 return;
10010
10011             } else if (thinkOutput[0] != NULLCHAR &&
10012                        strncmp(message, "    ", 4) == 0) {
10013                 unsigned message_len;
10014
10015                 p = message;
10016                 while (*p && *p == ' ') p++;
10017
10018                 message_len = strlen( p );
10019
10020                 /* [AS] Avoid buffer overflow */
10021                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
10022                     strcat(thinkOutput, " ");
10023                     strcat(thinkOutput, p);
10024                 }
10025
10026                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
10027                     strcat(programStats.movelist, " ");
10028                     strcat(programStats.movelist, p);
10029                 }
10030
10031                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10032                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10033                     DisplayMove(currentMove - 1);
10034                 }
10035                 return;
10036             }
10037         }
10038         else {
10039             buf1[0] = NULLCHAR;
10040
10041             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
10042                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
10043             {
10044                 ChessProgramStats cpstats;
10045
10046                 if (plyext != ' ' && plyext != '\t') {
10047                     time *= 100;
10048                 }
10049
10050                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10051                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10052                     curscore = -curscore;
10053                 }
10054
10055                 cpstats.depth = plylev;
10056                 cpstats.nodes = nodes;
10057                 cpstats.time = time;
10058                 cpstats.score = curscore;
10059                 cpstats.got_only_move = 0;
10060                 cpstats.movelist[0] = '\0';
10061
10062                 if (buf1[0] != NULLCHAR) {
10063                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10064                 }
10065
10066                 cpstats.ok_to_send = 0;
10067                 cpstats.line_is_book = 0;
10068                 cpstats.nr_moves = 0;
10069                 cpstats.moves_left = 0;
10070
10071                 SendProgramStatsToFrontend( cps, &cpstats );
10072             }
10073         }
10074     }
10075 }
10076
10077
10078 /* Parse a game score from the character string "game", and
10079    record it as the history of the current game.  The game
10080    score is NOT assumed to start from the standard position.
10081    The display is not updated in any way.
10082    */
10083 void
10084 ParseGameHistory (char *game)
10085 {
10086     ChessMove moveType;
10087     int fromX, fromY, toX, toY, boardIndex, mask;
10088     char promoChar;
10089     char *p, *q;
10090     char buf[MSG_SIZ];
10091
10092     if (appData.debugMode)
10093       fprintf(debugFP, "Parsing game history: %s\n", game);
10094
10095     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10096     gameInfo.site = StrSave(appData.icsHost);
10097     gameInfo.date = PGNDate();
10098     gameInfo.round = StrSave("-");
10099
10100     /* Parse out names of players */
10101     while (*game == ' ') game++;
10102     p = buf;
10103     while (*game != ' ') *p++ = *game++;
10104     *p = NULLCHAR;
10105     gameInfo.white = StrSave(buf);
10106     while (*game == ' ') game++;
10107     p = buf;
10108     while (*game != ' ' && *game != '\n') *p++ = *game++;
10109     *p = NULLCHAR;
10110     gameInfo.black = StrSave(buf);
10111
10112     /* Parse moves */
10113     boardIndex = blackPlaysFirst ? 1 : 0;
10114     yynewstr(game);
10115     for (;;) {
10116         yyboardindex = boardIndex;
10117         moveType = (ChessMove) Myylex();
10118         switch (moveType) {
10119           case IllegalMove:             /* maybe suicide chess, etc. */
10120   if (appData.debugMode) {
10121     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10122     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10123     setbuf(debugFP, NULL);
10124   }
10125           case WhitePromotion:
10126           case BlackPromotion:
10127           case WhiteNonPromotion:
10128           case BlackNonPromotion:
10129           case NormalMove:
10130           case FirstLeg:
10131           case WhiteCapturesEnPassant:
10132           case BlackCapturesEnPassant:
10133           case WhiteKingSideCastle:
10134           case WhiteQueenSideCastle:
10135           case BlackKingSideCastle:
10136           case BlackQueenSideCastle:
10137           case WhiteKingSideCastleWild:
10138           case WhiteQueenSideCastleWild:
10139           case BlackKingSideCastleWild:
10140           case BlackQueenSideCastleWild:
10141           /* PUSH Fabien */
10142           case WhiteHSideCastleFR:
10143           case WhiteASideCastleFR:
10144           case BlackHSideCastleFR:
10145           case BlackASideCastleFR:
10146           /* POP Fabien */
10147             fromX = currentMoveString[0] - AAA;
10148             fromY = currentMoveString[1] - ONE;
10149             toX = currentMoveString[2] - AAA;
10150             toY = currentMoveString[3] - ONE;
10151             promoChar = currentMoveString[4];
10152             break;
10153           case WhiteDrop:
10154           case BlackDrop:
10155             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10156             fromX = moveType == WhiteDrop ?
10157               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10158             (int) CharToPiece(ToLower(currentMoveString[0]));
10159             fromY = DROP_RANK;
10160             toX = currentMoveString[2] - AAA;
10161             toY = currentMoveString[3] - ONE;
10162             promoChar = NULLCHAR;
10163             break;
10164           case AmbiguousMove:
10165             /* bug? */
10166             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10167   if (appData.debugMode) {
10168     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10169     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10170     setbuf(debugFP, NULL);
10171   }
10172             DisplayError(buf, 0);
10173             return;
10174           case ImpossibleMove:
10175             /* bug? */
10176             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10177   if (appData.debugMode) {
10178     fprintf(debugFP, "Impossible 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 EndOfFile:
10185             if (boardIndex < backwardMostMove) {
10186                 /* Oops, gap.  How did that happen? */
10187                 DisplayError(_("Gap in move list"), 0);
10188                 return;
10189             }
10190             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10191             if (boardIndex > forwardMostMove) {
10192                 forwardMostMove = boardIndex;
10193             }
10194             return;
10195           case ElapsedTime:
10196             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10197                 strcat(parseList[boardIndex-1], " ");
10198                 strcat(parseList[boardIndex-1], yy_text);
10199             }
10200             continue;
10201           case Comment:
10202           case PGNTag:
10203           case NAG:
10204           default:
10205             /* ignore */
10206             continue;
10207           case WhiteWins:
10208           case BlackWins:
10209           case GameIsDrawn:
10210           case GameUnfinished:
10211             if (gameMode == IcsExamining) {
10212                 if (boardIndex < backwardMostMove) {
10213                     /* Oops, gap.  How did that happen? */
10214                     return;
10215                 }
10216                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10217                 return;
10218             }
10219             gameInfo.result = moveType;
10220             p = strchr(yy_text, '{');
10221             if (p == NULL) p = strchr(yy_text, '(');
10222             if (p == NULL) {
10223                 p = yy_text;
10224                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10225             } else {
10226                 q = strchr(p, *p == '{' ? '}' : ')');
10227                 if (q != NULL) *q = NULLCHAR;
10228                 p++;
10229             }
10230             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10231             gameInfo.resultDetails = StrSave(p);
10232             continue;
10233         }
10234         if (boardIndex >= forwardMostMove &&
10235             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10236             backwardMostMove = blackPlaysFirst ? 1 : 0;
10237             return;
10238         }
10239         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10240                                  fromY, fromX, toY, toX, promoChar,
10241                                  parseList[boardIndex]);
10242         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10243         /* currentMoveString is set as a side-effect of yylex */
10244         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10245         strcat(moveList[boardIndex], "\n");
10246         boardIndex++;
10247         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10248         mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10249         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10250           case MT_NONE:
10251           case MT_STALEMATE:
10252           default:
10253             break;
10254           case MT_CHECK:
10255             if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10256             if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10257                 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10258                 break;
10259             }
10260           case MT_CHECKMATE:
10261           case MT_STAINMATE:
10262             strcat(parseList[boardIndex - 1], "#");
10263             break;
10264         }
10265     }
10266 }
10267
10268
10269 /* Apply a move to the given board  */
10270 void
10271 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10272 {
10273   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10274   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10275
10276     /* [HGM] compute & store e.p. status and castling rights for new position */
10277     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10278
10279       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10280       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10281       board[EP_STATUS] = EP_NONE;
10282       board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10283
10284   if (fromY == DROP_RANK) {
10285         /* must be first */
10286         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10287             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10288             return;
10289         }
10290         piece = board[toY][toX] = (ChessSquare) fromX;
10291   } else {
10292 //      ChessSquare victim;
10293       int i;
10294
10295       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10296 //           victim = board[killY][killX],
10297            killed = board[killY][killX],
10298            board[killY][killX] = EmptySquare,
10299            board[EP_STATUS] = EP_CAPTURE;
10300            if( kill2X >= 0 && kill2Y >= 0)
10301              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10302       }
10303
10304       if( board[toY][toX] != EmptySquare ) {
10305            board[EP_STATUS] = EP_CAPTURE;
10306            if( (fromX != toX || fromY != toY) && // not igui!
10307                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10308                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10309                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10310            }
10311       }
10312
10313       pawn = board[fromY][fromX];
10314       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10315         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10316             captured = board[lastRank][lastFile]; // remove victim
10317             board[lastRank][lastFile] = EmptySquare;
10318             pawn = EmptySquare; // kludge to suppress old e.p. code
10319         }
10320       }
10321       if( pawn == WhiteLance || pawn == BlackLance ) {
10322            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10323                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10324                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10325            }
10326       }
10327       if( pawn == WhitePawn ) {
10328            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10329                board[EP_STATUS] = EP_PAWN_MOVE;
10330            if( toY-fromY>=2) {
10331                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10332                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10333                         gameInfo.variant != VariantBerolina || toX < fromX)
10334                       board[EP_STATUS] = toX | berolina;
10335                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10336                         gameInfo.variant != VariantBerolina || toX > fromX)
10337                       board[EP_STATUS] = toX;
10338                board[LAST_TO] = toX + 256*toY;
10339            }
10340       } else
10341       if( pawn == BlackPawn ) {
10342            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10343                board[EP_STATUS] = EP_PAWN_MOVE;
10344            if( toY-fromY<= -2) {
10345                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10346                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10347                         gameInfo.variant != VariantBerolina || toX < fromX)
10348                       board[EP_STATUS] = toX | berolina;
10349                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10350                         gameInfo.variant != VariantBerolina || toX > fromX)
10351                       board[EP_STATUS] = toX;
10352                board[LAST_TO] = toX + 256*toY;
10353            }
10354        }
10355
10356        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10357        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10358        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10359        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10360
10361        for(i=0; i<nrCastlingRights; i++) {
10362            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10363               board[CASTLING][i] == toX   && castlingRank[i] == toY
10364              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10365        }
10366
10367        if(gameInfo.variant == VariantSChess) { // update virginity
10368            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10369            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10370            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10371            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10372        }
10373
10374      if (fromX == toX && fromY == toY && killX < 0) return;
10375
10376      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10377      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10378      if(gameInfo.variant == VariantKnightmate)
10379          king += (int) WhiteUnicorn - (int) WhiteKing;
10380
10381     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10382        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10383         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10384         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10385         board[EP_STATUS] = EP_NONE; // capture was fake!
10386     } else
10387     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10388         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10389         board[toY][toX] = piece;
10390         board[EP_STATUS] = EP_NONE; // capture was fake!
10391     } else
10392     /* Code added by Tord: */
10393     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10394     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10395         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10396       board[EP_STATUS] = EP_NONE; // capture was fake!
10397       board[fromY][fromX] = EmptySquare;
10398       board[toY][toX] = EmptySquare;
10399       if((toX > fromX) != (piece == WhiteRook)) {
10400         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10401       } else {
10402         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10403       }
10404     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10405                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10406       board[EP_STATUS] = EP_NONE;
10407       board[fromY][fromX] = EmptySquare;
10408       board[toY][toX] = EmptySquare;
10409       if((toX > fromX) != (piece == BlackRook)) {
10410         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10411       } else {
10412         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10413       }
10414     /* End of code added by Tord */
10415
10416     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10417         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10418         board[toY][toX] = piece;
10419     } else if (board[fromY][fromX] == king
10420         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10421         && toY == fromY && toX > fromX+1) {
10422         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10423                                                                                              ; // castle with nearest piece
10424         board[fromY][toX-1] = board[fromY][rookX];
10425         board[fromY][rookX] = EmptySquare;
10426         board[fromY][fromX] = EmptySquare;
10427         board[toY][toX] = king;
10428     } else if (board[fromY][fromX] == king
10429         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10430                && toY == fromY && toX < fromX-1) {
10431         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10432                                                                                   ; // castle with nearest piece
10433         board[fromY][toX+1] = board[fromY][rookX];
10434         board[fromY][rookX] = EmptySquare;
10435         board[fromY][fromX] = EmptySquare;
10436         board[toY][toX] = king;
10437     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10438                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10439                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10440                ) {
10441         /* white pawn promotion */
10442         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10443         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10444             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10445         board[fromY][fromX] = EmptySquare;
10446     } else if ((fromY >= BOARD_HEIGHT>>1)
10447                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10448                && (toX != fromX)
10449                && gameInfo.variant != VariantXiangqi
10450                && gameInfo.variant != VariantBerolina
10451                && (pawn == WhitePawn)
10452                && (board[toY][toX] == EmptySquare)) {
10453         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10454         board[fromY][fromX] = EmptySquare;
10455         board[toY][toX] = piece;
10456         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10457     } else if ((fromY == BOARD_HEIGHT-4)
10458                && (toX == fromX)
10459                && gameInfo.variant == VariantBerolina
10460                && (board[fromY][fromX] == WhitePawn)
10461                && (board[toY][toX] == EmptySquare)) {
10462         board[fromY][fromX] = EmptySquare;
10463         board[toY][toX] = WhitePawn;
10464         if(oldEP & EP_BEROLIN_A) {
10465                 captured = board[fromY][fromX-1];
10466                 board[fromY][fromX-1] = EmptySquare;
10467         }else{  captured = board[fromY][fromX+1];
10468                 board[fromY][fromX+1] = EmptySquare;
10469         }
10470     } else if (board[fromY][fromX] == king
10471         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10472                && toY == fromY && toX > fromX+1) {
10473         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10474                                                                                              ;
10475         board[fromY][toX-1] = board[fromY][rookX];
10476         board[fromY][rookX] = EmptySquare;
10477         board[fromY][fromX] = EmptySquare;
10478         board[toY][toX] = king;
10479     } else if (board[fromY][fromX] == king
10480         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10481                && toY == fromY && toX < fromX-1) {
10482         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10483                                                                                 ;
10484         board[fromY][toX+1] = board[fromY][rookX];
10485         board[fromY][rookX] = EmptySquare;
10486         board[fromY][fromX] = EmptySquare;
10487         board[toY][toX] = king;
10488     } else if (fromY == 7 && fromX == 3
10489                && board[fromY][fromX] == BlackKing
10490                && toY == 7 && toX == 5) {
10491         board[fromY][fromX] = EmptySquare;
10492         board[toY][toX] = BlackKing;
10493         board[fromY][7] = EmptySquare;
10494         board[toY][4] = BlackRook;
10495     } else if (fromY == 7 && fromX == 3
10496                && board[fromY][fromX] == BlackKing
10497                && toY == 7 && toX == 1) {
10498         board[fromY][fromX] = EmptySquare;
10499         board[toY][toX] = BlackKing;
10500         board[fromY][0] = EmptySquare;
10501         board[toY][2] = BlackRook;
10502     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10503                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10504                && toY < promoRank && promoChar
10505                ) {
10506         /* black pawn promotion */
10507         board[toY][toX] = CharToPiece(ToLower(promoChar));
10508         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10509             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10510         board[fromY][fromX] = EmptySquare;
10511     } else if ((fromY < BOARD_HEIGHT>>1)
10512                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10513                && (toX != fromX)
10514                && gameInfo.variant != VariantXiangqi
10515                && gameInfo.variant != VariantBerolina
10516                && (pawn == BlackPawn)
10517                && (board[toY][toX] == EmptySquare)) {
10518         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10519         board[fromY][fromX] = EmptySquare;
10520         board[toY][toX] = piece;
10521         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10522     } else if ((fromY == 3)
10523                && (toX == fromX)
10524                && gameInfo.variant == VariantBerolina
10525                && (board[fromY][fromX] == BlackPawn)
10526                && (board[toY][toX] == EmptySquare)) {
10527         board[fromY][fromX] = EmptySquare;
10528         board[toY][toX] = BlackPawn;
10529         if(oldEP & EP_BEROLIN_A) {
10530                 captured = board[fromY][fromX-1];
10531                 board[fromY][fromX-1] = EmptySquare;
10532         }else{  captured = board[fromY][fromX+1];
10533                 board[fromY][fromX+1] = EmptySquare;
10534         }
10535     } else {
10536         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10537         board[fromY][fromX] = EmptySquare;
10538         board[toY][toX] = piece;
10539     }
10540   }
10541
10542     if (gameInfo.holdingsWidth != 0) {
10543
10544       /* !!A lot more code needs to be written to support holdings  */
10545       /* [HGM] OK, so I have written it. Holdings are stored in the */
10546       /* penultimate board files, so they are automaticlly stored   */
10547       /* in the game history.                                       */
10548       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10549                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10550         /* Delete from holdings, by decreasing count */
10551         /* and erasing image if necessary            */
10552         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10553         if(p < (int) BlackPawn) { /* white drop */
10554              p -= (int)WhitePawn;
10555                  p = PieceToNumber((ChessSquare)p);
10556              if(p >= gameInfo.holdingsSize) p = 0;
10557              if(--board[p][BOARD_WIDTH-2] <= 0)
10558                   board[p][BOARD_WIDTH-1] = EmptySquare;
10559              if((int)board[p][BOARD_WIDTH-2] < 0)
10560                         board[p][BOARD_WIDTH-2] = 0;
10561         } else {                  /* black drop */
10562              p -= (int)BlackPawn;
10563                  p = PieceToNumber((ChessSquare)p);
10564              if(p >= gameInfo.holdingsSize) p = 0;
10565              if(--board[handSize-1-p][1] <= 0)
10566                   board[handSize-1-p][0] = EmptySquare;
10567              if((int)board[handSize-1-p][1] < 0)
10568                         board[handSize-1-p][1] = 0;
10569         }
10570       }
10571       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10572           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10573         /* [HGM] holdings: Add to holdings, if holdings exist */
10574         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10575                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10576                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10577         }
10578         p = (int) captured;
10579         if (p >= (int) BlackPawn) {
10580           p -= (int)BlackPawn;
10581           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10582                   /* Restore shogi-promoted piece to its original  first */
10583                   captured = (ChessSquare) (DEMOTED(captured));
10584                   p = DEMOTED(p);
10585           }
10586           p = PieceToNumber((ChessSquare)p);
10587           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10588           board[p][BOARD_WIDTH-2]++;
10589           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10590         } else {
10591           p -= (int)WhitePawn;
10592           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10593                   captured = (ChessSquare) (DEMOTED(captured));
10594                   p = DEMOTED(p);
10595           }
10596           p = PieceToNumber((ChessSquare)p);
10597           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10598           board[handSize-1-p][1]++;
10599           board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10600         }
10601       }
10602     } else if (gameInfo.variant == VariantAtomic) {
10603       if (captured != EmptySquare) {
10604         int y, x;
10605         for (y = toY-1; y <= toY+1; y++) {
10606           for (x = toX-1; x <= toX+1; x++) {
10607             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10608                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10609               board[y][x] = EmptySquare;
10610             }
10611           }
10612         }
10613         board[toY][toX] = EmptySquare;
10614       }
10615     }
10616
10617     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10618         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10619     } else
10620     if(promoChar == '+') {
10621         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10622         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10623         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10624           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10625     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10626         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10627         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10628            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10629         board[toY][toX] = newPiece;
10630     }
10631     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10632                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10633         // [HGM] superchess: take promotion piece out of holdings
10634         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10635         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10636             if(!--board[k][BOARD_WIDTH-2])
10637                 board[k][BOARD_WIDTH-1] = EmptySquare;
10638         } else {
10639             if(!--board[handSize-1-k][1])
10640                 board[handSize-1-k][0] = EmptySquare;
10641         }
10642     }
10643 }
10644
10645 /* Updates forwardMostMove */
10646 void
10647 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10648 {
10649     int x = toX, y = toY, mask;
10650     char *s = parseList[forwardMostMove];
10651     ChessSquare p = boards[forwardMostMove][toY][toX];
10652 //    forwardMostMove++; // [HGM] bare: moved downstream
10653
10654     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10655     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10656     (void) CoordsToAlgebraic(boards[forwardMostMove],
10657                              PosFlags(forwardMostMove),
10658                              fromY, fromX, y, x, (killX < 0)*promoChar,
10659                              s);
10660     if(kill2X >= 0 && kill2Y >= 0)
10661         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10662     if(killX >= 0 && killY >= 0)
10663         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10664                                            toX + AAA, toY + ONE - '0', promoChar);
10665
10666     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10667         int timeLeft; static int lastLoadFlag=0; int king, piece;
10668         piece = boards[forwardMostMove][fromY][fromX];
10669         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10670         if(gameInfo.variant == VariantKnightmate)
10671             king += (int) WhiteUnicorn - (int) WhiteKing;
10672         if(forwardMostMove == 0) {
10673             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10674                 fprintf(serverMoves, "%s;", UserName());
10675             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10676                 fprintf(serverMoves, "%s;", second.tidy);
10677             fprintf(serverMoves, "%s;", first.tidy);
10678             if(gameMode == MachinePlaysWhite)
10679                 fprintf(serverMoves, "%s;", UserName());
10680             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10681                 fprintf(serverMoves, "%s;", second.tidy);
10682         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10683         lastLoadFlag = loadFlag;
10684         // print base move
10685         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10686         // print castling suffix
10687         if( toY == fromY && piece == king ) {
10688             if(toX-fromX > 1)
10689                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10690             if(fromX-toX >1)
10691                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10692         }
10693         // e.p. suffix
10694         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10695              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10696              boards[forwardMostMove][toY][toX] == EmptySquare
10697              && fromX != toX && fromY != toY)
10698                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10699         // promotion suffix
10700         if(promoChar != NULLCHAR) {
10701             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10702                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10703                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10704             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10705         }
10706         if(!loadFlag) {
10707                 char buf[MOVE_LEN*2], *p; int len;
10708             fprintf(serverMoves, "/%d/%d",
10709                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10710             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10711             else                      timeLeft = blackTimeRemaining/1000;
10712             fprintf(serverMoves, "/%d", timeLeft);
10713                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10714                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10715                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10716                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10717             fprintf(serverMoves, "/%s", buf);
10718         }
10719         fflush(serverMoves);
10720     }
10721
10722     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10723         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10724       return;
10725     }
10726     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10727     if (commentList[forwardMostMove+1] != NULL) {
10728         free(commentList[forwardMostMove+1]);
10729         commentList[forwardMostMove+1] = NULL;
10730     }
10731     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10732     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10733     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10734     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10735     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10736     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10737     adjustedClock = FALSE;
10738     gameInfo.result = GameUnfinished;
10739     if (gameInfo.resultDetails != NULL) {
10740         free(gameInfo.resultDetails);
10741         gameInfo.resultDetails = NULL;
10742     }
10743     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10744                               moveList[forwardMostMove - 1]);
10745     mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10746     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10747       case MT_NONE:
10748       case MT_STALEMATE:
10749       default:
10750         break;
10751       case MT_CHECK:
10752         if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10753         if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10754             if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10755             break;
10756         }
10757       case MT_CHECKMATE:
10758       case MT_STAINMATE:
10759         strcat(parseList[forwardMostMove - 1], "#");
10760         break;
10761     }
10762 }
10763
10764 /* Updates currentMove if not pausing */
10765 void
10766 ShowMove (int fromX, int fromY, int toX, int toY)
10767 {
10768     int instant = (gameMode == PlayFromGameFile) ?
10769         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10770     if(appData.noGUI) return;
10771     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10772         if (!instant) {
10773             if (forwardMostMove == currentMove + 1) {
10774                 AnimateMove(boards[forwardMostMove - 1],
10775                             fromX, fromY, toX, toY);
10776             }
10777         }
10778         currentMove = forwardMostMove;
10779     }
10780
10781     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10782
10783     if (instant) return;
10784
10785     DisplayMove(currentMove - 1);
10786     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10787             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10788                 SetHighlights(fromX, fromY, toX, toY);
10789             }
10790     }
10791     DrawPosition(FALSE, boards[currentMove]);
10792     DisplayBothClocks();
10793     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10794 }
10795
10796 void
10797 SendEgtPath (ChessProgramState *cps)
10798 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10799         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10800
10801         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10802
10803         while(*p) {
10804             char c, *q = name+1, *r, *s;
10805
10806             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10807             while(*p && *p != ',') *q++ = *p++;
10808             *q++ = ':'; *q = 0;
10809             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10810                 strcmp(name, ",nalimov:") == 0 ) {
10811                 // take nalimov path from the menu-changeable option first, if it is defined
10812               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10813                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10814             } else
10815             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10816                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10817                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10818                 s = r = StrStr(s, ":") + 1; // beginning of path info
10819                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10820                 c = *r; *r = 0;             // temporarily null-terminate path info
10821                     *--q = 0;               // strip of trailig ':' from name
10822                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10823                 *r = c;
10824                 SendToProgram(buf,cps);     // send egtbpath command for this format
10825             }
10826             if(*p == ',') p++; // read away comma to position for next format name
10827         }
10828 }
10829
10830 static int
10831 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10832 {
10833       int width = 8, height = 8, holdings = 0;             // most common sizes
10834       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10835       // correct the deviations default for each variant
10836       if( v == VariantXiangqi ) width = 9,  height = 10;
10837       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10838       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10839       if( v == VariantCapablanca || v == VariantCapaRandom ||
10840           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10841                                 width = 10;
10842       if( v == VariantCourier ) width = 12;
10843       if( v == VariantSuper )                            holdings = 8;
10844       if( v == VariantGreat )   width = 10,              holdings = 8;
10845       if( v == VariantSChess )                           holdings = 7;
10846       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10847       if( v == VariantChuChess) width = 10, height = 10;
10848       if( v == VariantChu )     width = 12, height = 12;
10849       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10850              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10851              holdingsSize >= 0 && holdingsSize != holdings;
10852 }
10853
10854 char variantError[MSG_SIZ];
10855
10856 char *
10857 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10858 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10859       char *p, *variant = VariantName(v);
10860       static char b[MSG_SIZ];
10861       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10862            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10863                                                holdingsSize, variant); // cook up sized variant name
10864            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10865            if(StrStr(list, b) == NULL) {
10866                // specific sized variant not known, check if general sizing allowed
10867                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10868                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10869                             boardWidth, boardHeight, holdingsSize, engine);
10870                    return NULL;
10871                }
10872                /* [HGM] here we really should compare with the maximum supported board size */
10873            }
10874       } else snprintf(b, MSG_SIZ,"%s", variant);
10875       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10876       p = StrStr(list, b);
10877       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10878       if(p == NULL) {
10879           // occurs not at all in list, or only as sub-string
10880           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10881           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10882               int l = strlen(variantError);
10883               char *q;
10884               while(p != list && p[-1] != ',') p--;
10885               q = strchr(p, ',');
10886               if(q) *q = NULLCHAR;
10887               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10888               if(q) *q= ',';
10889           }
10890           return NULL;
10891       }
10892       return b;
10893 }
10894
10895 void
10896 InitChessProgram (ChessProgramState *cps, int setup)
10897 /* setup needed to setup FRC opening position */
10898 {
10899     char buf[MSG_SIZ], *b;
10900     if (appData.noChessProgram) return;
10901     hintRequested = FALSE;
10902     bookRequested = FALSE;
10903
10904     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10905     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10906     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10907     if(cps->memSize) { /* [HGM] memory */
10908       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10909         SendToProgram(buf, cps);
10910     }
10911     SendEgtPath(cps); /* [HGM] EGT */
10912     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10913       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10914         SendToProgram(buf, cps);
10915     }
10916
10917     setboardSpoiledMachineBlack = FALSE;
10918     SendToProgram(cps->initString, cps);
10919     if (gameInfo.variant != VariantNormal &&
10920         gameInfo.variant != VariantLoadable
10921         /* [HGM] also send variant if board size non-standard */
10922         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10923
10924       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10925                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10926
10927       if (b == NULL) {
10928         VariantClass v;
10929         char c, *q = cps->variants, *p = strchr(q, ',');
10930         if(p) *p = NULLCHAR;
10931         v = StringToVariant(q);
10932         DisplayError(variantError, 0);
10933         if(v != VariantUnknown && cps == &first) {
10934             int w, h, s;
10935             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10936                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10937             ASSIGN(appData.variant, q);
10938             Reset(TRUE, FALSE);
10939         }
10940         if(p) *p = ',';
10941         return;
10942       }
10943
10944       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10945       SendToProgram(buf, cps);
10946     }
10947     currentlyInitializedVariant = gameInfo.variant;
10948
10949     /* [HGM] send opening position in FRC to first engine */
10950     if(setup) {
10951           SendToProgram("force\n", cps);
10952           SendBoard(cps, 0);
10953           /* engine is now in force mode! Set flag to wake it up after first move. */
10954           setboardSpoiledMachineBlack = 1;
10955     }
10956
10957     if (cps->sendICS) {
10958       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10959       SendToProgram(buf, cps);
10960     }
10961     cps->maybeThinking = FALSE;
10962     cps->offeredDraw = 0;
10963     if (!appData.icsActive) {
10964         SendTimeControl(cps, movesPerSession, timeControl,
10965                         timeIncrement, appData.searchDepth,
10966                         searchTime);
10967     }
10968     if (appData.showThinking
10969         // [HGM] thinking: four options require thinking output to be sent
10970         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10971                                 ) {
10972         SendToProgram("post\n", cps);
10973     }
10974     SendToProgram("hard\n", cps);
10975     if (!appData.ponderNextMove) {
10976         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10977            it without being sure what state we are in first.  "hard"
10978            is not a toggle, so that one is OK.
10979          */
10980         SendToProgram("easy\n", cps);
10981     }
10982     if (cps->usePing) {
10983       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10984       SendToProgram(buf, cps);
10985     }
10986     cps->initDone = TRUE;
10987     ClearEngineOutputPane(cps == &second);
10988 }
10989
10990
10991 char *
10992 ResendOptions (ChessProgramState *cps, int toEngine)
10993 { // send the stored value of the options
10994   int i;
10995   static char buf2[MSG_SIZ*10];
10996   char buf[MSG_SIZ], *p = buf2;
10997   Option *opt = cps->option;
10998   *p = NULLCHAR;
10999   for(i=0; i<cps->nrOptions; i++, opt++) {
11000       *buf = NULLCHAR;
11001       switch(opt->type) {
11002         case Spin:
11003         case Slider:
11004         case CheckBox:
11005             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11006             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
11007           break;
11008         case ComboBox:
11009             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11010             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
11011           break;
11012         default:
11013             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
11014             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
11015           break;
11016         case Button:
11017         case SaveButton:
11018           continue;
11019       }
11020       if(*buf) {
11021         if(toEngine) {
11022           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
11023           SendToProgram(buf2, cps);
11024         } else {
11025           if(p != buf2) *p++ = ',';
11026           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
11027           while(*p) p++;
11028         }
11029       }
11030   }
11031   return buf2;
11032 }
11033
11034 void
11035 StartChessProgram (ChessProgramState *cps)
11036 {
11037     char buf[MSG_SIZ];
11038     int err;
11039
11040     if (appData.noChessProgram) return;
11041     cps->initDone = FALSE;
11042
11043     if (strcmp(cps->host, "localhost") == 0) {
11044         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
11045     } else if (*appData.remoteShell == NULLCHAR) {
11046         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
11047     } else {
11048         if (*appData.remoteUser == NULLCHAR) {
11049           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11050                     cps->program);
11051         } else {
11052           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11053                     cps->host, appData.remoteUser, cps->program);
11054         }
11055         err = StartChildProcess(buf, "", &cps->pr);
11056     }
11057
11058     if (err != 0) {
11059       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11060         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11061         if(cps != &first) return;
11062         appData.noChessProgram = TRUE;
11063         ThawUI();
11064         SetNCPMode();
11065 //      DisplayFatalError(buf, err, 1);
11066 //      cps->pr = NoProc;
11067 //      cps->isr = NULL;
11068         return;
11069     }
11070
11071     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11072     if (cps->protocolVersion > 1) {
11073       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11074       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11075         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11076         cps->comboCnt = 0;  //                and values of combo boxes
11077       }
11078       SendToProgram(buf, cps);
11079       if(cps->reload) ResendOptions(cps, TRUE);
11080     } else {
11081       SendToProgram("xboard\n", cps);
11082     }
11083 }
11084
11085 void
11086 TwoMachinesEventIfReady P((void))
11087 {
11088   static int curMess = 0;
11089   if (first.lastPing != first.lastPong) {
11090     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11091     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11092     return;
11093   }
11094   if (second.lastPing != second.lastPong) {
11095     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11096     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11097     return;
11098   }
11099   DisplayMessage("", ""); curMess = 0;
11100   TwoMachinesEvent();
11101 }
11102
11103 char *
11104 MakeName (char *template)
11105 {
11106     time_t clock;
11107     struct tm *tm;
11108     static char buf[MSG_SIZ];
11109     char *p = buf;
11110     int i;
11111
11112     clock = time((time_t *)NULL);
11113     tm = localtime(&clock);
11114
11115     while(*p++ = *template++) if(p[-1] == '%') {
11116         switch(*template++) {
11117           case 0:   *p = 0; return buf;
11118           case 'Y': i = tm->tm_year+1900; break;
11119           case 'y': i = tm->tm_year-100; break;
11120           case 'M': i = tm->tm_mon+1; break;
11121           case 'd': i = tm->tm_mday; break;
11122           case 'h': i = tm->tm_hour; break;
11123           case 'm': i = tm->tm_min; break;
11124           case 's': i = tm->tm_sec; break;
11125           default:  i = 0;
11126         }
11127         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11128     }
11129     return buf;
11130 }
11131
11132 int
11133 CountPlayers (char *p)
11134 {
11135     int n = 0;
11136     while(p = strchr(p, '\n')) p++, n++; // count participants
11137     return n;
11138 }
11139
11140 FILE *
11141 WriteTourneyFile (char *results, FILE *f)
11142 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11143     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11144     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11145         // create a file with tournament description
11146         fprintf(f, "-participants {%s}\n", appData.participants);
11147         fprintf(f, "-seedBase %d\n", appData.seedBase);
11148         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11149         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11150         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11151         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11152         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11153         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11154         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11155         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11156         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11157         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11158         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11159         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11160         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11161         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11162         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11163         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11164         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11165         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11166         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11167         fprintf(f, "-smpCores %d\n", appData.smpCores);
11168         if(searchTime > 0)
11169                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11170         else {
11171                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11172                 fprintf(f, "-tc %s\n", appData.timeControl);
11173                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11174         }
11175         fprintf(f, "-results \"%s\"\n", results);
11176     }
11177     return f;
11178 }
11179
11180 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11181
11182 void
11183 Substitute (char *participants, int expunge)
11184 {
11185     int i, changed, changes=0, nPlayers=0;
11186     char *p, *q, *r, buf[MSG_SIZ];
11187     if(participants == NULL) return;
11188     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11189     r = p = participants; q = appData.participants;
11190     while(*p && *p == *q) {
11191         if(*p == '\n') r = p+1, nPlayers++;
11192         p++; q++;
11193     }
11194     if(*p) { // difference
11195         while(*p && *p++ != '\n')
11196                                  ;
11197         while(*q && *q++ != '\n')
11198                                  ;
11199       changed = nPlayers;
11200         changes = 1 + (strcmp(p, q) != 0);
11201     }
11202     if(changes == 1) { // a single engine mnemonic was changed
11203         q = r; while(*q) nPlayers += (*q++ == '\n');
11204         p = buf; while(*r && (*p = *r++) != '\n') p++;
11205         *p = NULLCHAR;
11206         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11207         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11208         if(mnemonic[i]) { // The substitute is valid
11209             FILE *f;
11210             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11211                 flock(fileno(f), LOCK_EX);
11212                 ParseArgsFromFile(f);
11213                 fseek(f, 0, SEEK_SET);
11214                 FREE(appData.participants); appData.participants = participants;
11215                 if(expunge) { // erase results of replaced engine
11216                     int len = strlen(appData.results), w, b, dummy;
11217                     for(i=0; i<len; i++) {
11218                         Pairing(i, nPlayers, &w, &b, &dummy);
11219                         if((w == changed || b == changed) && appData.results[i] == '*') {
11220                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11221                             fclose(f);
11222                             return;
11223                         }
11224                     }
11225                     for(i=0; i<len; i++) {
11226                         Pairing(i, nPlayers, &w, &b, &dummy);
11227                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11228                     }
11229                 }
11230                 WriteTourneyFile(appData.results, f);
11231                 fclose(f); // release lock
11232                 return;
11233             }
11234         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11235     }
11236     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11237     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11238     free(participants);
11239     return;
11240 }
11241
11242 int
11243 CheckPlayers (char *participants)
11244 {
11245         int i;
11246         char buf[MSG_SIZ], *p;
11247         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11248         while(p = strchr(participants, '\n')) {
11249             *p = NULLCHAR;
11250             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11251             if(!mnemonic[i]) {
11252                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11253                 *p = '\n';
11254                 DisplayError(buf, 0);
11255                 return 1;
11256             }
11257             *p = '\n';
11258             participants = p + 1;
11259         }
11260         return 0;
11261 }
11262
11263 int
11264 CreateTourney (char *name)
11265 {
11266         FILE *f;
11267         if(matchMode && strcmp(name, appData.tourneyFile)) {
11268              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11269         }
11270         if(name[0] == NULLCHAR) {
11271             if(appData.participants[0])
11272                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11273             return 0;
11274         }
11275         f = fopen(name, "r");
11276         if(f) { // file exists
11277             ASSIGN(appData.tourneyFile, name);
11278             ParseArgsFromFile(f); // parse it
11279         } else {
11280             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11281             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11282                 DisplayError(_("Not enough participants"), 0);
11283                 return 0;
11284             }
11285             if(CheckPlayers(appData.participants)) return 0;
11286             ASSIGN(appData.tourneyFile, name);
11287             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11288             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11289         }
11290         fclose(f);
11291         appData.noChessProgram = FALSE;
11292         appData.clockMode = TRUE;
11293         SetGNUMode();
11294         return 1;
11295 }
11296
11297 int
11298 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11299 {
11300     char buf[2*MSG_SIZ], *p, *q;
11301     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11302     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11303     skip = !all && group[0]; // if group requested, we start in skip mode
11304     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11305         p = names; q = buf; header = 0;
11306         while(*p && *p != '\n') *q++ = *p++;
11307         *q = 0;
11308         if(*p == '\n') p++;
11309         if(buf[0] == '#') {
11310             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11311             depth++; // we must be entering a new group
11312             if(all) continue; // suppress printing group headers when complete list requested
11313             header = 1;
11314             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11315         }
11316         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11317         if(engineList[i]) free(engineList[i]);
11318         engineList[i] = strdup(buf);
11319         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11320         if(engineMnemonic[i]) free(engineMnemonic[i]);
11321         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11322             strcat(buf, " (");
11323             sscanf(q + 8, "%s", buf + strlen(buf));
11324             strcat(buf, ")");
11325         }
11326         engineMnemonic[i] = strdup(buf);
11327         i++;
11328     }
11329     engineList[i] = engineMnemonic[i] = NULL;
11330     return i;
11331 }
11332
11333 void
11334 SaveEngineSettings (int n)
11335 {
11336     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11337     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11338     p = strstr(firstChessProgramNames, currentEngine[n]);
11339     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11340     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11341     len = strlen(currentEngine[n]);
11342     q = p + len; *p = 0; // cut list into head and tail piece
11343     s = strstr(currentEngine[n], "firstOptions");
11344     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11345         char *r = s + 14;
11346         while(*r && *r != s[13]) r++;
11347         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11348         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11349     } else if(*optionSettings) {
11350         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11351     }
11352     ASSIGN(currentEngine[n], buf); // updated engine line
11353     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11354     s = malloc(len);
11355     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11356     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11357 }
11358
11359 // following implemented as macro to avoid type limitations
11360 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11361
11362 void
11363 SwapEngines (int n)
11364 {   // swap settings for first engine and other engine (so far only some selected options)
11365     int h;
11366     char *p;
11367     if(n == 0) return;
11368     SWAP(directory, p)
11369     SWAP(chessProgram, p)
11370     SWAP(isUCI, h)
11371     SWAP(hasOwnBookUCI, h)
11372     SWAP(protocolVersion, h)
11373     SWAP(reuse, h)
11374     SWAP(scoreIsAbsolute, h)
11375     SWAP(timeOdds, h)
11376     SWAP(logo, p)
11377     SWAP(pgnName, p)
11378     SWAP(pvSAN, h)
11379     SWAP(engOptions, p)
11380     SWAP(engInitString, p)
11381     SWAP(computerString, p)
11382     SWAP(features, p)
11383     SWAP(fenOverride, p)
11384     SWAP(NPS, h)
11385     SWAP(accumulateTC, h)
11386     SWAP(drawDepth, h)
11387     SWAP(host, p)
11388     SWAP(pseudo, h)
11389 }
11390
11391 int
11392 GetEngineLine (char *s, int n)
11393 {
11394     int i;
11395     char buf[MSG_SIZ];
11396     extern char *icsNames;
11397     if(!s || !*s) return 0;
11398     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11399     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11400     if(!mnemonic[i]) return 0;
11401     if(n == 11) return 1; // just testing if there was a match
11402     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11403     if(n == 1) SwapEngines(n);
11404     ParseArgsFromString(buf);
11405     if(n == 1) SwapEngines(n);
11406     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11407     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11408         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11409         ParseArgsFromString(buf);
11410     }
11411     return 1;
11412 }
11413
11414 int
11415 SetPlayer (int player, char *p)
11416 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11417     int i;
11418     char buf[MSG_SIZ], *engineName;
11419     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11420     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11421     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11422     if(mnemonic[i]) {
11423         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11424         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11425         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11426         ParseArgsFromString(buf);
11427     } else { // no engine with this nickname is installed!
11428         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11429         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11430         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11431         ModeHighlight();
11432         DisplayError(buf, 0);
11433         return 0;
11434     }
11435     free(engineName);
11436     return i;
11437 }
11438
11439 char *recentEngines;
11440
11441 void
11442 RecentEngineEvent (int nr)
11443 {
11444     int n;
11445 //    SwapEngines(1); // bump first to second
11446 //    ReplaceEngine(&second, 1); // and load it there
11447     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11448     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11449     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11450         ReplaceEngine(&first, 0);
11451         FloatToFront(&appData.recentEngineList, command[n]);
11452         ASSIGN(currentEngine[0], command[n]);
11453     }
11454 }
11455
11456 int
11457 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11458 {   // determine players from game number
11459     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11460
11461     if(appData.tourneyType == 0) {
11462         roundsPerCycle = (nPlayers - 1) | 1;
11463         pairingsPerRound = nPlayers / 2;
11464     } else if(appData.tourneyType > 0) {
11465         roundsPerCycle = nPlayers - appData.tourneyType;
11466         pairingsPerRound = appData.tourneyType;
11467     }
11468     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11469     gamesPerCycle = gamesPerRound * roundsPerCycle;
11470     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11471     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11472     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11473     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11474     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11475     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11476
11477     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11478     if(appData.roundSync) *syncInterval = gamesPerRound;
11479
11480     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11481
11482     if(appData.tourneyType == 0) {
11483         if(curPairing == (nPlayers-1)/2 ) {
11484             *whitePlayer = curRound;
11485             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11486         } else {
11487             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11488             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11489             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11490             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11491         }
11492     } else if(appData.tourneyType > 1) {
11493         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11494         *whitePlayer = curRound + appData.tourneyType;
11495     } else if(appData.tourneyType > 0) {
11496         *whitePlayer = curPairing;
11497         *blackPlayer = curRound + appData.tourneyType;
11498     }
11499
11500     // take care of white/black alternation per round.
11501     // For cycles and games this is already taken care of by default, derived from matchGame!
11502     return curRound & 1;
11503 }
11504
11505 int
11506 NextTourneyGame (int nr, int *swapColors)
11507 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11508     char *p, *q;
11509     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11510     FILE *tf;
11511     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11512     tf = fopen(appData.tourneyFile, "r");
11513     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11514     ParseArgsFromFile(tf); fclose(tf);
11515     InitTimeControls(); // TC might be altered from tourney file
11516
11517     nPlayers = CountPlayers(appData.participants); // count participants
11518     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11519     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11520
11521     if(syncInterval) {
11522         p = q = appData.results;
11523         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11524         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11525             DisplayMessage(_("Waiting for other game(s)"),"");
11526             waitingForGame = TRUE;
11527             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11528             return 0;
11529         }
11530         waitingForGame = FALSE;
11531     }
11532
11533     if(appData.tourneyType < 0) {
11534         if(nr>=0 && !pairingReceived) {
11535             char buf[1<<16];
11536             if(pairing.pr == NoProc) {
11537                 if(!appData.pairingEngine[0]) {
11538                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11539                     return 0;
11540                 }
11541                 StartChessProgram(&pairing); // starts the pairing engine
11542             }
11543             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11544             SendToProgram(buf, &pairing);
11545             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11546             SendToProgram(buf, &pairing);
11547             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11548         }
11549         pairingReceived = 0;                              // ... so we continue here
11550         *swapColors = 0;
11551         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11552         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11553         matchGame = 1; roundNr = nr / syncInterval + 1;
11554     }
11555
11556     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11557
11558     // redefine engines, engine dir, etc.
11559     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11560     if(first.pr == NoProc) {
11561       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11562       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11563     }
11564     if(second.pr == NoProc) {
11565       SwapEngines(1);
11566       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11567       SwapEngines(1);         // and make that valid for second engine by swapping
11568       InitEngine(&second, 1);
11569     }
11570     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11571     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11572     return OK;
11573 }
11574
11575 void
11576 NextMatchGame ()
11577 {   // performs game initialization that does not invoke engines, and then tries to start the game
11578     int res, firstWhite, swapColors = 0;
11579     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11580     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
11581         char buf[MSG_SIZ];
11582         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11583         if(strcmp(buf, currentDebugFile)) { // name has changed
11584             FILE *f = fopen(buf, "w");
11585             if(f) { // if opening the new file failed, just keep using the old one
11586                 ASSIGN(currentDebugFile, buf);
11587                 fclose(debugFP);
11588                 debugFP = f;
11589             }
11590             if(appData.serverFileName) {
11591                 if(serverFP) fclose(serverFP);
11592                 serverFP = fopen(appData.serverFileName, "w");
11593                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11594                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11595             }
11596         }
11597     }
11598     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11599     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11600     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11601     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11602     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11603     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11604     Reset(FALSE, first.pr != NoProc);
11605     res = LoadGameOrPosition(matchGame); // setup game
11606     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11607     if(!res) return; // abort when bad game/pos file
11608     if(appData.epd) {// in EPD mode we make sure first engine is to move
11609         firstWhite = !(forwardMostMove & 1);
11610         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11611         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11612     }
11613     TwoMachinesEvent();
11614 }
11615
11616 void
11617 UserAdjudicationEvent (int result)
11618 {
11619     ChessMove gameResult = GameIsDrawn;
11620
11621     if( result > 0 ) {
11622         gameResult = WhiteWins;
11623     }
11624     else if( result < 0 ) {
11625         gameResult = BlackWins;
11626     }
11627
11628     if( gameMode == TwoMachinesPlay ) {
11629         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11630     }
11631 }
11632
11633
11634 // [HGM] save: calculate checksum of game to make games easily identifiable
11635 int
11636 StringCheckSum (char *s)
11637 {
11638         int i = 0;
11639         if(s==NULL) return 0;
11640         while(*s) i = i*259 + *s++;
11641         return i;
11642 }
11643
11644 int
11645 GameCheckSum ()
11646 {
11647         int i, sum=0;
11648         for(i=backwardMostMove; i<forwardMostMove; i++) {
11649                 sum += pvInfoList[i].depth;
11650                 sum += StringCheckSum(parseList[i]);
11651                 sum += StringCheckSum(commentList[i]);
11652                 sum *= 261;
11653         }
11654         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11655         return sum + StringCheckSum(commentList[i]);
11656 } // end of save patch
11657
11658 void
11659 GameEnds (ChessMove result, char *resultDetails, int whosays)
11660 {
11661     GameMode nextGameMode;
11662     int isIcsGame;
11663     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11664
11665     if(endingGame) return; /* [HGM] crash: forbid recursion */
11666     endingGame = 1;
11667     if(twoBoards) { // [HGM] dual: switch back to one board
11668         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11669         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11670     }
11671     if (appData.debugMode) {
11672       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11673               result, resultDetails ? resultDetails : "(null)", whosays);
11674     }
11675
11676     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11677
11678     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11679
11680     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11681         /* If we are playing on ICS, the server decides when the
11682            game is over, but the engine can offer to draw, claim
11683            a draw, or resign.
11684          */
11685 #if ZIPPY
11686         if (appData.zippyPlay && first.initDone) {
11687             if (result == GameIsDrawn) {
11688                 /* In case draw still needs to be claimed */
11689                 SendToICS(ics_prefix);
11690                 SendToICS("draw\n");
11691             } else if (StrCaseStr(resultDetails, "resign")) {
11692                 SendToICS(ics_prefix);
11693                 SendToICS("resign\n");
11694             }
11695         }
11696 #endif
11697         endingGame = 0; /* [HGM] crash */
11698         return;
11699     }
11700
11701     /* If we're loading the game from a file, stop */
11702     if (whosays == GE_FILE) {
11703       (void) StopLoadGameTimer();
11704       gameFileFP = NULL;
11705     }
11706
11707     /* Cancel draw offers */
11708     first.offeredDraw = second.offeredDraw = 0;
11709
11710     /* If this is an ICS game, only ICS can really say it's done;
11711        if not, anyone can. */
11712     isIcsGame = (gameMode == IcsPlayingWhite ||
11713                  gameMode == IcsPlayingBlack ||
11714                  gameMode == IcsObserving    ||
11715                  gameMode == IcsExamining);
11716
11717     if (!isIcsGame || whosays == GE_ICS) {
11718         /* OK -- not an ICS game, or ICS said it was done */
11719         StopClocks();
11720         if (!isIcsGame && !appData.noChessProgram)
11721           SetUserThinkingEnables();
11722
11723         /* [HGM] if a machine claims the game end we verify this claim */
11724         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11725             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11726                 char claimer;
11727                 ChessMove trueResult = (ChessMove) -1;
11728
11729                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11730                                             first.twoMachinesColor[0] :
11731                                             second.twoMachinesColor[0] ;
11732
11733                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11734                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11735                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11736                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11737                 } else
11738                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11739                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11740                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11741                 } else
11742                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11743                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11744                 }
11745
11746                 // now verify win claims, but not in drop games, as we don't understand those yet
11747                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11748                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11749                     (result == WhiteWins && claimer == 'w' ||
11750                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11751                       if (appData.debugMode) {
11752                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11753                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11754                       }
11755                       if(result != trueResult) {
11756                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11757                               result = claimer == 'w' ? BlackWins : WhiteWins;
11758                               resultDetails = buf;
11759                       }
11760                 } else
11761                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11762                     && (forwardMostMove <= backwardMostMove ||
11763                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11764                         (claimer=='b')==(forwardMostMove&1))
11765                                                                                   ) {
11766                       /* [HGM] verify: draws that were not flagged are false claims */
11767                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11768                       result = claimer == 'w' ? BlackWins : WhiteWins;
11769                       resultDetails = buf;
11770                 }
11771                 /* (Claiming a loss is accepted no questions asked!) */
11772             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11773                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11774                 result = GameUnfinished;
11775                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11776             }
11777             /* [HGM] bare: don't allow bare King to win */
11778             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11779                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11780                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11781                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11782                && result != GameIsDrawn)
11783             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11784                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11785                         int p = (int)boards[forwardMostMove][i][j] - color;
11786                         if(p >= 0 && p <= (int)WhiteKing) k++;
11787                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11788                 }
11789                 if (appData.debugMode) {
11790                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11791                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11792                 }
11793                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11794                         result = GameIsDrawn;
11795                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11796                         resultDetails = buf;
11797                 }
11798             }
11799         }
11800
11801
11802         if(serverMoves != NULL && !loadFlag) { char c = '=';
11803             if(result==WhiteWins) c = '+';
11804             if(result==BlackWins) c = '-';
11805             if(resultDetails != NULL)
11806                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11807         }
11808         if (resultDetails != NULL) {
11809             gameInfo.result = result;
11810             gameInfo.resultDetails = StrSave(resultDetails);
11811
11812             /* display last move only if game was not loaded from file */
11813             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11814                 DisplayMove(currentMove - 1);
11815
11816             if (forwardMostMove != 0) {
11817                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11818                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11819                                                                 ) {
11820                     if (*appData.saveGameFile != NULLCHAR) {
11821                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11822                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11823                         else
11824                         SaveGameToFile(appData.saveGameFile, TRUE);
11825                     } else if (appData.autoSaveGames) {
11826                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11827                     }
11828                     if (*appData.savePositionFile != NULLCHAR) {
11829                         SavePositionToFile(appData.savePositionFile);
11830                     }
11831                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11832                 }
11833             }
11834
11835             /* Tell program how game ended in case it is learning */
11836             /* [HGM] Moved this to after saving the PGN, just in case */
11837             /* engine died and we got here through time loss. In that */
11838             /* case we will get a fatal error writing the pipe, which */
11839             /* would otherwise lose us the PGN.                       */
11840             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11841             /* output during GameEnds should never be fatal anymore   */
11842             if (gameMode == MachinePlaysWhite ||
11843                 gameMode == MachinePlaysBlack ||
11844                 gameMode == TwoMachinesPlay ||
11845                 gameMode == IcsPlayingWhite ||
11846                 gameMode == IcsPlayingBlack ||
11847                 gameMode == BeginningOfGame) {
11848                 char buf[MSG_SIZ];
11849                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11850                         resultDetails);
11851                 if (first.pr != NoProc) {
11852                     SendToProgram(buf, &first);
11853                 }
11854                 if (second.pr != NoProc &&
11855                     gameMode == TwoMachinesPlay) {
11856                     SendToProgram(buf, &second);
11857                 }
11858             }
11859         }
11860
11861         if (appData.icsActive) {
11862             if (appData.quietPlay &&
11863                 (gameMode == IcsPlayingWhite ||
11864                  gameMode == IcsPlayingBlack)) {
11865                 SendToICS(ics_prefix);
11866                 SendToICS("set shout 1\n");
11867             }
11868             nextGameMode = IcsIdle;
11869             ics_user_moved = FALSE;
11870             /* clean up premove.  It's ugly when the game has ended and the
11871              * premove highlights are still on the board.
11872              */
11873             if (gotPremove) {
11874               gotPremove = FALSE;
11875               ClearPremoveHighlights();
11876               DrawPosition(FALSE, boards[currentMove]);
11877             }
11878             if (whosays == GE_ICS) {
11879                 switch (result) {
11880                 case WhiteWins:
11881                     if (gameMode == IcsPlayingWhite)
11882                         PlayIcsWinSound();
11883                     else if(gameMode == IcsPlayingBlack)
11884                         PlayIcsLossSound();
11885                     break;
11886                 case BlackWins:
11887                     if (gameMode == IcsPlayingBlack)
11888                         PlayIcsWinSound();
11889                     else if(gameMode == IcsPlayingWhite)
11890                         PlayIcsLossSound();
11891                     break;
11892                 case GameIsDrawn:
11893                     PlayIcsDrawSound();
11894                     break;
11895                 default:
11896                     PlayIcsUnfinishedSound();
11897                 }
11898             }
11899             if(appData.quitNext) { ExitEvent(0); return; }
11900         } else if (gameMode == EditGame ||
11901                    gameMode == PlayFromGameFile ||
11902                    gameMode == AnalyzeMode ||
11903                    gameMode == AnalyzeFile) {
11904             nextGameMode = gameMode;
11905         } else {
11906             nextGameMode = EndOfGame;
11907         }
11908         pausing = FALSE;
11909         ModeHighlight();
11910     } else {
11911         nextGameMode = gameMode;
11912     }
11913
11914     if (appData.noChessProgram) {
11915         gameMode = nextGameMode;
11916         ModeHighlight();
11917         endingGame = 0; /* [HGM] crash */
11918         return;
11919     }
11920
11921     if (first.reuse) {
11922         /* Put first chess program into idle state */
11923         if (first.pr != NoProc &&
11924             (gameMode == MachinePlaysWhite ||
11925              gameMode == MachinePlaysBlack ||
11926              gameMode == TwoMachinesPlay ||
11927              gameMode == IcsPlayingWhite ||
11928              gameMode == IcsPlayingBlack ||
11929              gameMode == BeginningOfGame)) {
11930             SendToProgram("force\n", &first);
11931             if (first.usePing) {
11932               char buf[MSG_SIZ];
11933               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11934               SendToProgram(buf, &first);
11935             }
11936         }
11937     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11938         /* Kill off first chess program */
11939         if (first.isr != NULL)
11940           RemoveInputSource(first.isr);
11941         first.isr = NULL;
11942
11943         if (first.pr != NoProc) {
11944             ExitAnalyzeMode();
11945             DoSleep( appData.delayBeforeQuit );
11946             SendToProgram("quit\n", &first);
11947             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11948             first.reload = TRUE;
11949         }
11950         first.pr = NoProc;
11951     }
11952     if (second.reuse) {
11953         /* Put second chess program into idle state */
11954         if (second.pr != NoProc &&
11955             gameMode == TwoMachinesPlay) {
11956             SendToProgram("force\n", &second);
11957             if (second.usePing) {
11958               char buf[MSG_SIZ];
11959               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11960               SendToProgram(buf, &second);
11961             }
11962         }
11963     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11964         /* Kill off second chess program */
11965         if (second.isr != NULL)
11966           RemoveInputSource(second.isr);
11967         second.isr = NULL;
11968
11969         if (second.pr != NoProc) {
11970             DoSleep( appData.delayBeforeQuit );
11971             SendToProgram("quit\n", &second);
11972             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11973             second.reload = TRUE;
11974         }
11975         second.pr = NoProc;
11976     }
11977
11978     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11979         char resChar = '=';
11980         switch (result) {
11981         case WhiteWins:
11982           resChar = '+';
11983           if (first.twoMachinesColor[0] == 'w') {
11984             first.matchWins++;
11985           } else {
11986             second.matchWins++;
11987           }
11988           break;
11989         case BlackWins:
11990           resChar = '-';
11991           if (first.twoMachinesColor[0] == 'b') {
11992             first.matchWins++;
11993           } else {
11994             second.matchWins++;
11995           }
11996           break;
11997         case GameUnfinished:
11998           resChar = ' ';
11999         default:
12000           break;
12001         }
12002
12003         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
12004         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
12005             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
12006             ReserveGame(nextGame, resChar); // sets nextGame
12007             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
12008             else ranking = strdup("busy"); //suppress popup when aborted but not finished
12009         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
12010
12011         if (nextGame <= appData.matchGames && !abortMatch) {
12012             gameMode = nextGameMode;
12013             matchGame = nextGame; // this will be overruled in tourney mode!
12014             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
12015             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
12016             endingGame = 0; /* [HGM] crash */
12017             return;
12018         } else {
12019             gameMode = nextGameMode;
12020             if(appData.epd) {
12021                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
12022                 OutputKibitz(2, buf);
12023                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
12024                 OutputKibitz(2, buf);
12025                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
12026                 if(second.matchWins) OutputKibitz(2, buf);
12027                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
12028                 OutputKibitz(2, buf);
12029             }
12030             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
12031                      first.tidy, second.tidy,
12032                      first.matchWins, second.matchWins,
12033                      appData.matchGames - (first.matchWins + second.matchWins));
12034             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
12035             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
12036             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
12037             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
12038                 first.twoMachinesColor = "black\n";
12039                 second.twoMachinesColor = "white\n";
12040             } else {
12041                 first.twoMachinesColor = "white\n";
12042                 second.twoMachinesColor = "black\n";
12043             }
12044         }
12045     }
12046     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
12047         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
12048       ExitAnalyzeMode();
12049     gameMode = nextGameMode;
12050     ModeHighlight();
12051     endingGame = 0;  /* [HGM] crash */
12052     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12053         if(matchMode == TRUE) { // match through command line: exit with or without popup
12054             if(ranking) {
12055                 ToNrEvent(forwardMostMove);
12056                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12057                 else ExitEvent(0);
12058             } else DisplayFatalError(buf, 0, 0);
12059         } else { // match through menu; just stop, with or without popup
12060             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12061             ModeHighlight();
12062             if(ranking){
12063                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12064             } else DisplayNote(buf);
12065       }
12066       if(ranking) free(ranking);
12067     }
12068 }
12069
12070 /* Assumes program was just initialized (initString sent).
12071    Leaves program in force mode. */
12072 void
12073 FeedMovesToProgram (ChessProgramState *cps, int upto)
12074 {
12075     int i;
12076
12077     if (appData.debugMode)
12078       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12079               startedFromSetupPosition ? "position and " : "",
12080               backwardMostMove, upto, cps->which);
12081     if(currentlyInitializedVariant != gameInfo.variant) {
12082       char buf[MSG_SIZ];
12083         // [HGM] variantswitch: make engine aware of new variant
12084         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12085                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12086                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12087         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12088         SendToProgram(buf, cps);
12089         currentlyInitializedVariant = gameInfo.variant;
12090     }
12091     SendToProgram("force\n", cps);
12092     if (startedFromSetupPosition) {
12093         SendBoard(cps, backwardMostMove);
12094     if (appData.debugMode) {
12095         fprintf(debugFP, "feedMoves\n");
12096     }
12097     }
12098     for (i = backwardMostMove; i < upto; i++) {
12099         SendMoveToProgram(i, cps);
12100     }
12101 }
12102
12103
12104 int
12105 ResurrectChessProgram ()
12106 {
12107      /* The chess program may have exited.
12108         If so, restart it and feed it all the moves made so far. */
12109     static int doInit = 0;
12110
12111     if (appData.noChessProgram) return 1;
12112
12113     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12114         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12115         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12116         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12117     } else {
12118         if (first.pr != NoProc) return 1;
12119         StartChessProgram(&first);
12120     }
12121     InitChessProgram(&first, FALSE);
12122     FeedMovesToProgram(&first, currentMove);
12123
12124     if (!first.sendTime) {
12125         /* can't tell gnuchess what its clock should read,
12126            so we bow to its notion. */
12127         ResetClocks();
12128         timeRemaining[0][currentMove] = whiteTimeRemaining;
12129         timeRemaining[1][currentMove] = blackTimeRemaining;
12130     }
12131
12132     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12133                 appData.icsEngineAnalyze) && first.analysisSupport) {
12134       SendToProgram("analyze\n", &first);
12135       first.analyzing = TRUE;
12136     }
12137     return 1;
12138 }
12139
12140 /*
12141  * Button procedures
12142  */
12143 void
12144 Reset (int redraw, int init)
12145 {
12146     int i;
12147
12148     if (appData.debugMode) {
12149         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12150                 redraw, init, gameMode);
12151     }
12152     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12153     deadRanks = 0; // assume entire board is used
12154     handSize = 0;
12155     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12156     CleanupTail(); // [HGM] vari: delete any stored variations
12157     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12158     pausing = pauseExamInvalid = FALSE;
12159     startedFromSetupPosition = blackPlaysFirst = FALSE;
12160     firstMove = TRUE;
12161     whiteFlag = blackFlag = FALSE;
12162     userOfferedDraw = FALSE;
12163     hintRequested = bookRequested = FALSE;
12164     first.maybeThinking = FALSE;
12165     second.maybeThinking = FALSE;
12166     first.bookSuspend = FALSE; // [HGM] book
12167     second.bookSuspend = FALSE;
12168     thinkOutput[0] = NULLCHAR;
12169     lastHint[0] = NULLCHAR;
12170     ClearGameInfo(&gameInfo);
12171     gameInfo.variant = StringToVariant(appData.variant);
12172     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12173         gameInfo.variant = VariantUnknown;
12174         strncpy(engineVariant, appData.variant, MSG_SIZ);
12175     }
12176     ics_user_moved = ics_clock_paused = FALSE;
12177     ics_getting_history = H_FALSE;
12178     ics_gamenum = -1;
12179     white_holding[0] = black_holding[0] = NULLCHAR;
12180     ClearProgramStats();
12181     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12182
12183     ResetFrontEnd();
12184     ClearHighlights();
12185     flipView = appData.flipView;
12186     ClearPremoveHighlights();
12187     gotPremove = FALSE;
12188     alarmSounded = FALSE;
12189     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12190
12191     GameEnds(EndOfFile, NULL, GE_PLAYER);
12192     if(appData.serverMovesName != NULL) {
12193         /* [HGM] prepare to make moves file for broadcasting */
12194         clock_t t = clock();
12195         if(serverMoves != NULL) fclose(serverMoves);
12196         serverMoves = fopen(appData.serverMovesName, "r");
12197         if(serverMoves != NULL) {
12198             fclose(serverMoves);
12199             /* delay 15 sec before overwriting, so all clients can see end */
12200             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12201         }
12202         serverMoves = fopen(appData.serverMovesName, "w");
12203     }
12204
12205     ExitAnalyzeMode();
12206     gameMode = BeginningOfGame;
12207     ModeHighlight();
12208     if(appData.icsActive) gameInfo.variant = VariantNormal;
12209     currentMove = forwardMostMove = backwardMostMove = 0;
12210     MarkTargetSquares(1);
12211     InitPosition(redraw);
12212     for (i = 0; i < MAX_MOVES; i++) {
12213         if (commentList[i] != NULL) {
12214             free(commentList[i]);
12215             commentList[i] = NULL;
12216         }
12217     }
12218     ResetClocks();
12219     timeRemaining[0][0] = whiteTimeRemaining;
12220     timeRemaining[1][0] = blackTimeRemaining;
12221
12222     if (first.pr == NoProc) {
12223         StartChessProgram(&first);
12224     }
12225     if (init) {
12226             InitChessProgram(&first, startedFromSetupPosition);
12227     }
12228     DisplayTitle("");
12229     DisplayMessage("", "");
12230     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12231     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12232     ClearMap();        // [HGM] exclude: invalidate map
12233 }
12234
12235 void
12236 AutoPlayGameLoop ()
12237 {
12238     for (;;) {
12239         if (!AutoPlayOneMove())
12240           return;
12241         if (matchMode || appData.timeDelay == 0)
12242           continue;
12243         if (appData.timeDelay < 0)
12244           return;
12245         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12246         break;
12247     }
12248 }
12249
12250 void
12251 AnalyzeNextGame()
12252 {
12253     ReloadGame(1); // next game
12254 }
12255
12256 int
12257 AutoPlayOneMove ()
12258 {
12259     int fromX, fromY, toX, toY;
12260
12261     if (appData.debugMode) {
12262       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12263     }
12264
12265     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12266       return FALSE;
12267
12268     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12269       pvInfoList[currentMove].depth = programStats.depth;
12270       pvInfoList[currentMove].score = programStats.score;
12271       pvInfoList[currentMove].time  = 0;
12272       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12273       else { // append analysis of final position as comment
12274         char buf[MSG_SIZ];
12275         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12276         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12277       }
12278       programStats.depth = 0;
12279     }
12280
12281     if (currentMove >= forwardMostMove) {
12282       if(gameMode == AnalyzeFile) {
12283           if(appData.loadGameIndex == -1) {
12284             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12285           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12286           } else {
12287           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12288         }
12289       }
12290 //      gameMode = EndOfGame;
12291 //      ModeHighlight();
12292
12293       /* [AS] Clear current move marker at the end of a game */
12294       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12295
12296       return FALSE;
12297     }
12298
12299     toX = moveList[currentMove][2] - AAA;
12300     toY = moveList[currentMove][3] - ONE;
12301
12302     if (moveList[currentMove][1] == '@') {
12303         if (appData.highlightLastMove) {
12304             SetHighlights(-1, -1, toX, toY);
12305         }
12306     } else {
12307         fromX = moveList[currentMove][0] - AAA;
12308         fromY = moveList[currentMove][1] - ONE;
12309
12310         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12311
12312         if(moveList[currentMove][4] == ';') { // multi-leg
12313             killX = moveList[currentMove][5] - AAA;
12314             killY = moveList[currentMove][6] - ONE;
12315         }
12316         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12317         killX = killY = -1;
12318
12319         if (appData.highlightLastMove) {
12320             SetHighlights(fromX, fromY, toX, toY);
12321         }
12322     }
12323     DisplayMove(currentMove);
12324     SendMoveToProgram(currentMove++, &first);
12325     DisplayBothClocks();
12326     DrawPosition(FALSE, boards[currentMove]);
12327     // [HGM] PV info: always display, routine tests if empty
12328     DisplayComment(currentMove - 1, commentList[currentMove]);
12329     return TRUE;
12330 }
12331
12332
12333 int
12334 LoadGameOneMove (ChessMove readAhead)
12335 {
12336     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12337     char promoChar = NULLCHAR;
12338     ChessMove moveType;
12339     char move[MSG_SIZ];
12340     char *p, *q;
12341
12342     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12343         gameMode != AnalyzeMode && gameMode != Training) {
12344         gameFileFP = NULL;
12345         return FALSE;
12346     }
12347
12348     yyboardindex = forwardMostMove;
12349     if (readAhead != EndOfFile) {
12350       moveType = readAhead;
12351     } else {
12352       if (gameFileFP == NULL)
12353           return FALSE;
12354       moveType = (ChessMove) Myylex();
12355     }
12356
12357     done = FALSE;
12358     switch (moveType) {
12359       case Comment:
12360         if (appData.debugMode)
12361           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12362         p = yy_text;
12363
12364         /* append the comment but don't display it */
12365         AppendComment(currentMove, p, FALSE);
12366         return TRUE;
12367
12368       case WhiteCapturesEnPassant:
12369       case BlackCapturesEnPassant:
12370       case WhitePromotion:
12371       case BlackPromotion:
12372       case WhiteNonPromotion:
12373       case BlackNonPromotion:
12374       case NormalMove:
12375       case FirstLeg:
12376       case WhiteKingSideCastle:
12377       case WhiteQueenSideCastle:
12378       case BlackKingSideCastle:
12379       case BlackQueenSideCastle:
12380       case WhiteKingSideCastleWild:
12381       case WhiteQueenSideCastleWild:
12382       case BlackKingSideCastleWild:
12383       case BlackQueenSideCastleWild:
12384       /* PUSH Fabien */
12385       case WhiteHSideCastleFR:
12386       case WhiteASideCastleFR:
12387       case BlackHSideCastleFR:
12388       case BlackASideCastleFR:
12389       /* POP Fabien */
12390         if (appData.debugMode)
12391           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12392         fromX = currentMoveString[0] - AAA;
12393         fromY = currentMoveString[1] - ONE;
12394         toX = currentMoveString[2] - AAA;
12395         toY = currentMoveString[3] - ONE;
12396         promoChar = currentMoveString[4];
12397         if(promoChar == ';') promoChar = currentMoveString[7];
12398         break;
12399
12400       case WhiteDrop:
12401       case BlackDrop:
12402         if (appData.debugMode)
12403           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12404         fromX = moveType == WhiteDrop ?
12405           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12406         (int) CharToPiece(ToLower(currentMoveString[0]));
12407         fromY = DROP_RANK;
12408         toX = currentMoveString[2] - AAA;
12409         toY = currentMoveString[3] - ONE;
12410         break;
12411
12412       case WhiteWins:
12413       case BlackWins:
12414       case GameIsDrawn:
12415       case GameUnfinished:
12416         if (appData.debugMode)
12417           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12418         p = strchr(yy_text, '{');
12419         if (p == NULL) p = strchr(yy_text, '(');
12420         if (p == NULL) {
12421             p = yy_text;
12422             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12423         } else {
12424             q = strchr(p, *p == '{' ? '}' : ')');
12425             if (q != NULL) *q = NULLCHAR;
12426             p++;
12427         }
12428         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12429         GameEnds(moveType, p, GE_FILE);
12430         done = TRUE;
12431         if (cmailMsgLoaded) {
12432             ClearHighlights();
12433             flipView = WhiteOnMove(currentMove);
12434             if (moveType == GameUnfinished) flipView = !flipView;
12435             if (appData.debugMode)
12436               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12437         }
12438         break;
12439
12440       case EndOfFile:
12441         if (appData.debugMode)
12442           fprintf(debugFP, "Parser hit end of file\n");
12443         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12444           case MT_NONE:
12445           case MT_CHECK:
12446             break;
12447           case MT_CHECKMATE:
12448           case MT_STAINMATE:
12449             if (WhiteOnMove(currentMove)) {
12450                 GameEnds(BlackWins, "Black mates", GE_FILE);
12451             } else {
12452                 GameEnds(WhiteWins, "White mates", GE_FILE);
12453             }
12454             break;
12455           case MT_STALEMATE:
12456             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12457             break;
12458         }
12459         done = TRUE;
12460         break;
12461
12462       case MoveNumberOne:
12463         if (lastLoadGameStart == GNUChessGame) {
12464             /* GNUChessGames have numbers, but they aren't move numbers */
12465             if (appData.debugMode)
12466               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12467                       yy_text, (int) moveType);
12468             return LoadGameOneMove(EndOfFile); /* tail recursion */
12469         }
12470         /* else fall thru */
12471
12472       case XBoardGame:
12473       case GNUChessGame:
12474       case PGNTag:
12475         /* Reached start of next game in file */
12476         if (appData.debugMode)
12477           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12478         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12479           case MT_NONE:
12480           case MT_CHECK:
12481             break;
12482           case MT_CHECKMATE:
12483           case MT_STAINMATE:
12484             if (WhiteOnMove(currentMove)) {
12485                 GameEnds(BlackWins, "Black mates", GE_FILE);
12486             } else {
12487                 GameEnds(WhiteWins, "White mates", GE_FILE);
12488             }
12489             break;
12490           case MT_STALEMATE:
12491             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12492             break;
12493         }
12494         done = TRUE;
12495         break;
12496
12497       case PositionDiagram:     /* should not happen; ignore */
12498       case ElapsedTime:         /* ignore */
12499       case NAG:                 /* ignore */
12500         if (appData.debugMode)
12501           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12502                   yy_text, (int) moveType);
12503         return LoadGameOneMove(EndOfFile); /* tail recursion */
12504
12505       case IllegalMove:
12506         if (appData.testLegality) {
12507             if (appData.debugMode)
12508               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12509             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12510                     (forwardMostMove / 2) + 1,
12511                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12512             DisplayError(move, 0);
12513             done = TRUE;
12514         } else {
12515             if (appData.debugMode)
12516               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12517                       yy_text, currentMoveString);
12518             if(currentMoveString[1] == '@') {
12519                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12520                 fromY = DROP_RANK;
12521             } else {
12522                 fromX = currentMoveString[0] - AAA;
12523                 fromY = currentMoveString[1] - ONE;
12524             }
12525             toX = currentMoveString[2] - AAA;
12526             toY = currentMoveString[3] - ONE;
12527             promoChar = currentMoveString[4];
12528         }
12529         break;
12530
12531       case AmbiguousMove:
12532         if (appData.debugMode)
12533           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12534         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12535                 (forwardMostMove / 2) + 1,
12536                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12537         DisplayError(move, 0);
12538         done = TRUE;
12539         break;
12540
12541       default:
12542       case ImpossibleMove:
12543         if (appData.debugMode)
12544           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12545         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12546                 (forwardMostMove / 2) + 1,
12547                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12548         DisplayError(move, 0);
12549         done = TRUE;
12550         break;
12551     }
12552
12553     if (done) {
12554         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12555             DrawPosition(FALSE, boards[currentMove]);
12556             DisplayBothClocks();
12557             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12558               DisplayComment(currentMove - 1, commentList[currentMove]);
12559         }
12560         (void) StopLoadGameTimer();
12561         gameFileFP = NULL;
12562         cmailOldMove = forwardMostMove;
12563         return FALSE;
12564     } else {
12565         /* currentMoveString is set as a side-effect of yylex */
12566
12567         thinkOutput[0] = NULLCHAR;
12568         MakeMove(fromX, fromY, toX, toY, promoChar);
12569         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12570         currentMove = forwardMostMove;
12571         return TRUE;
12572     }
12573 }
12574
12575 /* Load the nth game from the given file */
12576 int
12577 LoadGameFromFile (char *filename, int n, char *title, int useList)
12578 {
12579     FILE *f;
12580     char buf[MSG_SIZ];
12581
12582     if (strcmp(filename, "-") == 0) {
12583         f = stdin;
12584         title = "stdin";
12585     } else {
12586         f = fopen(filename, "rb");
12587         if (f == NULL) {
12588           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12589             DisplayError(buf, errno);
12590             return FALSE;
12591         }
12592     }
12593     if (fseek(f, 0, 0) == -1) {
12594         /* f is not seekable; probably a pipe */
12595         useList = FALSE;
12596     }
12597     if (useList && n == 0) {
12598         int error = GameListBuild(f);
12599         if (error) {
12600             DisplayError(_("Cannot build game list"), error);
12601         } else if (!ListEmpty(&gameList) &&
12602                    ((ListGame *) gameList.tailPred)->number > 1) {
12603             GameListPopUp(f, title);
12604             return TRUE;
12605         }
12606         GameListDestroy();
12607         n = 1;
12608     }
12609     if (n == 0) n = 1;
12610     return LoadGame(f, n, title, FALSE);
12611 }
12612
12613
12614 void
12615 MakeRegisteredMove ()
12616 {
12617     int fromX, fromY, toX, toY;
12618     char promoChar;
12619     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12620         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12621           case CMAIL_MOVE:
12622           case CMAIL_DRAW:
12623             if (appData.debugMode)
12624               fprintf(debugFP, "Restoring %s for game %d\n",
12625                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12626
12627             thinkOutput[0] = NULLCHAR;
12628             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12629             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12630             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12631             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12632             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12633             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12634             MakeMove(fromX, fromY, toX, toY, promoChar);
12635             ShowMove(fromX, fromY, toX, toY);
12636
12637             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12638               case MT_NONE:
12639               case MT_CHECK:
12640                 break;
12641
12642               case MT_CHECKMATE:
12643               case MT_STAINMATE:
12644                 if (WhiteOnMove(currentMove)) {
12645                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12646                 } else {
12647                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12648                 }
12649                 break;
12650
12651               case MT_STALEMATE:
12652                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12653                 break;
12654             }
12655
12656             break;
12657
12658           case CMAIL_RESIGN:
12659             if (WhiteOnMove(currentMove)) {
12660                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12661             } else {
12662                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12663             }
12664             break;
12665
12666           case CMAIL_ACCEPT:
12667             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12668             break;
12669
12670           default:
12671             break;
12672         }
12673     }
12674
12675     return;
12676 }
12677
12678 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12679 int
12680 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12681 {
12682     int retVal;
12683
12684     if (gameNumber > nCmailGames) {
12685         DisplayError(_("No more games in this message"), 0);
12686         return FALSE;
12687     }
12688     if (f == lastLoadGameFP) {
12689         int offset = gameNumber - lastLoadGameNumber;
12690         if (offset == 0) {
12691             cmailMsg[0] = NULLCHAR;
12692             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12693                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12694                 nCmailMovesRegistered--;
12695             }
12696             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12697             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12698                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12699             }
12700         } else {
12701             if (! RegisterMove()) return FALSE;
12702         }
12703     }
12704
12705     retVal = LoadGame(f, gameNumber, title, useList);
12706
12707     /* Make move registered during previous look at this game, if any */
12708     MakeRegisteredMove();
12709
12710     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12711         commentList[currentMove]
12712           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12713         DisplayComment(currentMove - 1, commentList[currentMove]);
12714     }
12715
12716     return retVal;
12717 }
12718
12719 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12720 int
12721 ReloadGame (int offset)
12722 {
12723     int gameNumber = lastLoadGameNumber + offset;
12724     if (lastLoadGameFP == NULL) {
12725         DisplayError(_("No game has been loaded yet"), 0);
12726         return FALSE;
12727     }
12728     if (gameNumber <= 0) {
12729         DisplayError(_("Can't back up any further"), 0);
12730         return FALSE;
12731     }
12732     if (cmailMsgLoaded) {
12733         return CmailLoadGame(lastLoadGameFP, gameNumber,
12734                              lastLoadGameTitle, lastLoadGameUseList);
12735     } else {
12736         return LoadGame(lastLoadGameFP, gameNumber,
12737                         lastLoadGameTitle, lastLoadGameUseList);
12738     }
12739 }
12740
12741 int keys[EmptySquare+1];
12742
12743 int
12744 PositionMatches (Board b1, Board b2)
12745 {
12746     int r, f, sum=0;
12747     switch(appData.searchMode) {
12748         case 1: return CompareWithRights(b1, b2);
12749         case 2:
12750             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12751                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12752             }
12753             return TRUE;
12754         case 3:
12755             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12756               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12757                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12758             }
12759             return sum==0;
12760         case 4:
12761             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12762                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12763             }
12764             return sum==0;
12765     }
12766     return TRUE;
12767 }
12768
12769 #define Q_PROMO  4
12770 #define Q_EP     3
12771 #define Q_BCASTL 2
12772 #define Q_WCASTL 1
12773
12774 int pieceList[256], quickBoard[256];
12775 ChessSquare pieceType[256] = { EmptySquare };
12776 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12777 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12778 int soughtTotal, turn;
12779 Boolean epOK, flipSearch;
12780
12781 typedef struct {
12782     unsigned char piece, to;
12783 } Move;
12784
12785 #define DSIZE (250000)
12786
12787 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12788 Move *moveDatabase = initialSpace;
12789 unsigned int movePtr, dataSize = DSIZE;
12790
12791 int
12792 MakePieceList (Board board, int *counts)
12793 {
12794     int r, f, n=Q_PROMO, total=0;
12795     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12796     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12797         int sq = f + (r<<4);
12798         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12799             quickBoard[sq] = ++n;
12800             pieceList[n] = sq;
12801             pieceType[n] = board[r][f];
12802             counts[board[r][f]]++;
12803             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12804             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12805             total++;
12806         }
12807     }
12808     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12809     return total;
12810 }
12811
12812 void
12813 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12814 {
12815     int sq = fromX + (fromY<<4);
12816     int piece = quickBoard[sq], rook;
12817     quickBoard[sq] = 0;
12818     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12819     if(piece == pieceList[1] && fromY == toY) {
12820       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12821         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12822         moveDatabase[movePtr++].piece = Q_WCASTL;
12823         quickBoard[sq] = piece;
12824         piece = quickBoard[from]; quickBoard[from] = 0;
12825         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12826       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12827         quickBoard[sq] = 0; // remove Rook
12828         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12829         moveDatabase[movePtr++].piece = Q_WCASTL;
12830         quickBoard[sq] = pieceList[1]; // put King
12831         piece = rook;
12832         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12833       }
12834     } else
12835     if(piece == pieceList[2] && fromY == toY) {
12836       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12837         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12838         moveDatabase[movePtr++].piece = Q_BCASTL;
12839         quickBoard[sq] = piece;
12840         piece = quickBoard[from]; quickBoard[from] = 0;
12841         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12842       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12843         quickBoard[sq] = 0; // remove Rook
12844         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12845         moveDatabase[movePtr++].piece = Q_BCASTL;
12846         quickBoard[sq] = pieceList[2]; // put King
12847         piece = rook;
12848         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12849       }
12850     } else
12851     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12852         quickBoard[(fromY<<4)+toX] = 0;
12853         moveDatabase[movePtr].piece = Q_EP;
12854         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12855         moveDatabase[movePtr].to = sq;
12856     } else
12857     if(promoPiece != pieceType[piece]) {
12858         moveDatabase[movePtr++].piece = Q_PROMO;
12859         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12860     }
12861     moveDatabase[movePtr].piece = piece;
12862     quickBoard[sq] = piece;
12863     movePtr++;
12864 }
12865
12866 int
12867 PackGame (Board board)
12868 {
12869     Move *newSpace = NULL;
12870     moveDatabase[movePtr].piece = 0; // terminate previous game
12871     if(movePtr > dataSize) {
12872         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12873         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12874         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12875         if(newSpace) {
12876             int i;
12877             Move *p = moveDatabase, *q = newSpace;
12878             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12879             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12880             moveDatabase = newSpace;
12881         } else { // calloc failed, we must be out of memory. Too bad...
12882             dataSize = 0; // prevent calloc events for all subsequent games
12883             return 0;     // and signal this one isn't cached
12884         }
12885     }
12886     movePtr++;
12887     MakePieceList(board, counts);
12888     return movePtr;
12889 }
12890
12891 int
12892 QuickCompare (Board board, int *minCounts, int *maxCounts)
12893 {   // compare according to search mode
12894     int r, f;
12895     switch(appData.searchMode)
12896     {
12897       case 1: // exact position match
12898         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12899         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12900             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12901         }
12902         break;
12903       case 2: // can have extra material on empty squares
12904         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12905             if(board[r][f] == EmptySquare) continue;
12906             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12907         }
12908         break;
12909       case 3: // material with exact Pawn structure
12910         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12911             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12912             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12913         } // fall through to material comparison
12914       case 4: // exact material
12915         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12916         break;
12917       case 6: // material range with given imbalance
12918         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12919         // fall through to range comparison
12920       case 5: // material range
12921         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12922     }
12923     return TRUE;
12924 }
12925
12926 int
12927 QuickScan (Board board, Move *move)
12928 {   // reconstruct game,and compare all positions in it
12929     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12930     do {
12931         int piece = move->piece;
12932         int to = move->to, from = pieceList[piece];
12933         if(found < 0) { // if already found just scan to game end for final piece count
12934           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12935            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12936            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12937                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12938             ) {
12939             static int lastCounts[EmptySquare+1];
12940             int i;
12941             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12942             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12943           } else stretch = 0;
12944           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12945           if(found >= 0 && !appData.minPieces) return found;
12946         }
12947         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12948           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12949           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12950             piece = (++move)->piece;
12951             from = pieceList[piece];
12952             counts[pieceType[piece]]--;
12953             pieceType[piece] = (ChessSquare) move->to;
12954             counts[move->to]++;
12955           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12956             counts[pieceType[quickBoard[to]]]--;
12957             quickBoard[to] = 0; total--;
12958             move++;
12959             continue;
12960           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12961             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12962             from  = pieceList[piece]; // so this must be King
12963             quickBoard[from] = 0;
12964             pieceList[piece] = to;
12965             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12966             quickBoard[from] = 0; // rook
12967             quickBoard[to] = piece;
12968             to = move->to; piece = move->piece;
12969             goto aftercastle;
12970           }
12971         }
12972         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12973         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12974         quickBoard[from] = 0;
12975       aftercastle:
12976         quickBoard[to] = piece;
12977         pieceList[piece] = to;
12978         cnt++; turn ^= 3;
12979         move++;
12980     } while(1);
12981 }
12982
12983 void
12984 InitSearch ()
12985 {
12986     int r, f;
12987     flipSearch = FALSE;
12988     CopyBoard(soughtBoard, boards[currentMove]);
12989     soughtTotal = MakePieceList(soughtBoard, maxSought);
12990     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12991     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12992     CopyBoard(reverseBoard, boards[currentMove]);
12993     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12994         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12995         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12996         reverseBoard[r][f] = piece;
12997     }
12998     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12999     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
13000     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
13001                  || (boards[currentMove][CASTLING][2] == NoRights ||
13002                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
13003                  && (boards[currentMove][CASTLING][5] == NoRights ||
13004                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
13005       ) {
13006         flipSearch = TRUE;
13007         CopyBoard(flipBoard, soughtBoard);
13008         CopyBoard(rotateBoard, reverseBoard);
13009         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13010             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
13011             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
13012         }
13013     }
13014     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
13015     if(appData.searchMode >= 5) {
13016         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
13017         MakePieceList(soughtBoard, minSought);
13018         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
13019     }
13020     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
13021         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
13022 }
13023
13024 GameInfo dummyInfo;
13025 static int creatingBook;
13026
13027 int
13028 GameContainsPosition (FILE *f, ListGame *lg)
13029 {
13030     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
13031     int fromX, fromY, toX, toY;
13032     char promoChar;
13033     static int initDone=FALSE;
13034
13035     // weed out games based on numerical tag comparison
13036     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
13037     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
13038     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
13039     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
13040     if(!initDone) {
13041         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
13042         initDone = TRUE;
13043     }
13044     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
13045     else CopyBoard(boards[scratch], initialPosition); // default start position
13046     if(lg->moves) {
13047         turn = btm + 1;
13048         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
13049         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13050     }
13051     if(btm) plyNr++;
13052     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13053     fseek(f, lg->offset, 0);
13054     yynewfile(f);
13055     while(1) {
13056         yyboardindex = scratch;
13057         quickFlag = plyNr+1;
13058         next = Myylex();
13059         quickFlag = 0;
13060         switch(next) {
13061             case PGNTag:
13062                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13063             default:
13064                 continue;
13065
13066             case XBoardGame:
13067             case GNUChessGame:
13068                 if(plyNr) return -1; // after we have seen moves, this is for new game
13069               continue;
13070
13071             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13072             case ImpossibleMove:
13073             case WhiteWins: // game ends here with these four
13074             case BlackWins:
13075             case GameIsDrawn:
13076             case GameUnfinished:
13077                 return -1;
13078
13079             case IllegalMove:
13080                 if(appData.testLegality) return -1;
13081             case WhiteCapturesEnPassant:
13082             case BlackCapturesEnPassant:
13083             case WhitePromotion:
13084             case BlackPromotion:
13085             case WhiteNonPromotion:
13086             case BlackNonPromotion:
13087             case NormalMove:
13088             case FirstLeg:
13089             case WhiteKingSideCastle:
13090             case WhiteQueenSideCastle:
13091             case BlackKingSideCastle:
13092             case BlackQueenSideCastle:
13093             case WhiteKingSideCastleWild:
13094             case WhiteQueenSideCastleWild:
13095             case BlackKingSideCastleWild:
13096             case BlackQueenSideCastleWild:
13097             case WhiteHSideCastleFR:
13098             case WhiteASideCastleFR:
13099             case BlackHSideCastleFR:
13100             case BlackASideCastleFR:
13101                 fromX = currentMoveString[0] - AAA;
13102                 fromY = currentMoveString[1] - ONE;
13103                 toX = currentMoveString[2] - AAA;
13104                 toY = currentMoveString[3] - ONE;
13105                 promoChar = currentMoveString[4];
13106                 break;
13107             case WhiteDrop:
13108             case BlackDrop:
13109                 fromX = next == WhiteDrop ?
13110                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13111                   (int) CharToPiece(ToLower(currentMoveString[0]));
13112                 fromY = DROP_RANK;
13113                 toX = currentMoveString[2] - AAA;
13114                 toY = currentMoveString[3] - ONE;
13115                 promoChar = 0;
13116                 break;
13117         }
13118         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13119         plyNr++;
13120         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13121         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13122         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13123         if(appData.findMirror) {
13124             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13125             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13126         }
13127     }
13128 }
13129
13130 /* Load the nth game from open file f */
13131 int
13132 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13133 {
13134     ChessMove cm;
13135     char buf[MSG_SIZ];
13136     int gn = gameNumber;
13137     ListGame *lg = NULL;
13138     int numPGNTags = 0, i;
13139     int err, pos = -1;
13140     GameMode oldGameMode;
13141     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13142     char oldName[MSG_SIZ];
13143
13144     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13145
13146     if (appData.debugMode)
13147         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13148
13149     if (gameMode == Training )
13150         SetTrainingModeOff();
13151
13152     oldGameMode = gameMode;
13153     if (gameMode != BeginningOfGame) {
13154       Reset(FALSE, TRUE);
13155     }
13156     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13157
13158     gameFileFP = f;
13159     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13160         fclose(lastLoadGameFP);
13161     }
13162
13163     if (useList) {
13164         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13165
13166         if (lg) {
13167             fseek(f, lg->offset, 0);
13168             GameListHighlight(gameNumber);
13169             pos = lg->position;
13170             gn = 1;
13171         }
13172         else {
13173             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13174               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13175             else
13176             DisplayError(_("Game number out of range"), 0);
13177             return FALSE;
13178         }
13179     } else {
13180         GameListDestroy();
13181         if (fseek(f, 0, 0) == -1) {
13182             if (f == lastLoadGameFP ?
13183                 gameNumber == lastLoadGameNumber + 1 :
13184                 gameNumber == 1) {
13185                 gn = 1;
13186             } else {
13187                 DisplayError(_("Can't seek on game file"), 0);
13188                 return FALSE;
13189             }
13190         }
13191     }
13192     lastLoadGameFP = f;
13193     lastLoadGameNumber = gameNumber;
13194     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13195     lastLoadGameUseList = useList;
13196
13197     yynewfile(f);
13198
13199     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13200       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13201                 lg->gameInfo.black);
13202             DisplayTitle(buf);
13203     } else if (*title != NULLCHAR) {
13204         if (gameNumber > 1) {
13205           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13206             DisplayTitle(buf);
13207         } else {
13208             DisplayTitle(title);
13209         }
13210     }
13211
13212     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13213         gameMode = PlayFromGameFile;
13214         ModeHighlight();
13215     }
13216
13217     currentMove = forwardMostMove = backwardMostMove = 0;
13218     CopyBoard(boards[0], initialPosition);
13219     StopClocks();
13220
13221     /*
13222      * Skip the first gn-1 games in the file.
13223      * Also skip over anything that precedes an identifiable
13224      * start of game marker, to avoid being confused by
13225      * garbage at the start of the file.  Currently
13226      * recognized start of game markers are the move number "1",
13227      * the pattern "gnuchess .* game", the pattern
13228      * "^[#;%] [^ ]* game file", and a PGN tag block.
13229      * A game that starts with one of the latter two patterns
13230      * will also have a move number 1, possibly
13231      * following a position diagram.
13232      * 5-4-02: Let's try being more lenient and allowing a game to
13233      * start with an unnumbered move.  Does that break anything?
13234      */
13235     cm = lastLoadGameStart = EndOfFile;
13236     while (gn > 0) {
13237         yyboardindex = forwardMostMove;
13238         cm = (ChessMove) Myylex();
13239         switch (cm) {
13240           case EndOfFile:
13241             if (cmailMsgLoaded) {
13242                 nCmailGames = CMAIL_MAX_GAMES - gn;
13243             } else {
13244                 Reset(TRUE, TRUE);
13245                 DisplayError(_("Game not found in file"), 0);
13246             }
13247             return FALSE;
13248
13249           case GNUChessGame:
13250           case XBoardGame:
13251             gn--;
13252             lastLoadGameStart = cm;
13253             break;
13254
13255           case MoveNumberOne:
13256             switch (lastLoadGameStart) {
13257               case GNUChessGame:
13258               case XBoardGame:
13259               case PGNTag:
13260                 break;
13261               case MoveNumberOne:
13262               case EndOfFile:
13263                 gn--;           /* count this game */
13264                 lastLoadGameStart = cm;
13265                 break;
13266               default:
13267                 /* impossible */
13268                 break;
13269             }
13270             break;
13271
13272           case PGNTag:
13273             switch (lastLoadGameStart) {
13274               case GNUChessGame:
13275               case PGNTag:
13276               case MoveNumberOne:
13277               case EndOfFile:
13278                 gn--;           /* count this game */
13279                 lastLoadGameStart = cm;
13280                 break;
13281               case XBoardGame:
13282                 lastLoadGameStart = cm; /* game counted already */
13283                 break;
13284               default:
13285                 /* impossible */
13286                 break;
13287             }
13288             if (gn > 0) {
13289                 do {
13290                     yyboardindex = forwardMostMove;
13291                     cm = (ChessMove) Myylex();
13292                 } while (cm == PGNTag || cm == Comment);
13293             }
13294             break;
13295
13296           case WhiteWins:
13297           case BlackWins:
13298           case GameIsDrawn:
13299             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13300                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13301                     != CMAIL_OLD_RESULT) {
13302                     nCmailResults ++ ;
13303                     cmailResult[  CMAIL_MAX_GAMES
13304                                 - gn - 1] = CMAIL_OLD_RESULT;
13305                 }
13306             }
13307             break;
13308
13309           case NormalMove:
13310           case FirstLeg:
13311             /* Only a NormalMove can be at the start of a game
13312              * without a position diagram. */
13313             if (lastLoadGameStart == EndOfFile ) {
13314               gn--;
13315               lastLoadGameStart = MoveNumberOne;
13316             }
13317             break;
13318
13319           default:
13320             break;
13321         }
13322     }
13323
13324     if (appData.debugMode)
13325       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13326
13327     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13328
13329     if (cm == XBoardGame) {
13330         /* Skip any header junk before position diagram and/or move 1 */
13331         for (;;) {
13332             yyboardindex = forwardMostMove;
13333             cm = (ChessMove) Myylex();
13334
13335             if (cm == EndOfFile ||
13336                 cm == GNUChessGame || cm == XBoardGame) {
13337                 /* Empty game; pretend end-of-file and handle later */
13338                 cm = EndOfFile;
13339                 break;
13340             }
13341
13342             if (cm == MoveNumberOne || cm == PositionDiagram ||
13343                 cm == PGNTag || cm == Comment)
13344               break;
13345         }
13346     } else if (cm == GNUChessGame) {
13347         if (gameInfo.event != NULL) {
13348             free(gameInfo.event);
13349         }
13350         gameInfo.event = StrSave(yy_text);
13351     }
13352
13353     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13354     while (cm == PGNTag) {
13355         if (appData.debugMode)
13356           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13357         err = ParsePGNTag(yy_text, &gameInfo);
13358         if (!err) numPGNTags++;
13359
13360         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13361         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13362             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13363             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13364             InitPosition(TRUE);
13365             oldVariant = gameInfo.variant;
13366             if (appData.debugMode)
13367               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13368         }
13369
13370
13371         if (gameInfo.fen != NULL) {
13372           Board initial_position;
13373           startedFromSetupPosition = TRUE;
13374           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13375             Reset(TRUE, TRUE);
13376             DisplayError(_("Bad FEN position in file"), 0);
13377             return FALSE;
13378           }
13379           CopyBoard(boards[0], initial_position);
13380           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13381             CopyBoard(initialPosition, initial_position);
13382           if (blackPlaysFirst) {
13383             currentMove = forwardMostMove = backwardMostMove = 1;
13384             CopyBoard(boards[1], initial_position);
13385             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13386             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13387             timeRemaining[0][1] = whiteTimeRemaining;
13388             timeRemaining[1][1] = blackTimeRemaining;
13389             if (commentList[0] != NULL) {
13390               commentList[1] = commentList[0];
13391               commentList[0] = NULL;
13392             }
13393           } else {
13394             currentMove = forwardMostMove = backwardMostMove = 0;
13395           }
13396           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13397           {   int i;
13398               initialRulePlies = FENrulePlies;
13399               for( i=0; i< nrCastlingRights; i++ )
13400                   initialRights[i] = initial_position[CASTLING][i];
13401           }
13402           yyboardindex = forwardMostMove;
13403           free(gameInfo.fen);
13404           gameInfo.fen = NULL;
13405         }
13406
13407         yyboardindex = forwardMostMove;
13408         cm = (ChessMove) Myylex();
13409
13410         /* Handle comments interspersed among the tags */
13411         while (cm == Comment) {
13412             char *p;
13413             if (appData.debugMode)
13414               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13415             p = yy_text;
13416             AppendComment(currentMove, p, FALSE);
13417             yyboardindex = forwardMostMove;
13418             cm = (ChessMove) Myylex();
13419         }
13420     }
13421
13422     /* don't rely on existence of Event tag since if game was
13423      * pasted from clipboard the Event tag may not exist
13424      */
13425     if (numPGNTags > 0){
13426         char *tags;
13427         if (gameInfo.variant == VariantNormal) {
13428           VariantClass v = StringToVariant(gameInfo.event);
13429           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13430           if(v < VariantShogi) gameInfo.variant = v;
13431         }
13432         if (!matchMode) {
13433           if( appData.autoDisplayTags ) {
13434             tags = PGNTags(&gameInfo);
13435             TagsPopUp(tags, CmailMsg());
13436             free(tags);
13437           }
13438         }
13439     } else {
13440         /* Make something up, but don't display it now */
13441         SetGameInfo();
13442         TagsPopDown();
13443     }
13444
13445     if (cm == PositionDiagram) {
13446         int i, j;
13447         char *p;
13448         Board initial_position;
13449
13450         if (appData.debugMode)
13451           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13452
13453         if (!startedFromSetupPosition) {
13454             p = yy_text;
13455             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13456               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13457                 switch (*p) {
13458                   case '{':
13459                   case '[':
13460                   case '-':
13461                   case ' ':
13462                   case '\t':
13463                   case '\n':
13464                   case '\r':
13465                     break;
13466                   default:
13467                     initial_position[i][j++] = CharToPiece(*p);
13468                     break;
13469                 }
13470             while (*p == ' ' || *p == '\t' ||
13471                    *p == '\n' || *p == '\r') p++;
13472
13473             if (strncmp(p, "black", strlen("black"))==0)
13474               blackPlaysFirst = TRUE;
13475             else
13476               blackPlaysFirst = FALSE;
13477             startedFromSetupPosition = TRUE;
13478
13479             CopyBoard(boards[0], initial_position);
13480             if (blackPlaysFirst) {
13481                 currentMove = forwardMostMove = backwardMostMove = 1;
13482                 CopyBoard(boards[1], initial_position);
13483                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13484                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13485                 timeRemaining[0][1] = whiteTimeRemaining;
13486                 timeRemaining[1][1] = blackTimeRemaining;
13487                 if (commentList[0] != NULL) {
13488                     commentList[1] = commentList[0];
13489                     commentList[0] = NULL;
13490                 }
13491             } else {
13492                 currentMove = forwardMostMove = backwardMostMove = 0;
13493             }
13494         }
13495         yyboardindex = forwardMostMove;
13496         cm = (ChessMove) Myylex();
13497     }
13498
13499   if(!creatingBook) {
13500     if (first.pr == NoProc) {
13501         StartChessProgram(&first);
13502     }
13503     InitChessProgram(&first, FALSE);
13504     if(gameInfo.variant == VariantUnknown && *oldName) {
13505         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13506         gameInfo.variant = v;
13507     }
13508     SendToProgram("force\n", &first);
13509     if (startedFromSetupPosition) {
13510         SendBoard(&first, forwardMostMove);
13511     if (appData.debugMode) {
13512         fprintf(debugFP, "Load Game\n");
13513     }
13514         DisplayBothClocks();
13515     }
13516   }
13517
13518     /* [HGM] server: flag to write setup moves in broadcast file as one */
13519     loadFlag = appData.suppressLoadMoves;
13520
13521     while (cm == Comment) {
13522         char *p;
13523         if (appData.debugMode)
13524           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13525         p = yy_text;
13526         AppendComment(currentMove, p, FALSE);
13527         yyboardindex = forwardMostMove;
13528         cm = (ChessMove) Myylex();
13529     }
13530
13531     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13532         cm == WhiteWins || cm == BlackWins ||
13533         cm == GameIsDrawn || cm == GameUnfinished) {
13534         DisplayMessage("", _("No moves in game"));
13535         if (cmailMsgLoaded) {
13536             if (appData.debugMode)
13537               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13538             ClearHighlights();
13539             flipView = FALSE;
13540         }
13541         DrawPosition(FALSE, boards[currentMove]);
13542         DisplayBothClocks();
13543         gameMode = EditGame;
13544         ModeHighlight();
13545         gameFileFP = NULL;
13546         cmailOldMove = 0;
13547         return TRUE;
13548     }
13549
13550     // [HGM] PV info: routine tests if comment empty
13551     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13552         DisplayComment(currentMove - 1, commentList[currentMove]);
13553     }
13554     if (!matchMode && appData.timeDelay != 0)
13555       DrawPosition(FALSE, boards[currentMove]);
13556
13557     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13558       programStats.ok_to_send = 1;
13559     }
13560
13561     /* if the first token after the PGN tags is a move
13562      * and not move number 1, retrieve it from the parser
13563      */
13564     if (cm != MoveNumberOne)
13565         LoadGameOneMove(cm);
13566
13567     /* load the remaining moves from the file */
13568     while (LoadGameOneMove(EndOfFile)) {
13569       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13570       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13571     }
13572
13573     /* rewind to the start of the game */
13574     currentMove = backwardMostMove;
13575
13576     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13577
13578     if (oldGameMode == AnalyzeFile) {
13579       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13580       AnalyzeFileEvent();
13581     } else
13582     if (oldGameMode == AnalyzeMode) {
13583       AnalyzeFileEvent();
13584     }
13585
13586     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13587         long int w, b; // [HGM] adjourn: restore saved clock times
13588         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13589         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13590             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13591             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13592         }
13593     }
13594
13595     if(creatingBook) return TRUE;
13596     if (!matchMode && pos > 0) {
13597         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13598     } else
13599     if (matchMode || appData.timeDelay == 0) {
13600       ToEndEvent();
13601     } else if (appData.timeDelay > 0) {
13602       AutoPlayGameLoop();
13603     }
13604
13605     if (appData.debugMode)
13606         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13607
13608     loadFlag = 0; /* [HGM] true game starts */
13609     return TRUE;
13610 }
13611
13612 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13613 int
13614 ReloadPosition (int offset)
13615 {
13616     int positionNumber = lastLoadPositionNumber + offset;
13617     if (lastLoadPositionFP == NULL) {
13618         DisplayError(_("No position has been loaded yet"), 0);
13619         return FALSE;
13620     }
13621     if (positionNumber <= 0) {
13622         DisplayError(_("Can't back up any further"), 0);
13623         return FALSE;
13624     }
13625     return LoadPosition(lastLoadPositionFP, positionNumber,
13626                         lastLoadPositionTitle);
13627 }
13628
13629 /* Load the nth position from the given file */
13630 int
13631 LoadPositionFromFile (char *filename, int n, char *title)
13632 {
13633     FILE *f;
13634     char buf[MSG_SIZ];
13635
13636     if (strcmp(filename, "-") == 0) {
13637         return LoadPosition(stdin, n, "stdin");
13638     } else {
13639         f = fopen(filename, "rb");
13640         if (f == NULL) {
13641             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13642             DisplayError(buf, errno);
13643             return FALSE;
13644         } else {
13645             return LoadPosition(f, n, title);
13646         }
13647     }
13648 }
13649
13650 /* Load the nth position from the given open file, and close it */
13651 int
13652 LoadPosition (FILE *f, int positionNumber, char *title)
13653 {
13654     char *p, line[MSG_SIZ];
13655     Board initial_position;
13656     int i, j, fenMode, pn;
13657
13658     if (gameMode == Training )
13659         SetTrainingModeOff();
13660
13661     if (gameMode != BeginningOfGame) {
13662         Reset(FALSE, TRUE);
13663     }
13664     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13665         fclose(lastLoadPositionFP);
13666     }
13667     if (positionNumber == 0) positionNumber = 1;
13668     lastLoadPositionFP = f;
13669     lastLoadPositionNumber = positionNumber;
13670     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13671     if (first.pr == NoProc && !appData.noChessProgram) {
13672       StartChessProgram(&first);
13673       InitChessProgram(&first, FALSE);
13674     }
13675     pn = positionNumber;
13676     if (positionNumber < 0) {
13677         /* Negative position number means to seek to that byte offset */
13678         if (fseek(f, -positionNumber, 0) == -1) {
13679             DisplayError(_("Can't seek on position file"), 0);
13680             return FALSE;
13681         };
13682         pn = 1;
13683     } else {
13684         if (fseek(f, 0, 0) == -1) {
13685             if (f == lastLoadPositionFP ?
13686                 positionNumber == lastLoadPositionNumber + 1 :
13687                 positionNumber == 1) {
13688                 pn = 1;
13689             } else {
13690                 DisplayError(_("Can't seek on position file"), 0);
13691                 return FALSE;
13692             }
13693         }
13694     }
13695     /* See if this file is FEN or old-style xboard */
13696     if (fgets(line, MSG_SIZ, f) == NULL) {
13697         DisplayError(_("Position not found in file"), 0);
13698         return FALSE;
13699     }
13700     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13701     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13702
13703     if (pn >= 2) {
13704         if (fenMode || line[0] == '#') pn--;
13705         while (pn > 0) {
13706             /* skip positions before number pn */
13707             if (fgets(line, MSG_SIZ, f) == NULL) {
13708                 Reset(TRUE, TRUE);
13709                 DisplayError(_("Position not found in file"), 0);
13710                 return FALSE;
13711             }
13712             if (fenMode || line[0] == '#') pn--;
13713         }
13714     }
13715
13716     if (fenMode) {
13717         char *p;
13718         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13719             DisplayError(_("Bad FEN position in file"), 0);
13720             return FALSE;
13721         }
13722         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13723             sscanf(p+4, "%[^;]", bestMove);
13724         } else *bestMove = NULLCHAR;
13725         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13726             sscanf(p+4, "%[^;]", avoidMove);
13727         } else *avoidMove = NULLCHAR;
13728     } else {
13729         (void) fgets(line, MSG_SIZ, f);
13730         (void) fgets(line, MSG_SIZ, f);
13731
13732         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13733             (void) fgets(line, MSG_SIZ, f);
13734             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13735                 if (*p == ' ')
13736                   continue;
13737                 initial_position[i][j++] = CharToPiece(*p);
13738             }
13739         }
13740
13741         blackPlaysFirst = FALSE;
13742         if (!feof(f)) {
13743             (void) fgets(line, MSG_SIZ, f);
13744             if (strncmp(line, "black", strlen("black"))==0)
13745               blackPlaysFirst = TRUE;
13746         }
13747     }
13748     startedFromSetupPosition = TRUE;
13749
13750     CopyBoard(boards[0], initial_position);
13751     if (blackPlaysFirst) {
13752         currentMove = forwardMostMove = backwardMostMove = 1;
13753         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13754         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13755         CopyBoard(boards[1], initial_position);
13756         DisplayMessage("", _("Black to play"));
13757     } else {
13758         currentMove = forwardMostMove = backwardMostMove = 0;
13759         DisplayMessage("", _("White to play"));
13760     }
13761     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13762     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13763         SendToProgram("force\n", &first);
13764         SendBoard(&first, forwardMostMove);
13765     }
13766     if (appData.debugMode) {
13767 int i, j;
13768   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13769   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13770         fprintf(debugFP, "Load Position\n");
13771     }
13772
13773     if (positionNumber > 1) {
13774       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13775         DisplayTitle(line);
13776     } else {
13777         DisplayTitle(title);
13778     }
13779     gameMode = EditGame;
13780     ModeHighlight();
13781     ResetClocks();
13782     timeRemaining[0][1] = whiteTimeRemaining;
13783     timeRemaining[1][1] = blackTimeRemaining;
13784     DrawPosition(FALSE, boards[currentMove]);
13785
13786     return TRUE;
13787 }
13788
13789
13790 void
13791 CopyPlayerNameIntoFileName (char **dest, char *src)
13792 {
13793     while (*src != NULLCHAR && *src != ',') {
13794         if (*src == ' ') {
13795             *(*dest)++ = '_';
13796             src++;
13797         } else {
13798             *(*dest)++ = *src++;
13799         }
13800     }
13801 }
13802
13803 char *
13804 DefaultFileName (char *ext)
13805 {
13806     static char def[MSG_SIZ];
13807     char *p;
13808
13809     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13810         p = def;
13811         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13812         *p++ = '-';
13813         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13814         *p++ = '.';
13815         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13816     } else {
13817         def[0] = NULLCHAR;
13818     }
13819     return def;
13820 }
13821
13822 /* Save the current game to the given file */
13823 int
13824 SaveGameToFile (char *filename, int append)
13825 {
13826     FILE *f;
13827     char buf[MSG_SIZ];
13828     int result, i, t,tot=0;
13829
13830     if (strcmp(filename, "-") == 0) {
13831         return SaveGame(stdout, 0, NULL);
13832     } else {
13833         for(i=0; i<10; i++) { // upto 10 tries
13834              f = fopen(filename, append ? "a" : "w");
13835              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13836              if(f || errno != 13) break;
13837              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13838              tot += t;
13839         }
13840         if (f == NULL) {
13841             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13842             DisplayError(buf, errno);
13843             return FALSE;
13844         } else {
13845             safeStrCpy(buf, lastMsg, MSG_SIZ);
13846             DisplayMessage(_("Waiting for access to save file"), "");
13847             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13848             DisplayMessage(_("Saving game"), "");
13849             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13850             result = SaveGame(f, 0, NULL);
13851             DisplayMessage(buf, "");
13852             return result;
13853         }
13854     }
13855 }
13856
13857 char *
13858 SavePart (char *str)
13859 {
13860     static char buf[MSG_SIZ];
13861     char *p;
13862
13863     p = strchr(str, ' ');
13864     if (p == NULL) return str;
13865     strncpy(buf, str, p - str);
13866     buf[p - str] = NULLCHAR;
13867     return buf;
13868 }
13869
13870 #define PGN_MAX_LINE 75
13871
13872 #define PGN_SIDE_WHITE  0
13873 #define PGN_SIDE_BLACK  1
13874
13875 static int
13876 FindFirstMoveOutOfBook (int side)
13877 {
13878     int result = -1;
13879
13880     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13881         int index = backwardMostMove;
13882         int has_book_hit = 0;
13883
13884         if( (index % 2) != side ) {
13885             index++;
13886         }
13887
13888         while( index < forwardMostMove ) {
13889             /* Check to see if engine is in book */
13890             int depth = pvInfoList[index].depth;
13891             int score = pvInfoList[index].score;
13892             int in_book = 0;
13893
13894             if( depth <= 2 ) {
13895                 in_book = 1;
13896             }
13897             else if( score == 0 && depth == 63 ) {
13898                 in_book = 1; /* Zappa */
13899             }
13900             else if( score == 2 && depth == 99 ) {
13901                 in_book = 1; /* Abrok */
13902             }
13903
13904             has_book_hit += in_book;
13905
13906             if( ! in_book ) {
13907                 result = index;
13908
13909                 break;
13910             }
13911
13912             index += 2;
13913         }
13914     }
13915
13916     return result;
13917 }
13918
13919 void
13920 GetOutOfBookInfo (char * buf)
13921 {
13922     int oob[2];
13923     int i;
13924     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13925
13926     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13927     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13928
13929     *buf = '\0';
13930
13931     if( oob[0] >= 0 || oob[1] >= 0 ) {
13932         for( i=0; i<2; i++ ) {
13933             int idx = oob[i];
13934
13935             if( idx >= 0 ) {
13936                 if( i > 0 && oob[0] >= 0 ) {
13937                     strcat( buf, "   " );
13938                 }
13939
13940                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13941                 sprintf( buf+strlen(buf), "%s%.2f",
13942                     pvInfoList[idx].score >= 0 ? "+" : "",
13943                     pvInfoList[idx].score / 100.0 );
13944             }
13945         }
13946     }
13947 }
13948
13949 /* Save game in PGN style */
13950 static void
13951 SaveGamePGN2 (FILE *f)
13952 {
13953     int i, offset, linelen, newblock;
13954 //    char *movetext;
13955     char numtext[32];
13956     int movelen, numlen, blank;
13957     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13958
13959     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13960
13961     PrintPGNTags(f, &gameInfo);
13962
13963     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13964
13965     if (backwardMostMove > 0 || startedFromSetupPosition) {
13966         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13967         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13968         fprintf(f, "\n{--------------\n");
13969         PrintPosition(f, backwardMostMove);
13970         fprintf(f, "--------------}\n");
13971         free(fen);
13972     }
13973     else {
13974         /* [AS] Out of book annotation */
13975         if( appData.saveOutOfBookInfo ) {
13976             char buf[64];
13977
13978             GetOutOfBookInfo( buf );
13979
13980             if( buf[0] != '\0' ) {
13981                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13982             }
13983         }
13984
13985         fprintf(f, "\n");
13986     }
13987
13988     i = backwardMostMove;
13989     linelen = 0;
13990     newblock = TRUE;
13991
13992     while (i < forwardMostMove) {
13993         /* Print comments preceding this move */
13994         if (commentList[i] != NULL) {
13995             if (linelen > 0) fprintf(f, "\n");
13996             fprintf(f, "%s", commentList[i]);
13997             linelen = 0;
13998             newblock = TRUE;
13999         }
14000
14001         /* Format move number */
14002         if ((i % 2) == 0)
14003           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
14004         else
14005           if (newblock)
14006             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
14007           else
14008             numtext[0] = NULLCHAR;
14009
14010         numlen = strlen(numtext);
14011         newblock = FALSE;
14012
14013         /* Print move number */
14014         blank = linelen > 0 && numlen > 0;
14015         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
14016             fprintf(f, "\n");
14017             linelen = 0;
14018             blank = 0;
14019         }
14020         if (blank) {
14021             fprintf(f, " ");
14022             linelen++;
14023         }
14024         fprintf(f, "%s", numtext);
14025         linelen += numlen;
14026
14027         /* Get move */
14028         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
14029         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
14030
14031         /* Print move */
14032         blank = linelen > 0 && movelen > 0;
14033         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14034             fprintf(f, "\n");
14035             linelen = 0;
14036             blank = 0;
14037         }
14038         if (blank) {
14039             fprintf(f, " ");
14040             linelen++;
14041         }
14042         fprintf(f, "%s", move_buffer);
14043         linelen += movelen;
14044
14045         /* [AS] Add PV info if present */
14046         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
14047             /* [HGM] add time */
14048             char buf[MSG_SIZ]; int seconds;
14049
14050             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14051
14052             if( seconds <= 0)
14053               buf[0] = 0;
14054             else
14055               if( seconds < 30 )
14056                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14057               else
14058                 {
14059                   seconds = (seconds + 4)/10; // round to full seconds
14060                   if( seconds < 60 )
14061                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14062                   else
14063                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14064                 }
14065
14066             if(appData.cumulativeTimePGN) {
14067                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14068             }
14069
14070             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14071                       pvInfoList[i].score >= 0 ? "+" : "",
14072                       pvInfoList[i].score / 100.0,
14073                       pvInfoList[i].depth,
14074                       buf );
14075
14076             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14077
14078             /* Print score/depth */
14079             blank = linelen > 0 && movelen > 0;
14080             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14081                 fprintf(f, "\n");
14082                 linelen = 0;
14083                 blank = 0;
14084             }
14085             if (blank) {
14086                 fprintf(f, " ");
14087                 linelen++;
14088             }
14089             fprintf(f, "%s", move_buffer);
14090             linelen += movelen;
14091         }
14092
14093         i++;
14094     }
14095
14096     /* Start a new line */
14097     if (linelen > 0) fprintf(f, "\n");
14098
14099     /* Print comments after last move */
14100     if (commentList[i] != NULL) {
14101         fprintf(f, "%s\n", commentList[i]);
14102     }
14103
14104     /* Print result */
14105     if (gameInfo.resultDetails != NULL &&
14106         gameInfo.resultDetails[0] != NULLCHAR) {
14107         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14108         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14109            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14110             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14111         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14112     } else {
14113         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14114     }
14115 }
14116
14117 /* Save game in PGN style and close the file */
14118 int
14119 SaveGamePGN (FILE *f)
14120 {
14121     SaveGamePGN2(f);
14122     fclose(f);
14123     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14124     return TRUE;
14125 }
14126
14127 /* Save game in old style and close the file */
14128 int
14129 SaveGameOldStyle (FILE *f)
14130 {
14131     int i, offset;
14132     time_t tm;
14133
14134     tm = time((time_t *) NULL);
14135
14136     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14137     PrintOpponents(f);
14138
14139     if (backwardMostMove > 0 || startedFromSetupPosition) {
14140         fprintf(f, "\n[--------------\n");
14141         PrintPosition(f, backwardMostMove);
14142         fprintf(f, "--------------]\n");
14143     } else {
14144         fprintf(f, "\n");
14145     }
14146
14147     i = backwardMostMove;
14148     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14149
14150     while (i < forwardMostMove) {
14151         if (commentList[i] != NULL) {
14152             fprintf(f, "[%s]\n", commentList[i]);
14153         }
14154
14155         if ((i % 2) == 1) {
14156             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14157             i++;
14158         } else {
14159             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14160             i++;
14161             if (commentList[i] != NULL) {
14162                 fprintf(f, "\n");
14163                 continue;
14164             }
14165             if (i >= forwardMostMove) {
14166                 fprintf(f, "\n");
14167                 break;
14168             }
14169             fprintf(f, "%s\n", parseList[i]);
14170             i++;
14171         }
14172     }
14173
14174     if (commentList[i] != NULL) {
14175         fprintf(f, "[%s]\n", commentList[i]);
14176     }
14177
14178     /* This isn't really the old style, but it's close enough */
14179     if (gameInfo.resultDetails != NULL &&
14180         gameInfo.resultDetails[0] != NULLCHAR) {
14181         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14182                 gameInfo.resultDetails);
14183     } else {
14184         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14185     }
14186
14187     fclose(f);
14188     return TRUE;
14189 }
14190
14191 /* Save the current game to open file f and close the file */
14192 int
14193 SaveGame (FILE *f, int dummy, char *dummy2)
14194 {
14195     if (gameMode == EditPosition) EditPositionDone(TRUE);
14196     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14197     if (appData.oldSaveStyle)
14198       return SaveGameOldStyle(f);
14199     else
14200       return SaveGamePGN(f);
14201 }
14202
14203 /* Save the current position to the given file */
14204 int
14205 SavePositionToFile (char *filename)
14206 {
14207     FILE *f;
14208     char buf[MSG_SIZ];
14209
14210     if (strcmp(filename, "-") == 0) {
14211         return SavePosition(stdout, 0, NULL);
14212     } else {
14213         f = fopen(filename, "a");
14214         if (f == NULL) {
14215             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14216             DisplayError(buf, errno);
14217             return FALSE;
14218         } else {
14219             safeStrCpy(buf, lastMsg, MSG_SIZ);
14220             DisplayMessage(_("Waiting for access to save file"), "");
14221             flock(fileno(f), LOCK_EX); // [HGM] lock
14222             DisplayMessage(_("Saving position"), "");
14223             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14224             SavePosition(f, 0, NULL);
14225             DisplayMessage(buf, "");
14226             return TRUE;
14227         }
14228     }
14229 }
14230
14231 /* Save the current position to the given open file and close the file */
14232 int
14233 SavePosition (FILE *f, int dummy, char *dummy2)
14234 {
14235     time_t tm;
14236     char *fen;
14237
14238     if (gameMode == EditPosition) EditPositionDone(TRUE);
14239     if (appData.oldSaveStyle) {
14240         tm = time((time_t *) NULL);
14241
14242         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14243         PrintOpponents(f);
14244         fprintf(f, "[--------------\n");
14245         PrintPosition(f, currentMove);
14246         fprintf(f, "--------------]\n");
14247     } else {
14248         fen = PositionToFEN(currentMove, NULL, 1);
14249         fprintf(f, "%s\n", fen);
14250         free(fen);
14251     }
14252     fclose(f);
14253     return TRUE;
14254 }
14255
14256 void
14257 ReloadCmailMsgEvent (int unregister)
14258 {
14259 #if !WIN32
14260     static char *inFilename = NULL;
14261     static char *outFilename;
14262     int i;
14263     struct stat inbuf, outbuf;
14264     int status;
14265
14266     /* Any registered moves are unregistered if unregister is set, */
14267     /* i.e. invoked by the signal handler */
14268     if (unregister) {
14269         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14270             cmailMoveRegistered[i] = FALSE;
14271             if (cmailCommentList[i] != NULL) {
14272                 free(cmailCommentList[i]);
14273                 cmailCommentList[i] = NULL;
14274             }
14275         }
14276         nCmailMovesRegistered = 0;
14277     }
14278
14279     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14280         cmailResult[i] = CMAIL_NOT_RESULT;
14281     }
14282     nCmailResults = 0;
14283
14284     if (inFilename == NULL) {
14285         /* Because the filenames are static they only get malloced once  */
14286         /* and they never get freed                                      */
14287         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14288         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14289
14290         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14291         sprintf(outFilename, "%s.out", appData.cmailGameName);
14292     }
14293
14294     status = stat(outFilename, &outbuf);
14295     if (status < 0) {
14296         cmailMailedMove = FALSE;
14297     } else {
14298         status = stat(inFilename, &inbuf);
14299         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14300     }
14301
14302     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14303        counts the games, notes how each one terminated, etc.
14304
14305        It would be nice to remove this kludge and instead gather all
14306        the information while building the game list.  (And to keep it
14307        in the game list nodes instead of having a bunch of fixed-size
14308        parallel arrays.)  Note this will require getting each game's
14309        termination from the PGN tags, as the game list builder does
14310        not process the game moves.  --mann
14311        */
14312     cmailMsgLoaded = TRUE;
14313     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14314
14315     /* Load first game in the file or popup game menu */
14316     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14317
14318 #endif /* !WIN32 */
14319     return;
14320 }
14321
14322 int
14323 RegisterMove ()
14324 {
14325     FILE *f;
14326     char string[MSG_SIZ];
14327
14328     if (   cmailMailedMove
14329         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14330         return TRUE;            /* Allow free viewing  */
14331     }
14332
14333     /* Unregister move to ensure that we don't leave RegisterMove        */
14334     /* with the move registered when the conditions for registering no   */
14335     /* longer hold                                                       */
14336     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14337         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14338         nCmailMovesRegistered --;
14339
14340         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14341           {
14342               free(cmailCommentList[lastLoadGameNumber - 1]);
14343               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14344           }
14345     }
14346
14347     if (cmailOldMove == -1) {
14348         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14349         return FALSE;
14350     }
14351
14352     if (currentMove > cmailOldMove + 1) {
14353         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14354         return FALSE;
14355     }
14356
14357     if (currentMove < cmailOldMove) {
14358         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14359         return FALSE;
14360     }
14361
14362     if (forwardMostMove > currentMove) {
14363         /* Silently truncate extra moves */
14364         TruncateGame();
14365     }
14366
14367     if (   (currentMove == cmailOldMove + 1)
14368         || (   (currentMove == cmailOldMove)
14369             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14370                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14371         if (gameInfo.result != GameUnfinished) {
14372             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14373         }
14374
14375         if (commentList[currentMove] != NULL) {
14376             cmailCommentList[lastLoadGameNumber - 1]
14377               = StrSave(commentList[currentMove]);
14378         }
14379         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14380
14381         if (appData.debugMode)
14382           fprintf(debugFP, "Saving %s for game %d\n",
14383                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14384
14385         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14386
14387         f = fopen(string, "w");
14388         if (appData.oldSaveStyle) {
14389             SaveGameOldStyle(f); /* also closes the file */
14390
14391             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14392             f = fopen(string, "w");
14393             SavePosition(f, 0, NULL); /* also closes the file */
14394         } else {
14395             fprintf(f, "{--------------\n");
14396             PrintPosition(f, currentMove);
14397             fprintf(f, "--------------}\n\n");
14398
14399             SaveGame(f, 0, NULL); /* also closes the file*/
14400         }
14401
14402         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14403         nCmailMovesRegistered ++;
14404     } else if (nCmailGames == 1) {
14405         DisplayError(_("You have not made a move yet"), 0);
14406         return FALSE;
14407     }
14408
14409     return TRUE;
14410 }
14411
14412 void
14413 MailMoveEvent ()
14414 {
14415 #if !WIN32
14416     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14417     FILE *commandOutput;
14418     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14419     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14420     int nBuffers;
14421     int i;
14422     int archived;
14423     char *arcDir;
14424
14425     if (! cmailMsgLoaded) {
14426         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14427         return;
14428     }
14429
14430     if (nCmailGames == nCmailResults) {
14431         DisplayError(_("No unfinished games"), 0);
14432         return;
14433     }
14434
14435 #if CMAIL_PROHIBIT_REMAIL
14436     if (cmailMailedMove) {
14437       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);
14438         DisplayError(msg, 0);
14439         return;
14440     }
14441 #endif
14442
14443     if (! (cmailMailedMove || RegisterMove())) return;
14444
14445     if (   cmailMailedMove
14446         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14447       snprintf(string, MSG_SIZ, partCommandString,
14448                appData.debugMode ? " -v" : "", appData.cmailGameName);
14449         commandOutput = popen(string, "r");
14450
14451         if (commandOutput == NULL) {
14452             DisplayError(_("Failed to invoke cmail"), 0);
14453         } else {
14454             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14455                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14456             }
14457             if (nBuffers > 1) {
14458                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14459                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14460                 nBytes = MSG_SIZ - 1;
14461             } else {
14462                 (void) memcpy(msg, buffer, nBytes);
14463             }
14464             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14465
14466             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14467                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14468
14469                 archived = TRUE;
14470                 for (i = 0; i < nCmailGames; i ++) {
14471                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14472                         archived = FALSE;
14473                     }
14474                 }
14475                 if (   archived
14476                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14477                         != NULL)) {
14478                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14479                            arcDir,
14480                            appData.cmailGameName,
14481                            gameInfo.date);
14482                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14483                     cmailMsgLoaded = FALSE;
14484                 }
14485             }
14486
14487             DisplayInformation(msg);
14488             pclose(commandOutput);
14489         }
14490     } else {
14491         if ((*cmailMsg) != '\0') {
14492             DisplayInformation(cmailMsg);
14493         }
14494     }
14495
14496     return;
14497 #endif /* !WIN32 */
14498 }
14499
14500 char *
14501 CmailMsg ()
14502 {
14503 #if WIN32
14504     return NULL;
14505 #else
14506     int  prependComma = 0;
14507     char number[5];
14508     char string[MSG_SIZ];       /* Space for game-list */
14509     int  i;
14510
14511     if (!cmailMsgLoaded) return "";
14512
14513     if (cmailMailedMove) {
14514       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14515     } else {
14516         /* Create a list of games left */
14517       snprintf(string, MSG_SIZ, "[");
14518         for (i = 0; i < nCmailGames; i ++) {
14519             if (! (   cmailMoveRegistered[i]
14520                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14521                 if (prependComma) {
14522                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14523                 } else {
14524                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14525                     prependComma = 1;
14526                 }
14527
14528                 strcat(string, number);
14529             }
14530         }
14531         strcat(string, "]");
14532
14533         if (nCmailMovesRegistered + nCmailResults == 0) {
14534             switch (nCmailGames) {
14535               case 1:
14536                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14537                 break;
14538
14539               case 2:
14540                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14541                 break;
14542
14543               default:
14544                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14545                          nCmailGames);
14546                 break;
14547             }
14548         } else {
14549             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14550               case 1:
14551                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14552                          string);
14553                 break;
14554
14555               case 0:
14556                 if (nCmailResults == nCmailGames) {
14557                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14558                 } else {
14559                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14560                 }
14561                 break;
14562
14563               default:
14564                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14565                          string);
14566             }
14567         }
14568     }
14569     return cmailMsg;
14570 #endif /* WIN32 */
14571 }
14572
14573 void
14574 ResetGameEvent ()
14575 {
14576     if (gameMode == Training)
14577       SetTrainingModeOff();
14578
14579     Reset(TRUE, TRUE);
14580     cmailMsgLoaded = FALSE;
14581     if (appData.icsActive) {
14582       SendToICS(ics_prefix);
14583       SendToICS("refresh\n");
14584     }
14585 }
14586
14587 void
14588 ExitEvent (int status)
14589 {
14590     exiting++;
14591     if (exiting > 2) {
14592       /* Give up on clean exit */
14593       exit(status);
14594     }
14595     if (exiting > 1) {
14596       /* Keep trying for clean exit */
14597       return;
14598     }
14599
14600     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14601     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14602
14603     if (telnetISR != NULL) {
14604       RemoveInputSource(telnetISR);
14605     }
14606     if (icsPR != NoProc) {
14607       DestroyChildProcess(icsPR, TRUE);
14608     }
14609
14610     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14611     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14612
14613     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14614     /* make sure this other one finishes before killing it!                  */
14615     if(endingGame) { int count = 0;
14616         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14617         while(endingGame && count++ < 10) DoSleep(1);
14618         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14619     }
14620
14621     /* Kill off chess programs */
14622     if (first.pr != NoProc) {
14623         ExitAnalyzeMode();
14624
14625         DoSleep( appData.delayBeforeQuit );
14626         SendToProgram("quit\n", &first);
14627         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14628     }
14629     if (second.pr != NoProc) {
14630         DoSleep( appData.delayBeforeQuit );
14631         SendToProgram("quit\n", &second);
14632         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14633     }
14634     if (first.isr != NULL) {
14635         RemoveInputSource(first.isr);
14636     }
14637     if (second.isr != NULL) {
14638         RemoveInputSource(second.isr);
14639     }
14640
14641     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14642     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14643
14644     ShutDownFrontEnd();
14645     exit(status);
14646 }
14647
14648 void
14649 PauseEngine (ChessProgramState *cps)
14650 {
14651     SendToProgram("pause\n", cps);
14652     cps->pause = 2;
14653 }
14654
14655 void
14656 UnPauseEngine (ChessProgramState *cps)
14657 {
14658     SendToProgram("resume\n", cps);
14659     cps->pause = 1;
14660 }
14661
14662 void
14663 PauseEvent ()
14664 {
14665     if (appData.debugMode)
14666         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14667     if (pausing) {
14668         pausing = FALSE;
14669         ModeHighlight();
14670         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14671             StartClocks();
14672             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14673                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14674                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14675             }
14676             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14677             HandleMachineMove(stashedInputMove, stalledEngine);
14678             stalledEngine = NULL;
14679             return;
14680         }
14681         if (gameMode == MachinePlaysWhite ||
14682             gameMode == TwoMachinesPlay   ||
14683             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14684             if(first.pause)  UnPauseEngine(&first);
14685             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14686             if(second.pause) UnPauseEngine(&second);
14687             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14688             StartClocks();
14689         } else {
14690             DisplayBothClocks();
14691         }
14692         if (gameMode == PlayFromGameFile) {
14693             if (appData.timeDelay >= 0)
14694                 AutoPlayGameLoop();
14695         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14696             Reset(FALSE, TRUE);
14697             SendToICS(ics_prefix);
14698             SendToICS("refresh\n");
14699         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14700             ForwardInner(forwardMostMove);
14701         }
14702         pauseExamInvalid = FALSE;
14703     } else {
14704         switch (gameMode) {
14705           default:
14706             return;
14707           case IcsExamining:
14708             pauseExamForwardMostMove = forwardMostMove;
14709             pauseExamInvalid = FALSE;
14710             /* fall through */
14711           case IcsObserving:
14712           case IcsPlayingWhite:
14713           case IcsPlayingBlack:
14714             pausing = TRUE;
14715             ModeHighlight();
14716             return;
14717           case PlayFromGameFile:
14718             (void) StopLoadGameTimer();
14719             pausing = TRUE;
14720             ModeHighlight();
14721             break;
14722           case BeginningOfGame:
14723             if (appData.icsActive) return;
14724             /* else fall through */
14725           case MachinePlaysWhite:
14726           case MachinePlaysBlack:
14727           case TwoMachinesPlay:
14728             if (forwardMostMove == 0)
14729               return;           /* don't pause if no one has moved */
14730             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14731                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14732                 if(onMove->pause) {           // thinking engine can be paused
14733                     PauseEngine(onMove);      // do it
14734                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14735                         PauseEngine(onMove->other);
14736                     else
14737                         SendToProgram("easy\n", onMove->other);
14738                     StopClocks();
14739                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14740             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14741                 if(first.pause) {
14742                     PauseEngine(&first);
14743                     StopClocks();
14744                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14745             } else { // human on move, pause pondering by either method
14746                 if(first.pause)
14747                     PauseEngine(&first);
14748                 else if(appData.ponderNextMove)
14749                     SendToProgram("easy\n", &first);
14750                 StopClocks();
14751             }
14752             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14753           case AnalyzeMode:
14754             pausing = TRUE;
14755             ModeHighlight();
14756             break;
14757         }
14758     }
14759 }
14760
14761 void
14762 EditCommentEvent ()
14763 {
14764     char title[MSG_SIZ];
14765
14766     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14767       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14768     } else {
14769       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14770                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14771                parseList[currentMove - 1]);
14772     }
14773
14774     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14775 }
14776
14777
14778 void
14779 EditTagsEvent ()
14780 {
14781     char *tags = PGNTags(&gameInfo);
14782     bookUp = FALSE;
14783     EditTagsPopUp(tags, NULL);
14784     free(tags);
14785 }
14786
14787 void
14788 StartSecond ()
14789 {
14790     if(WaitForEngine(&second, StartSecond)) return;
14791     InitChessProgram(&second, FALSE);
14792     FeedMovesToProgram(&second, currentMove);
14793
14794     SendToProgram("analyze\n", &second);
14795     second.analyzing = TRUE;
14796     ThawUI();
14797 }
14798
14799 void
14800 ToggleSecond ()
14801 {
14802   if(second.analyzing) {
14803     SendToProgram("exit\n", &second);
14804     second.analyzing = FALSE;
14805   } else {
14806     StartSecond();
14807   }
14808 }
14809
14810 /* Toggle ShowThinking */
14811 void
14812 ToggleShowThinking()
14813 {
14814   appData.showThinking = !appData.showThinking;
14815   ShowThinkingEvent();
14816 }
14817
14818 int
14819 AnalyzeModeEvent ()
14820 {
14821     char buf[MSG_SIZ];
14822
14823     if (!first.analysisSupport) {
14824       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14825       DisplayError(buf, 0);
14826       return 0;
14827     }
14828     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14829     if (appData.icsActive) {
14830         if (gameMode != IcsObserving) {
14831           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14832             DisplayError(buf, 0);
14833             /* secure check */
14834             if (appData.icsEngineAnalyze) {
14835                 if (appData.debugMode)
14836                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14837                 ExitAnalyzeMode();
14838                 ModeHighlight();
14839             }
14840             return 0;
14841         }
14842         /* if enable, user wants to disable icsEngineAnalyze */
14843         if (appData.icsEngineAnalyze) {
14844                 ExitAnalyzeMode();
14845                 ModeHighlight();
14846                 return 0;
14847         }
14848         appData.icsEngineAnalyze = TRUE;
14849         if (appData.debugMode)
14850             fprintf(debugFP, "ICS engine analyze starting... \n");
14851     }
14852
14853     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14854     if (appData.noChessProgram || gameMode == AnalyzeMode)
14855       return 0;
14856
14857     if (gameMode != AnalyzeFile) {
14858         if (!appData.icsEngineAnalyze) {
14859                EditGameEvent();
14860                if (gameMode != EditGame) return 0;
14861         }
14862         if (!appData.showThinking) ToggleShowThinking();
14863         ResurrectChessProgram();
14864         SendToProgram("analyze\n", &first);
14865         first.analyzing = TRUE;
14866         /*first.maybeThinking = TRUE;*/
14867         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14868         EngineOutputPopUp();
14869     }
14870     if (!appData.icsEngineAnalyze) {
14871         gameMode = AnalyzeMode;
14872         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14873     }
14874     pausing = FALSE;
14875     ModeHighlight();
14876     SetGameInfo();
14877
14878     StartAnalysisClock();
14879     GetTimeMark(&lastNodeCountTime);
14880     lastNodeCount = 0;
14881     return 1;
14882 }
14883
14884 void
14885 AnalyzeFileEvent ()
14886 {
14887     if (appData.noChessProgram || gameMode == AnalyzeFile)
14888       return;
14889
14890     if (!first.analysisSupport) {
14891       char buf[MSG_SIZ];
14892       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14893       DisplayError(buf, 0);
14894       return;
14895     }
14896
14897     if (gameMode != AnalyzeMode) {
14898         keepInfo = 1; // mere annotating should not alter PGN tags
14899         EditGameEvent();
14900         keepInfo = 0;
14901         if (gameMode != EditGame) return;
14902         if (!appData.showThinking) ToggleShowThinking();
14903         ResurrectChessProgram();
14904         SendToProgram("analyze\n", &first);
14905         first.analyzing = TRUE;
14906         /*first.maybeThinking = TRUE;*/
14907         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14908         EngineOutputPopUp();
14909     }
14910     gameMode = AnalyzeFile;
14911     pausing = FALSE;
14912     ModeHighlight();
14913
14914     StartAnalysisClock();
14915     GetTimeMark(&lastNodeCountTime);
14916     lastNodeCount = 0;
14917     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14918     AnalysisPeriodicEvent(1);
14919 }
14920
14921 void
14922 MachineWhiteEvent ()
14923 {
14924     char buf[MSG_SIZ];
14925     char *bookHit = NULL;
14926
14927     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14928       return;
14929
14930
14931     if (gameMode == PlayFromGameFile ||
14932         gameMode == TwoMachinesPlay  ||
14933         gameMode == Training         ||
14934         gameMode == AnalyzeMode      ||
14935         gameMode == EndOfGame)
14936         EditGameEvent();
14937
14938     if (gameMode == EditPosition)
14939         EditPositionDone(TRUE);
14940
14941     if (!WhiteOnMove(currentMove)) {
14942         DisplayError(_("It is not White's turn"), 0);
14943         return;
14944     }
14945
14946     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14947       ExitAnalyzeMode();
14948
14949     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14950         gameMode == AnalyzeFile)
14951         TruncateGame();
14952
14953     ResurrectChessProgram();    /* in case it isn't running */
14954     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14955         gameMode = MachinePlaysWhite;
14956         ResetClocks();
14957     } else
14958     gameMode = MachinePlaysWhite;
14959     pausing = FALSE;
14960     ModeHighlight();
14961     SetGameInfo();
14962     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14963     DisplayTitle(buf);
14964     if (first.sendName) {
14965       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14966       SendToProgram(buf, &first);
14967     }
14968     if (first.sendTime) {
14969       if (first.useColors) {
14970         SendToProgram("black\n", &first); /*gnu kludge*/
14971       }
14972       SendTimeRemaining(&first, TRUE);
14973     }
14974     if (first.useColors) {
14975       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14976     }
14977     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14978     SetMachineThinkingEnables();
14979     first.maybeThinking = TRUE;
14980     StartClocks();
14981     firstMove = FALSE;
14982
14983     if (appData.autoFlipView && !flipView) {
14984       flipView = !flipView;
14985       DrawPosition(FALSE, NULL);
14986       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14987     }
14988
14989     if(bookHit) { // [HGM] book: simulate book reply
14990         static char bookMove[MSG_SIZ]; // a bit generous?
14991
14992         programStats.nodes = programStats.depth = programStats.time =
14993         programStats.score = programStats.got_only_move = 0;
14994         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14995
14996         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14997         strcat(bookMove, bookHit);
14998         savedMessage = bookMove; // args for deferred call
14999         savedState = &first;
15000         ScheduleDelayedEvent(DeferredBookMove, 1);
15001     }
15002 }
15003
15004 void
15005 MachineBlackEvent ()
15006 {
15007   char buf[MSG_SIZ];
15008   char *bookHit = NULL;
15009
15010     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
15011         return;
15012
15013
15014     if (gameMode == PlayFromGameFile ||
15015         gameMode == TwoMachinesPlay  ||
15016         gameMode == Training         ||
15017         gameMode == AnalyzeMode      ||
15018         gameMode == EndOfGame)
15019         EditGameEvent();
15020
15021     if (gameMode == EditPosition)
15022         EditPositionDone(TRUE);
15023
15024     if (WhiteOnMove(currentMove)) {
15025         DisplayError(_("It is not Black's turn"), 0);
15026         return;
15027     }
15028
15029     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15030       ExitAnalyzeMode();
15031
15032     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15033         gameMode == AnalyzeFile)
15034         TruncateGame();
15035
15036     ResurrectChessProgram();    /* in case it isn't running */
15037     gameMode = MachinePlaysBlack;
15038     pausing = FALSE;
15039     ModeHighlight();
15040     SetGameInfo();
15041     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15042     DisplayTitle(buf);
15043     if (first.sendName) {
15044       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
15045       SendToProgram(buf, &first);
15046     }
15047     if (first.sendTime) {
15048       if (first.useColors) {
15049         SendToProgram("white\n", &first); /*gnu kludge*/
15050       }
15051       SendTimeRemaining(&first, FALSE);
15052     }
15053     if (first.useColors) {
15054       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
15055     }
15056     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15057     SetMachineThinkingEnables();
15058     first.maybeThinking = TRUE;
15059     StartClocks();
15060
15061     if (appData.autoFlipView && flipView) {
15062       flipView = !flipView;
15063       DrawPosition(FALSE, NULL);
15064       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15065     }
15066     if(bookHit) { // [HGM] book: simulate book reply
15067         static char bookMove[MSG_SIZ]; // a bit generous?
15068
15069         programStats.nodes = programStats.depth = programStats.time =
15070         programStats.score = programStats.got_only_move = 0;
15071         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15072
15073         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15074         strcat(bookMove, bookHit);
15075         savedMessage = bookMove; // args for deferred call
15076         savedState = &first;
15077         ScheduleDelayedEvent(DeferredBookMove, 1);
15078     }
15079 }
15080
15081
15082 void
15083 DisplayTwoMachinesTitle ()
15084 {
15085     char buf[MSG_SIZ];
15086     if (appData.matchGames > 0) {
15087         if(appData.tourneyFile[0]) {
15088           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15089                    gameInfo.white, _("vs."), gameInfo.black,
15090                    nextGame+1, appData.matchGames+1,
15091                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15092         } else
15093         if (first.twoMachinesColor[0] == 'w') {
15094           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15095                    gameInfo.white, _("vs."),  gameInfo.black,
15096                    first.matchWins, second.matchWins,
15097                    matchGame - 1 - (first.matchWins + second.matchWins));
15098         } else {
15099           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15100                    gameInfo.white, _("vs."), gameInfo.black,
15101                    second.matchWins, first.matchWins,
15102                    matchGame - 1 - (first.matchWins + second.matchWins));
15103         }
15104     } else {
15105       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15106     }
15107     DisplayTitle(buf);
15108 }
15109
15110 void
15111 SettingsMenuIfReady ()
15112 {
15113   if (second.lastPing != second.lastPong) {
15114     DisplayMessage("", _("Waiting for second chess program"));
15115     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15116     return;
15117   }
15118   ThawUI();
15119   DisplayMessage("", "");
15120   SettingsPopUp(&second);
15121 }
15122
15123 int
15124 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15125 {
15126     char buf[MSG_SIZ];
15127     if (cps->pr == NoProc) {
15128         StartChessProgram(cps);
15129         if (cps->protocolVersion == 1) {
15130           retry();
15131           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15132         } else {
15133           /* kludge: allow timeout for initial "feature" command */
15134           if(retry != TwoMachinesEventIfReady) FreezeUI();
15135           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15136           DisplayMessage("", buf);
15137           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15138         }
15139         return 1;
15140     }
15141     return 0;
15142 }
15143
15144 void
15145 TwoMachinesEvent P((void))
15146 {
15147     int i, move = forwardMostMove;
15148     char buf[MSG_SIZ];
15149     ChessProgramState *onmove;
15150     char *bookHit = NULL;
15151     static int stalling = 0;
15152     TimeMark now;
15153     long wait;
15154
15155     if (appData.noChessProgram) return;
15156
15157     switch (gameMode) {
15158       case TwoMachinesPlay:
15159         return;
15160       case MachinePlaysWhite:
15161       case MachinePlaysBlack:
15162         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15163             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15164             return;
15165         }
15166         /* fall through */
15167       case BeginningOfGame:
15168       case PlayFromGameFile:
15169       case EndOfGame:
15170         EditGameEvent();
15171         if (gameMode != EditGame) return;
15172         break;
15173       case EditPosition:
15174         EditPositionDone(TRUE);
15175         break;
15176       case AnalyzeMode:
15177       case AnalyzeFile:
15178         ExitAnalyzeMode();
15179         break;
15180       case EditGame:
15181       default:
15182         break;
15183     }
15184
15185 //    forwardMostMove = currentMove;
15186     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15187     startingEngine = TRUE;
15188
15189     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15190
15191     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15192     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15193       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15194       return;
15195     }
15196   if(!appData.epd) {
15197     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15198
15199     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15200                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15201         startingEngine = matchMode = FALSE;
15202         DisplayError("second engine does not play this", 0);
15203         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15204         EditGameEvent(); // switch back to EditGame mode
15205         return;
15206     }
15207
15208     if(!stalling) {
15209       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15210       SendToProgram("force\n", &second);
15211       stalling = 1;
15212       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15213       return;
15214     }
15215   }
15216     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15217     if(appData.matchPause>10000 || appData.matchPause<10)
15218                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15219     wait = SubtractTimeMarks(&now, &pauseStart);
15220     if(wait < appData.matchPause) {
15221         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15222         return;
15223     }
15224     // we are now committed to starting the game
15225     stalling = 0;
15226     DisplayMessage("", "");
15227   if(!appData.epd) {
15228     if (startedFromSetupPosition) {
15229         SendBoard(&second, backwardMostMove);
15230     if (appData.debugMode) {
15231         fprintf(debugFP, "Two Machines\n");
15232     }
15233     }
15234     for (i = backwardMostMove; i < forwardMostMove; i++) {
15235         SendMoveToProgram(i, &second);
15236     }
15237   }
15238
15239     gameMode = TwoMachinesPlay;
15240     pausing = startingEngine = FALSE;
15241     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15242     SetGameInfo();
15243     DisplayTwoMachinesTitle();
15244     firstMove = TRUE;
15245     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15246         onmove = &first;
15247     } else {
15248         onmove = &second;
15249     }
15250     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15251     SendToProgram(first.computerString, &first);
15252     if (first.sendName) {
15253       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15254       SendToProgram(buf, &first);
15255     }
15256   if(!appData.epd) {
15257     SendToProgram(second.computerString, &second);
15258     if (second.sendName) {
15259       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15260       SendToProgram(buf, &second);
15261     }
15262   }
15263
15264     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15265         ResetClocks();
15266         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15267         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15268     }
15269     if (onmove->sendTime) {
15270       if (onmove->useColors) {
15271         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15272       }
15273       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15274     }
15275     if (onmove->useColors) {
15276       SendToProgram(onmove->twoMachinesColor, onmove);
15277     }
15278     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15279 //    SendToProgram("go\n", onmove);
15280     onmove->maybeThinking = TRUE;
15281     SetMachineThinkingEnables();
15282
15283     StartClocks();
15284
15285     if(bookHit) { // [HGM] book: simulate book reply
15286         static char bookMove[MSG_SIZ]; // a bit generous?
15287
15288         programStats.nodes = programStats.depth = programStats.time =
15289         programStats.score = programStats.got_only_move = 0;
15290         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15291
15292         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15293         strcat(bookMove, bookHit);
15294         savedMessage = bookMove; // args for deferred call
15295         savedState = onmove;
15296         ScheduleDelayedEvent(DeferredBookMove, 1);
15297     }
15298 }
15299
15300 void
15301 TrainingEvent ()
15302 {
15303     if (gameMode == Training) {
15304       SetTrainingModeOff();
15305       gameMode = PlayFromGameFile;
15306       DisplayMessage("", _("Training mode off"));
15307     } else {
15308       gameMode = Training;
15309       animateTraining = appData.animate;
15310
15311       /* make sure we are not already at the end of the game */
15312       if (currentMove < forwardMostMove) {
15313         SetTrainingModeOn();
15314         DisplayMessage("", _("Training mode on"));
15315       } else {
15316         gameMode = PlayFromGameFile;
15317         DisplayError(_("Already at end of game"), 0);
15318       }
15319     }
15320     ModeHighlight();
15321 }
15322
15323 void
15324 IcsClientEvent ()
15325 {
15326     if (!appData.icsActive) return;
15327     switch (gameMode) {
15328       case IcsPlayingWhite:
15329       case IcsPlayingBlack:
15330       case IcsObserving:
15331       case IcsIdle:
15332       case BeginningOfGame:
15333       case IcsExamining:
15334         return;
15335
15336       case EditGame:
15337         break;
15338
15339       case EditPosition:
15340         EditPositionDone(TRUE);
15341         break;
15342
15343       case AnalyzeMode:
15344       case AnalyzeFile:
15345         ExitAnalyzeMode();
15346         break;
15347
15348       default:
15349         EditGameEvent();
15350         break;
15351     }
15352
15353     gameMode = IcsIdle;
15354     ModeHighlight();
15355     return;
15356 }
15357
15358 void
15359 EditGameEvent ()
15360 {
15361     int i;
15362
15363     switch (gameMode) {
15364       case Training:
15365         SetTrainingModeOff();
15366         break;
15367       case MachinePlaysWhite:
15368       case MachinePlaysBlack:
15369       case BeginningOfGame:
15370         SendToProgram("force\n", &first);
15371         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15372             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15373                 char buf[MSG_SIZ];
15374                 abortEngineThink = TRUE;
15375                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15376                 SendToProgram(buf, &first);
15377                 DisplayMessage("Aborting engine think", "");
15378                 FreezeUI();
15379             }
15380         }
15381         SetUserThinkingEnables();
15382         break;
15383       case PlayFromGameFile:
15384         (void) StopLoadGameTimer();
15385         if (gameFileFP != NULL) {
15386             gameFileFP = NULL;
15387         }
15388         break;
15389       case EditPosition:
15390         EditPositionDone(TRUE);
15391         break;
15392       case AnalyzeMode:
15393       case AnalyzeFile:
15394         ExitAnalyzeMode();
15395         SendToProgram("force\n", &first);
15396         break;
15397       case TwoMachinesPlay:
15398         GameEnds(EndOfFile, NULL, GE_PLAYER);
15399         ResurrectChessProgram();
15400         SetUserThinkingEnables();
15401         break;
15402       case EndOfGame:
15403         ResurrectChessProgram();
15404         break;
15405       case IcsPlayingBlack:
15406       case IcsPlayingWhite:
15407         DisplayError(_("Warning: You are still playing a game"), 0);
15408         break;
15409       case IcsObserving:
15410         DisplayError(_("Warning: You are still observing a game"), 0);
15411         break;
15412       case IcsExamining:
15413         DisplayError(_("Warning: You are still examining a game"), 0);
15414         break;
15415       case IcsIdle:
15416         break;
15417       case EditGame:
15418       default:
15419         return;
15420     }
15421
15422     pausing = FALSE;
15423     StopClocks();
15424     first.offeredDraw = second.offeredDraw = 0;
15425
15426     if (gameMode == PlayFromGameFile) {
15427         whiteTimeRemaining = timeRemaining[0][currentMove];
15428         blackTimeRemaining = timeRemaining[1][currentMove];
15429         DisplayTitle("");
15430     }
15431
15432     if (gameMode == MachinePlaysWhite ||
15433         gameMode == MachinePlaysBlack ||
15434         gameMode == TwoMachinesPlay ||
15435         gameMode == EndOfGame) {
15436         i = forwardMostMove;
15437         while (i > currentMove) {
15438             SendToProgram("undo\n", &first);
15439             i--;
15440         }
15441         if(!adjustedClock) {
15442         whiteTimeRemaining = timeRemaining[0][currentMove];
15443         blackTimeRemaining = timeRemaining[1][currentMove];
15444         DisplayBothClocks();
15445         }
15446         if (whiteFlag || blackFlag) {
15447             whiteFlag = blackFlag = 0;
15448         }
15449         DisplayTitle("");
15450     }
15451
15452     gameMode = EditGame;
15453     ModeHighlight();
15454     SetGameInfo();
15455 }
15456
15457 void
15458 EditPositionEvent ()
15459 {
15460     int i;
15461     if (gameMode == EditPosition) {
15462         EditGameEvent();
15463         return;
15464     }
15465
15466     EditGameEvent();
15467     if (gameMode != EditGame) return;
15468
15469     gameMode = EditPosition;
15470     ModeHighlight();
15471     SetGameInfo();
15472     CopyBoard(rightsBoard, nullBoard);
15473     if (currentMove > 0)
15474       CopyBoard(boards[0], boards[currentMove]);
15475     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15476       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15477
15478     blackPlaysFirst = !WhiteOnMove(currentMove);
15479     ResetClocks();
15480     currentMove = forwardMostMove = backwardMostMove = 0;
15481     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15482     DisplayMove(-1);
15483     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15484 }
15485
15486 void
15487 ExitAnalyzeMode ()
15488 {
15489     /* [DM] icsEngineAnalyze - possible call from other functions */
15490     if (appData.icsEngineAnalyze) {
15491         appData.icsEngineAnalyze = FALSE;
15492
15493         DisplayMessage("",_("Close ICS engine analyze..."));
15494     }
15495     if (first.analysisSupport && first.analyzing) {
15496       SendToBoth("exit\n");
15497       first.analyzing = second.analyzing = FALSE;
15498     }
15499     thinkOutput[0] = NULLCHAR;
15500 }
15501
15502 void
15503 EditPositionDone (Boolean fakeRights)
15504 {
15505     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15506
15507     startedFromSetupPosition = TRUE;
15508     InitChessProgram(&first, FALSE);
15509     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15510       int r, f;
15511       boards[0][EP_STATUS] = EP_NONE;
15512       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15513       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15514         if(rightsBoard[r][f]) {
15515           ChessSquare p = boards[0][r][f];
15516           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15517           else if(p == king) boards[0][CASTLING][2] = f;
15518           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15519           else rightsBoard[r][f] = 2; // mark for second pass
15520         }
15521       }
15522       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15523         if(rightsBoard[r][f] == 2) {
15524           ChessSquare p = boards[0][r][f];
15525           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15526           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15527         }
15528       }
15529     }
15530     SendToProgram("force\n", &first);
15531     if (blackPlaysFirst) {
15532         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15533         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15534         currentMove = forwardMostMove = backwardMostMove = 1;
15535         CopyBoard(boards[1], boards[0]);
15536     } else {
15537         currentMove = forwardMostMove = backwardMostMove = 0;
15538     }
15539     SendBoard(&first, forwardMostMove);
15540     if (appData.debugMode) {
15541         fprintf(debugFP, "EditPosDone\n");
15542     }
15543     DisplayTitle("");
15544     DisplayMessage("", "");
15545     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15546     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15547     gameMode = EditGame;
15548     ModeHighlight();
15549     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15550     ClearHighlights(); /* [AS] */
15551 }
15552
15553 /* Pause for `ms' milliseconds */
15554 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15555 void
15556 TimeDelay (long ms)
15557 {
15558     TimeMark m1, m2;
15559
15560     GetTimeMark(&m1);
15561     do {
15562         GetTimeMark(&m2);
15563     } while (SubtractTimeMarks(&m2, &m1) < ms);
15564 }
15565
15566 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15567 void
15568 SendMultiLineToICS (char *buf)
15569 {
15570     char temp[MSG_SIZ+1], *p;
15571     int len;
15572
15573     len = strlen(buf);
15574     if (len > MSG_SIZ)
15575       len = MSG_SIZ;
15576
15577     strncpy(temp, buf, len);
15578     temp[len] = 0;
15579
15580     p = temp;
15581     while (*p) {
15582         if (*p == '\n' || *p == '\r')
15583           *p = ' ';
15584         ++p;
15585     }
15586
15587     strcat(temp, "\n");
15588     SendToICS(temp);
15589     SendToPlayer(temp, strlen(temp));
15590 }
15591
15592 void
15593 SetWhiteToPlayEvent ()
15594 {
15595     if (gameMode == EditPosition) {
15596         blackPlaysFirst = FALSE;
15597         DisplayBothClocks();    /* works because currentMove is 0 */
15598     } else if (gameMode == IcsExamining) {
15599         SendToICS(ics_prefix);
15600         SendToICS("tomove white\n");
15601     }
15602 }
15603
15604 void
15605 SetBlackToPlayEvent ()
15606 {
15607     if (gameMode == EditPosition) {
15608         blackPlaysFirst = TRUE;
15609         currentMove = 1;        /* kludge */
15610         DisplayBothClocks();
15611         currentMove = 0;
15612     } else if (gameMode == IcsExamining) {
15613         SendToICS(ics_prefix);
15614         SendToICS("tomove black\n");
15615     }
15616 }
15617
15618 void
15619 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15620 {
15621     char buf[MSG_SIZ];
15622     ChessSquare piece = boards[0][y][x];
15623     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15624     static int lastVariant;
15625     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15626
15627     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15628
15629     switch (selection) {
15630       case ClearBoard:
15631         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15632         MarkTargetSquares(1);
15633         CopyBoard(currentBoard, boards[0]);
15634         CopyBoard(menuBoard, initialPosition);
15635         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15636             SendToICS(ics_prefix);
15637             SendToICS("bsetup clear\n");
15638         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15639             SendToICS(ics_prefix);
15640             SendToICS("clearboard\n");
15641         } else {
15642             int nonEmpty = 0;
15643             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15644                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15645                 for (y = 0; y < BOARD_HEIGHT; y++) {
15646                     if (gameMode == IcsExamining) {
15647                         if (boards[currentMove][y][x] != EmptySquare) {
15648                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15649                                     AAA + x, ONE + y);
15650                             SendToICS(buf);
15651                         }
15652                     } else if(boards[0][y][x] != DarkSquare) {
15653                         if(boards[0][y][x] != p) nonEmpty++;
15654                         boards[0][y][x] = p;
15655                     }
15656                 }
15657             }
15658             CopyBoard(rightsBoard, nullBoard);
15659             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15660                 int r, i;
15661                 for(r = 0; r < BOARD_HEIGHT; r++) {
15662                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15663                     ChessSquare p = menuBoard[r][x];
15664                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15665                   }
15666                 }
15667                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15668                 DisplayMessage("Clicking clock again restores position", "");
15669                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15670                 if(!nonEmpty) { // asked to clear an empty board
15671                     CopyBoard(boards[0], menuBoard);
15672                 } else
15673                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15674                     CopyBoard(boards[0], initialPosition);
15675                 } else
15676                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15677                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15678                     CopyBoard(boards[0], erasedBoard);
15679                 } else
15680                     CopyBoard(erasedBoard, currentBoard);
15681
15682                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15683                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15684             }
15685         }
15686         if (gameMode == EditPosition) {
15687             DrawPosition(FALSE, boards[0]);
15688         }
15689         break;
15690
15691       case WhitePlay:
15692         SetWhiteToPlayEvent();
15693         break;
15694
15695       case BlackPlay:
15696         SetBlackToPlayEvent();
15697         break;
15698
15699       case EmptySquare:
15700         if (gameMode == IcsExamining) {
15701             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15702             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15703             SendToICS(buf);
15704         } else {
15705             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15706                 if(x == BOARD_LEFT-2) {
15707                     if(y < handSize-1-gameInfo.holdingsSize) break;
15708                     boards[0][y][1] = 0;
15709                 } else
15710                 if(x == BOARD_RGHT+1) {
15711                     if(y >= gameInfo.holdingsSize) break;
15712                     boards[0][y][BOARD_WIDTH-2] = 0;
15713                 } else break;
15714             }
15715             boards[0][y][x] = EmptySquare;
15716             DrawPosition(FALSE, boards[0]);
15717         }
15718         break;
15719
15720       case PromotePiece:
15721         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15722            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15723             selection = (ChessSquare) (PROMOTED(piece));
15724         } else if(piece == EmptySquare) selection = WhiteSilver;
15725         else selection = (ChessSquare)((int)piece - 1);
15726         goto defaultlabel;
15727
15728       case DemotePiece:
15729         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15730            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15731             selection = (ChessSquare) (DEMOTED(piece));
15732         } else if(piece == EmptySquare) selection = BlackSilver;
15733         else selection = (ChessSquare)((int)piece + 1);
15734         goto defaultlabel;
15735
15736       case WhiteQueen:
15737       case BlackQueen:
15738         if(gameInfo.variant == VariantShatranj ||
15739            gameInfo.variant == VariantXiangqi  ||
15740            gameInfo.variant == VariantCourier  ||
15741            gameInfo.variant == VariantASEAN    ||
15742            gameInfo.variant == VariantMakruk     )
15743             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15744         goto defaultlabel;
15745
15746       case WhiteRook:
15747         baseRank = 0;
15748       case BlackRook:
15749         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15750         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15751         goto defaultlabel;
15752
15753       case WhiteKing:
15754         baseRank = 0;
15755       case BlackKing:
15756         if(gameInfo.variant == VariantXiangqi)
15757             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15758         if(gameInfo.variant == VariantKnightmate)
15759             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15760         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15761       default:
15762         defaultlabel:
15763         if (gameMode == IcsExamining) {
15764             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15765             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15766                      PieceToChar(selection), AAA + x, ONE + y);
15767             SendToICS(buf);
15768         } else {
15769             rightsBoard[y][x] = hasRights;
15770             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15771                 int n;
15772                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15773                     n = PieceToNumber(selection - BlackPawn);
15774                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15775                     boards[0][handSize-1-n][0] = selection;
15776                     boards[0][handSize-1-n][1]++;
15777                 } else
15778                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15779                     n = PieceToNumber(selection);
15780                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15781                     boards[0][n][BOARD_WIDTH-1] = selection;
15782                     boards[0][n][BOARD_WIDTH-2]++;
15783                 }
15784             } else
15785             boards[0][y][x] = selection;
15786             DrawPosition(TRUE, boards[0]);
15787             ClearHighlights();
15788             fromX = fromY = -1;
15789         }
15790         break;
15791     }
15792 }
15793
15794
15795 void
15796 DropMenuEvent (ChessSquare selection, int x, int y)
15797 {
15798     ChessMove moveType;
15799
15800     switch (gameMode) {
15801       case IcsPlayingWhite:
15802       case MachinePlaysBlack:
15803         if (!WhiteOnMove(currentMove)) {
15804             DisplayMoveError(_("It is Black's turn"));
15805             return;
15806         }
15807         moveType = WhiteDrop;
15808         break;
15809       case IcsPlayingBlack:
15810       case MachinePlaysWhite:
15811         if (WhiteOnMove(currentMove)) {
15812             DisplayMoveError(_("It is White's turn"));
15813             return;
15814         }
15815         moveType = BlackDrop;
15816         break;
15817       case EditGame:
15818         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15819         break;
15820       default:
15821         return;
15822     }
15823
15824     if (moveType == BlackDrop && selection < BlackPawn) {
15825       selection = (ChessSquare) ((int) selection
15826                                  + (int) BlackPawn - (int) WhitePawn);
15827     }
15828     if (boards[currentMove][y][x] != EmptySquare) {
15829         DisplayMoveError(_("That square is occupied"));
15830         return;
15831     }
15832
15833     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15834 }
15835
15836 void
15837 AcceptEvent ()
15838 {
15839     /* Accept a pending offer of any kind from opponent */
15840
15841     if (appData.icsActive) {
15842         SendToICS(ics_prefix);
15843         SendToICS("accept\n");
15844     } else if (cmailMsgLoaded) {
15845         if (currentMove == cmailOldMove &&
15846             commentList[cmailOldMove] != NULL &&
15847             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15848                    "Black offers a draw" : "White offers a draw")) {
15849             TruncateGame();
15850             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15851             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15852         } else {
15853             DisplayError(_("There is no pending offer on this move"), 0);
15854             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15855         }
15856     } else {
15857         /* Not used for offers from chess program */
15858     }
15859 }
15860
15861 void
15862 DeclineEvent ()
15863 {
15864     /* Decline a pending offer of any kind from opponent */
15865
15866     if (appData.icsActive) {
15867         SendToICS(ics_prefix);
15868         SendToICS("decline\n");
15869     } else if (cmailMsgLoaded) {
15870         if (currentMove == cmailOldMove &&
15871             commentList[cmailOldMove] != NULL &&
15872             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15873                    "Black offers a draw" : "White offers a draw")) {
15874 #ifdef NOTDEF
15875             AppendComment(cmailOldMove, "Draw declined", TRUE);
15876             DisplayComment(cmailOldMove - 1, "Draw declined");
15877 #endif /*NOTDEF*/
15878         } else {
15879             DisplayError(_("There is no pending offer on this move"), 0);
15880         }
15881     } else {
15882         /* Not used for offers from chess program */
15883     }
15884 }
15885
15886 void
15887 RematchEvent ()
15888 {
15889     /* Issue ICS rematch command */
15890     if (appData.icsActive) {
15891         SendToICS(ics_prefix);
15892         SendToICS("rematch\n");
15893     }
15894 }
15895
15896 void
15897 CallFlagEvent ()
15898 {
15899     /* Call your opponent's flag (claim a win on time) */
15900     if (appData.icsActive) {
15901         SendToICS(ics_prefix);
15902         SendToICS("flag\n");
15903     } else {
15904         switch (gameMode) {
15905           default:
15906             return;
15907           case MachinePlaysWhite:
15908             if (whiteFlag) {
15909                 if (blackFlag)
15910                   GameEnds(GameIsDrawn, "Both players ran out of time",
15911                            GE_PLAYER);
15912                 else
15913                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15914             } else {
15915                 DisplayError(_("Your opponent is not out of time"), 0);
15916             }
15917             break;
15918           case MachinePlaysBlack:
15919             if (blackFlag) {
15920                 if (whiteFlag)
15921                   GameEnds(GameIsDrawn, "Both players ran out of time",
15922                            GE_PLAYER);
15923                 else
15924                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15925             } else {
15926                 DisplayError(_("Your opponent is not out of time"), 0);
15927             }
15928             break;
15929         }
15930     }
15931 }
15932
15933 void
15934 ClockClick (int which)
15935 {       // [HGM] code moved to back-end from winboard.c
15936         if(which) { // black clock
15937           if (gameMode == EditPosition || gameMode == IcsExamining) {
15938             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15939             SetBlackToPlayEvent();
15940           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15941                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15942           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15943           } else if (shiftKey) {
15944             AdjustClock(which, -1);
15945           } else if (gameMode == IcsPlayingWhite ||
15946                      gameMode == MachinePlaysBlack) {
15947             CallFlagEvent();
15948           }
15949         } else { // white clock
15950           if (gameMode == EditPosition || gameMode == IcsExamining) {
15951             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15952             SetWhiteToPlayEvent();
15953           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15954                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15955           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15956           } else if (shiftKey) {
15957             AdjustClock(which, -1);
15958           } else if (gameMode == IcsPlayingBlack ||
15959                    gameMode == MachinePlaysWhite) {
15960             CallFlagEvent();
15961           }
15962         }
15963 }
15964
15965 void
15966 DrawEvent ()
15967 {
15968     /* Offer draw or accept pending draw offer from opponent */
15969
15970     if (appData.icsActive) {
15971         /* Note: tournament rules require draw offers to be
15972            made after you make your move but before you punch
15973            your clock.  Currently ICS doesn't let you do that;
15974            instead, you immediately punch your clock after making
15975            a move, but you can offer a draw at any time. */
15976
15977         SendToICS(ics_prefix);
15978         SendToICS("draw\n");
15979         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15980     } else if (cmailMsgLoaded) {
15981         if (currentMove == cmailOldMove &&
15982             commentList[cmailOldMove] != NULL &&
15983             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15984                    "Black offers a draw" : "White offers a draw")) {
15985             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15986             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15987         } else if (currentMove == cmailOldMove + 1) {
15988             char *offer = WhiteOnMove(cmailOldMove) ?
15989               "White offers a draw" : "Black offers a draw";
15990             AppendComment(currentMove, offer, TRUE);
15991             DisplayComment(currentMove - 1, offer);
15992             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15993         } else {
15994             DisplayError(_("You must make your move before offering a draw"), 0);
15995             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15996         }
15997     } else if (first.offeredDraw) {
15998         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15999     } else {
16000         if (first.sendDrawOffers) {
16001             SendToProgram("draw\n", &first);
16002             userOfferedDraw = TRUE;
16003         }
16004     }
16005 }
16006
16007 void
16008 AdjournEvent ()
16009 {
16010     /* Offer Adjourn or accept pending Adjourn offer from opponent */
16011
16012     if (appData.icsActive) {
16013         SendToICS(ics_prefix);
16014         SendToICS("adjourn\n");
16015     } else {
16016         /* Currently GNU Chess doesn't offer or accept Adjourns */
16017     }
16018 }
16019
16020
16021 void
16022 AbortEvent ()
16023 {
16024     /* Offer Abort or accept pending Abort offer from opponent */
16025
16026     if (appData.icsActive) {
16027         SendToICS(ics_prefix);
16028         SendToICS("abort\n");
16029     } else {
16030         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
16031     }
16032 }
16033
16034 void
16035 ResignEvent ()
16036 {
16037     /* Resign.  You can do this even if it's not your turn. */
16038
16039     if (appData.icsActive) {
16040         SendToICS(ics_prefix);
16041         SendToICS("resign\n");
16042     } else {
16043         switch (gameMode) {
16044           case MachinePlaysWhite:
16045             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16046             break;
16047           case MachinePlaysBlack:
16048             GameEnds(BlackWins, "White resigns", GE_PLAYER);
16049             break;
16050           case EditGame:
16051             if (cmailMsgLoaded) {
16052                 TruncateGame();
16053                 if (WhiteOnMove(cmailOldMove)) {
16054                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
16055                 } else {
16056                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16057                 }
16058                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16059             }
16060             break;
16061           default:
16062             break;
16063         }
16064     }
16065 }
16066
16067
16068 void
16069 StopObservingEvent ()
16070 {
16071     /* Stop observing current games */
16072     SendToICS(ics_prefix);
16073     SendToICS("unobserve\n");
16074 }
16075
16076 void
16077 StopExaminingEvent ()
16078 {
16079     /* Stop observing current game */
16080     SendToICS(ics_prefix);
16081     SendToICS("unexamine\n");
16082 }
16083
16084 void
16085 ForwardInner (int target)
16086 {
16087     int limit; int oldSeekGraphUp = seekGraphUp;
16088
16089     if (appData.debugMode)
16090         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16091                 target, currentMove, forwardMostMove);
16092
16093     if (gameMode == EditPosition)
16094       return;
16095
16096     seekGraphUp = FALSE;
16097     MarkTargetSquares(1);
16098     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16099
16100     if (gameMode == PlayFromGameFile && !pausing)
16101       PauseEvent();
16102
16103     if (gameMode == IcsExamining && pausing)
16104       limit = pauseExamForwardMostMove;
16105     else
16106       limit = forwardMostMove;
16107
16108     if (target > limit) target = limit;
16109
16110     if (target > 0 && moveList[target - 1][0]) {
16111         int fromX, fromY, toX, toY;
16112         toX = moveList[target - 1][2] - AAA;
16113         toY = moveList[target - 1][3] - ONE;
16114         if (moveList[target - 1][1] == '@') {
16115             if (appData.highlightLastMove) {
16116                 SetHighlights(-1, -1, toX, toY);
16117             }
16118         } else {
16119             fromX = moveList[target - 1][0] - AAA;
16120             fromY = moveList[target - 1][1] - ONE;
16121             if (target == currentMove + 1) {
16122                 if(moveList[target - 1][4] == ';') { // multi-leg
16123                     killX = moveList[target - 1][5] - AAA;
16124                     killY = moveList[target - 1][6] - ONE;
16125                 }
16126                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16127                 killX = killY = -1;
16128             }
16129             if (appData.highlightLastMove) {
16130                 SetHighlights(fromX, fromY, toX, toY);
16131             }
16132         }
16133     }
16134     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16135         gameMode == Training || gameMode == PlayFromGameFile ||
16136         gameMode == AnalyzeFile) {
16137         while (currentMove < target) {
16138             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16139             SendMoveToProgram(currentMove++, &first);
16140         }
16141     } else {
16142         currentMove = target;
16143     }
16144
16145     if (gameMode == EditGame || gameMode == EndOfGame) {
16146         whiteTimeRemaining = timeRemaining[0][currentMove];
16147         blackTimeRemaining = timeRemaining[1][currentMove];
16148     }
16149     DisplayBothClocks();
16150     DisplayMove(currentMove - 1);
16151     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16152     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16153     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16154         DisplayComment(currentMove - 1, commentList[currentMove]);
16155     }
16156     ClearMap(); // [HGM] exclude: invalidate map
16157 }
16158
16159
16160 void
16161 ForwardEvent ()
16162 {
16163     if (gameMode == IcsExamining && !pausing) {
16164         SendToICS(ics_prefix);
16165         SendToICS("forward\n");
16166     } else {
16167         ForwardInner(currentMove + 1);
16168     }
16169 }
16170
16171 void
16172 ToEndEvent ()
16173 {
16174     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16175         /* to optimze, we temporarily turn off analysis mode while we feed
16176          * the remaining moves to the engine. Otherwise we get analysis output
16177          * after each move.
16178          */
16179         if (first.analysisSupport) {
16180           SendToProgram("exit\nforce\n", &first);
16181           first.analyzing = FALSE;
16182         }
16183     }
16184
16185     if (gameMode == IcsExamining && !pausing) {
16186         SendToICS(ics_prefix);
16187         SendToICS("forward 999999\n");
16188     } else {
16189         ForwardInner(forwardMostMove);
16190     }
16191
16192     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16193         /* we have fed all the moves, so reactivate analysis mode */
16194         SendToProgram("analyze\n", &first);
16195         first.analyzing = TRUE;
16196         /*first.maybeThinking = TRUE;*/
16197         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16198     }
16199 }
16200
16201 void
16202 BackwardInner (int target)
16203 {
16204     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16205
16206     if (appData.debugMode)
16207         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16208                 target, currentMove, forwardMostMove);
16209
16210     if (gameMode == EditPosition) return;
16211     seekGraphUp = FALSE;
16212     MarkTargetSquares(1);
16213     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16214     if (currentMove <= backwardMostMove) {
16215         ClearHighlights();
16216         DrawPosition(full_redraw, boards[currentMove]);
16217         return;
16218     }
16219     if (gameMode == PlayFromGameFile && !pausing)
16220       PauseEvent();
16221
16222     if (moveList[target][0]) {
16223         int fromX, fromY, toX, toY;
16224         toX = moveList[target][2] - AAA;
16225         toY = moveList[target][3] - ONE;
16226         if (moveList[target][1] == '@') {
16227             if (appData.highlightLastMove) {
16228                 SetHighlights(-1, -1, toX, toY);
16229             }
16230         } else {
16231             fromX = moveList[target][0] - AAA;
16232             fromY = moveList[target][1] - ONE;
16233             if (target == currentMove - 1) {
16234                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16235             }
16236             if (appData.highlightLastMove) {
16237                 SetHighlights(fromX, fromY, toX, toY);
16238             }
16239         }
16240     }
16241     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16242         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16243         while (currentMove > target) {
16244             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16245                 // null move cannot be undone. Reload program with move history before it.
16246                 int i;
16247                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16248                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16249                 }
16250                 SendBoard(&first, i);
16251               if(second.analyzing) SendBoard(&second, i);
16252                 for(currentMove=i; currentMove<target; currentMove++) {
16253                     SendMoveToProgram(currentMove, &first);
16254                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16255                 }
16256                 break;
16257             }
16258             SendToBoth("undo\n");
16259             currentMove--;
16260         }
16261     } else {
16262         currentMove = target;
16263     }
16264
16265     if (gameMode == EditGame || gameMode == EndOfGame) {
16266         whiteTimeRemaining = timeRemaining[0][currentMove];
16267         blackTimeRemaining = timeRemaining[1][currentMove];
16268     }
16269     DisplayBothClocks();
16270     DisplayMove(currentMove - 1);
16271     DrawPosition(full_redraw, boards[currentMove]);
16272     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16273     // [HGM] PV info: routine tests if comment empty
16274     DisplayComment(currentMove - 1, commentList[currentMove]);
16275     ClearMap(); // [HGM] exclude: invalidate map
16276 }
16277
16278 void
16279 BackwardEvent ()
16280 {
16281     if (gameMode == IcsExamining && !pausing) {
16282         SendToICS(ics_prefix);
16283         SendToICS("backward\n");
16284     } else {
16285         BackwardInner(currentMove - 1);
16286     }
16287 }
16288
16289 void
16290 ToStartEvent ()
16291 {
16292     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16293         /* to optimize, we temporarily turn off analysis mode while we undo
16294          * all the moves. Otherwise we get analysis output after each undo.
16295          */
16296         if (first.analysisSupport) {
16297           SendToProgram("exit\nforce\n", &first);
16298           first.analyzing = FALSE;
16299         }
16300     }
16301
16302     if (gameMode == IcsExamining && !pausing) {
16303         SendToICS(ics_prefix);
16304         SendToICS("backward 999999\n");
16305     } else {
16306         BackwardInner(backwardMostMove);
16307     }
16308
16309     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16310         /* we have fed all the moves, so reactivate analysis mode */
16311         SendToProgram("analyze\n", &first);
16312         first.analyzing = TRUE;
16313         /*first.maybeThinking = TRUE;*/
16314         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16315     }
16316 }
16317
16318 void
16319 ToNrEvent (int to)
16320 {
16321   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16322   if (to >= forwardMostMove) to = forwardMostMove;
16323   if (to <= backwardMostMove) to = backwardMostMove;
16324   if (to < currentMove) {
16325     BackwardInner(to);
16326   } else {
16327     ForwardInner(to);
16328   }
16329 }
16330
16331 void
16332 RevertEvent (Boolean annotate)
16333 {
16334     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16335         return;
16336     }
16337     if (gameMode != IcsExamining) {
16338         DisplayError(_("You are not examining a game"), 0);
16339         return;
16340     }
16341     if (pausing) {
16342         DisplayError(_("You can't revert while pausing"), 0);
16343         return;
16344     }
16345     SendToICS(ics_prefix);
16346     SendToICS("revert\n");
16347 }
16348
16349 void
16350 RetractMoveEvent ()
16351 {
16352     switch (gameMode) {
16353       case MachinePlaysWhite:
16354       case MachinePlaysBlack:
16355         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16356             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16357             return;
16358         }
16359         if (forwardMostMove < 2) return;
16360         currentMove = forwardMostMove = forwardMostMove - 2;
16361         whiteTimeRemaining = timeRemaining[0][currentMove];
16362         blackTimeRemaining = timeRemaining[1][currentMove];
16363         DisplayBothClocks();
16364         DisplayMove(currentMove - 1);
16365         ClearHighlights();/*!! could figure this out*/
16366         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16367         SendToProgram("remove\n", &first);
16368         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16369         break;
16370
16371       case BeginningOfGame:
16372       default:
16373         break;
16374
16375       case IcsPlayingWhite:
16376       case IcsPlayingBlack:
16377         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16378             SendToICS(ics_prefix);
16379             SendToICS("takeback 2\n");
16380         } else {
16381             SendToICS(ics_prefix);
16382             SendToICS("takeback 1\n");
16383         }
16384         break;
16385     }
16386 }
16387
16388 void
16389 MoveNowEvent ()
16390 {
16391     ChessProgramState *cps;
16392
16393     switch (gameMode) {
16394       case MachinePlaysWhite:
16395         if (!WhiteOnMove(forwardMostMove)) {
16396             DisplayError(_("It is your turn"), 0);
16397             return;
16398         }
16399         cps = &first;
16400         break;
16401       case MachinePlaysBlack:
16402         if (WhiteOnMove(forwardMostMove)) {
16403             DisplayError(_("It is your turn"), 0);
16404             return;
16405         }
16406         cps = &first;
16407         break;
16408       case TwoMachinesPlay:
16409         if (WhiteOnMove(forwardMostMove) ==
16410             (first.twoMachinesColor[0] == 'w')) {
16411             cps = &first;
16412         } else {
16413             cps = &second;
16414         }
16415         break;
16416       case BeginningOfGame:
16417       default:
16418         return;
16419     }
16420     SendToProgram("?\n", cps);
16421 }
16422
16423 void
16424 TruncateGameEvent ()
16425 {
16426     EditGameEvent();
16427     if (gameMode != EditGame) return;
16428     TruncateGame();
16429 }
16430
16431 void
16432 TruncateGame ()
16433 {
16434     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16435     if (forwardMostMove > currentMove) {
16436         if (gameInfo.resultDetails != NULL) {
16437             free(gameInfo.resultDetails);
16438             gameInfo.resultDetails = NULL;
16439             gameInfo.result = GameUnfinished;
16440         }
16441         forwardMostMove = currentMove;
16442         HistorySet(parseList, backwardMostMove, forwardMostMove,
16443                    currentMove-1);
16444     }
16445 }
16446
16447 void
16448 HintEvent ()
16449 {
16450     if (appData.noChessProgram) return;
16451     switch (gameMode) {
16452       case MachinePlaysWhite:
16453         if (WhiteOnMove(forwardMostMove)) {
16454             DisplayError(_("Wait until your turn."), 0);
16455             return;
16456         }
16457         break;
16458       case BeginningOfGame:
16459       case MachinePlaysBlack:
16460         if (!WhiteOnMove(forwardMostMove)) {
16461             DisplayError(_("Wait until your turn."), 0);
16462             return;
16463         }
16464         break;
16465       default:
16466         DisplayError(_("No hint available"), 0);
16467         return;
16468     }
16469     SendToProgram("hint\n", &first);
16470     hintRequested = TRUE;
16471 }
16472
16473 int
16474 SaveSelected (FILE *g, int dummy, char *dummy2)
16475 {
16476     ListGame * lg = (ListGame *) gameList.head;
16477     int nItem, cnt=0;
16478     FILE *f;
16479
16480     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16481         DisplayError(_("Game list not loaded or empty"), 0);
16482         return 0;
16483     }
16484
16485     creatingBook = TRUE; // suppresses stuff during load game
16486
16487     /* Get list size */
16488     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16489         if(lg->position >= 0) { // selected?
16490             LoadGame(f, nItem, "", TRUE);
16491             SaveGamePGN2(g); // leaves g open
16492             cnt++; DoEvents();
16493         }
16494         lg = (ListGame *) lg->node.succ;
16495     }
16496
16497     fclose(g);
16498     creatingBook = FALSE;
16499
16500     return cnt;
16501 }
16502
16503 void
16504 CreateBookEvent ()
16505 {
16506     ListGame * lg = (ListGame *) gameList.head;
16507     FILE *f, *g;
16508     int nItem;
16509     static int secondTime = FALSE;
16510
16511     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16512         DisplayError(_("Game list not loaded or empty"), 0);
16513         return;
16514     }
16515
16516     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16517         fclose(g);
16518         secondTime++;
16519         DisplayNote(_("Book file exists! Try again for overwrite."));
16520         return;
16521     }
16522
16523     creatingBook = TRUE;
16524     secondTime = FALSE;
16525
16526     /* Get list size */
16527     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16528         if(lg->position >= 0) {
16529             LoadGame(f, nItem, "", TRUE);
16530             AddGameToBook(TRUE);
16531             DoEvents();
16532         }
16533         lg = (ListGame *) lg->node.succ;
16534     }
16535
16536     creatingBook = FALSE;
16537     FlushBook();
16538 }
16539
16540 void
16541 BookEvent ()
16542 {
16543     if (appData.noChessProgram) return;
16544     switch (gameMode) {
16545       case MachinePlaysWhite:
16546         if (WhiteOnMove(forwardMostMove)) {
16547             DisplayError(_("Wait until your turn."), 0);
16548             return;
16549         }
16550         break;
16551       case BeginningOfGame:
16552       case MachinePlaysBlack:
16553         if (!WhiteOnMove(forwardMostMove)) {
16554             DisplayError(_("Wait until your turn."), 0);
16555             return;
16556         }
16557         break;
16558       case EditPosition:
16559         EditPositionDone(TRUE);
16560         break;
16561       case TwoMachinesPlay:
16562         return;
16563       default:
16564         break;
16565     }
16566     SendToProgram("bk\n", &first);
16567     bookOutput[0] = NULLCHAR;
16568     bookRequested = TRUE;
16569 }
16570
16571 void
16572 AboutGameEvent ()
16573 {
16574     char *tags = PGNTags(&gameInfo);
16575     TagsPopUp(tags, CmailMsg());
16576     free(tags);
16577 }
16578
16579 /* end button procedures */
16580
16581 void
16582 PrintPosition (FILE *fp, int move)
16583 {
16584     int i, j;
16585
16586     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16587         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16588             char c = PieceToChar(boards[move][i][j]);
16589             fputc(c == '?' ? '.' : c, fp);
16590             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16591         }
16592     }
16593     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16594       fprintf(fp, "white to play\n");
16595     else
16596       fprintf(fp, "black to play\n");
16597 }
16598
16599 void
16600 PrintOpponents (FILE *fp)
16601 {
16602     if (gameInfo.white != NULL) {
16603         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16604     } else {
16605         fprintf(fp, "\n");
16606     }
16607 }
16608
16609 /* Find last component of program's own name, using some heuristics */
16610 void
16611 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16612 {
16613     char *p, *q, c;
16614     int local = (strcmp(host, "localhost") == 0);
16615     while (!local && (p = strchr(prog, ';')) != NULL) {
16616         p++;
16617         while (*p == ' ') p++;
16618         prog = p;
16619     }
16620     if (*prog == '"' || *prog == '\'') {
16621         q = strchr(prog + 1, *prog);
16622     } else {
16623         q = strchr(prog, ' ');
16624     }
16625     if (q == NULL) q = prog + strlen(prog);
16626     p = q;
16627     while (p >= prog && *p != '/' && *p != '\\') p--;
16628     p++;
16629     if(p == prog && *p == '"') p++;
16630     c = *q; *q = 0;
16631     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16632     memcpy(buf, p, q - p);
16633     buf[q - p] = NULLCHAR;
16634     if (!local) {
16635         strcat(buf, "@");
16636         strcat(buf, host);
16637     }
16638 }
16639
16640 char *
16641 TimeControlTagValue ()
16642 {
16643     char buf[MSG_SIZ];
16644     if (!appData.clockMode) {
16645       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16646     } else if (movesPerSession > 0) {
16647       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16648     } else if (timeIncrement == 0) {
16649       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16650     } else {
16651       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16652     }
16653     return StrSave(buf);
16654 }
16655
16656 void
16657 SetGameInfo ()
16658 {
16659     /* This routine is used only for certain modes */
16660     VariantClass v = gameInfo.variant;
16661     ChessMove r = GameUnfinished;
16662     char *p = NULL;
16663
16664     if(keepInfo) return;
16665
16666     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16667         r = gameInfo.result;
16668         p = gameInfo.resultDetails;
16669         gameInfo.resultDetails = NULL;
16670     }
16671     ClearGameInfo(&gameInfo);
16672     gameInfo.variant = v;
16673
16674     switch (gameMode) {
16675       case MachinePlaysWhite:
16676         gameInfo.event = StrSave( appData.pgnEventHeader );
16677         gameInfo.site = StrSave(HostName());
16678         gameInfo.date = PGNDate();
16679         gameInfo.round = StrSave("-");
16680         gameInfo.white = StrSave(first.tidy);
16681         gameInfo.black = StrSave(UserName());
16682         gameInfo.timeControl = TimeControlTagValue();
16683         break;
16684
16685       case MachinePlaysBlack:
16686         gameInfo.event = StrSave( appData.pgnEventHeader );
16687         gameInfo.site = StrSave(HostName());
16688         gameInfo.date = PGNDate();
16689         gameInfo.round = StrSave("-");
16690         gameInfo.white = StrSave(UserName());
16691         gameInfo.black = StrSave(first.tidy);
16692         gameInfo.timeControl = TimeControlTagValue();
16693         break;
16694
16695       case TwoMachinesPlay:
16696         gameInfo.event = StrSave( appData.pgnEventHeader );
16697         gameInfo.site = StrSave(HostName());
16698         gameInfo.date = PGNDate();
16699         if (roundNr > 0) {
16700             char buf[MSG_SIZ];
16701             snprintf(buf, MSG_SIZ, "%d", roundNr);
16702             gameInfo.round = StrSave(buf);
16703         } else {
16704             gameInfo.round = StrSave("-");
16705         }
16706         if (first.twoMachinesColor[0] == 'w') {
16707             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16708             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16709         } else {
16710             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16711             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16712         }
16713         gameInfo.timeControl = TimeControlTagValue();
16714         break;
16715
16716       case EditGame:
16717         gameInfo.event = StrSave("Edited game");
16718         gameInfo.site = StrSave(HostName());
16719         gameInfo.date = PGNDate();
16720         gameInfo.round = StrSave("-");
16721         gameInfo.white = StrSave("-");
16722         gameInfo.black = StrSave("-");
16723         gameInfo.result = r;
16724         gameInfo.resultDetails = p;
16725         break;
16726
16727       case EditPosition:
16728         gameInfo.event = StrSave("Edited position");
16729         gameInfo.site = StrSave(HostName());
16730         gameInfo.date = PGNDate();
16731         gameInfo.round = StrSave("-");
16732         gameInfo.white = StrSave("-");
16733         gameInfo.black = StrSave("-");
16734         break;
16735
16736       case IcsPlayingWhite:
16737       case IcsPlayingBlack:
16738       case IcsObserving:
16739       case IcsExamining:
16740         break;
16741
16742       case PlayFromGameFile:
16743         gameInfo.event = StrSave("Game from non-PGN file");
16744         gameInfo.site = StrSave(HostName());
16745         gameInfo.date = PGNDate();
16746         gameInfo.round = StrSave("-");
16747         gameInfo.white = StrSave("?");
16748         gameInfo.black = StrSave("?");
16749         break;
16750
16751       default:
16752         break;
16753     }
16754 }
16755
16756 void
16757 ReplaceComment (int index, char *text)
16758 {
16759     int len;
16760     char *p;
16761     float score;
16762
16763     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16764        pvInfoList[index-1].depth == len &&
16765        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16766        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16767     while (*text == '\n') text++;
16768     len = strlen(text);
16769     while (len > 0 && text[len - 1] == '\n') len--;
16770
16771     if (commentList[index] != NULL)
16772       free(commentList[index]);
16773
16774     if (len == 0) {
16775         commentList[index] = NULL;
16776         return;
16777     }
16778   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16779       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16780       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16781     commentList[index] = (char *) malloc(len + 2);
16782     strncpy(commentList[index], text, len);
16783     commentList[index][len] = '\n';
16784     commentList[index][len + 1] = NULLCHAR;
16785   } else {
16786     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16787     char *p;
16788     commentList[index] = (char *) malloc(len + 7);
16789     safeStrCpy(commentList[index], "{\n", 3);
16790     safeStrCpy(commentList[index]+2, text, len+1);
16791     commentList[index][len+2] = NULLCHAR;
16792     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16793     strcat(commentList[index], "\n}\n");
16794   }
16795 }
16796
16797 void
16798 CrushCRs (char *text)
16799 {
16800   char *p = text;
16801   char *q = text;
16802   char ch;
16803
16804   do {
16805     ch = *p++;
16806     if (ch == '\r') continue;
16807     *q++ = ch;
16808   } while (ch != '\0');
16809 }
16810
16811 void
16812 AppendComment (int index, char *text, Boolean addBraces)
16813 /* addBraces  tells if we should add {} */
16814 {
16815     int oldlen, len;
16816     char *old;
16817
16818 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16819     if(addBraces == 3) addBraces = 0; else // force appending literally
16820     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16821
16822     CrushCRs(text);
16823     while (*text == '\n') text++;
16824     len = strlen(text);
16825     while (len > 0 && text[len - 1] == '\n') len--;
16826     text[len] = NULLCHAR;
16827
16828     if (len == 0) return;
16829
16830     if (commentList[index] != NULL) {
16831       Boolean addClosingBrace = addBraces;
16832         old = commentList[index];
16833         oldlen = strlen(old);
16834         while(commentList[index][oldlen-1] ==  '\n')
16835           commentList[index][--oldlen] = NULLCHAR;
16836         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16837         safeStrCpy(commentList[index], old, oldlen + len + 6);
16838         free(old);
16839         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16840         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16841           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16842           while (*text == '\n') { text++; len--; }
16843           commentList[index][--oldlen] = NULLCHAR;
16844       }
16845         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16846         else          strcat(commentList[index], "\n");
16847         strcat(commentList[index], text);
16848         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16849         else          strcat(commentList[index], "\n");
16850     } else {
16851         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16852         if(addBraces)
16853           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16854         else commentList[index][0] = NULLCHAR;
16855         strcat(commentList[index], text);
16856         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16857         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16858     }
16859 }
16860
16861 static char *
16862 FindStr (char * text, char * sub_text)
16863 {
16864     char * result = strstr( text, sub_text );
16865
16866     if( result != NULL ) {
16867         result += strlen( sub_text );
16868     }
16869
16870     return result;
16871 }
16872
16873 /* [AS] Try to extract PV info from PGN comment */
16874 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16875 char *
16876 GetInfoFromComment (int index, char * text)
16877 {
16878     char * sep = text, *p;
16879
16880     if( text != NULL && index > 0 ) {
16881         int score = 0;
16882         int depth = 0;
16883         int time = -1, sec = 0, deci;
16884         char * s_eval = FindStr( text, "[%eval " );
16885         char * s_emt = FindStr( text, "[%emt " );
16886 #if 0
16887         if( s_eval != NULL || s_emt != NULL ) {
16888 #else
16889         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16890 #endif
16891             /* New style */
16892             char delim;
16893
16894             if( s_eval != NULL ) {
16895                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16896                     return text;
16897                 }
16898
16899                 if( delim != ']' ) {
16900                     return text;
16901                 }
16902             }
16903
16904             if( s_emt != NULL ) {
16905             }
16906                 return text;
16907         }
16908         else {
16909             /* We expect something like: [+|-]nnn.nn/dd */
16910             int score_lo = 0;
16911
16912             if(*text != '{') return text; // [HGM] braces: must be normal comment
16913
16914             sep = strchr( text, '/' );
16915             if( sep == NULL || sep < (text+4) ) {
16916                 return text;
16917             }
16918
16919             p = text;
16920             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16921             if(p[1] == '(') { // comment starts with PV
16922                p = strchr(p, ')'); // locate end of PV
16923                if(p == NULL || sep < p+5) return text;
16924                // at this point we have something like "{(.*) +0.23/6 ..."
16925                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16926                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16927                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16928             }
16929             time = -1; sec = -1; deci = -1;
16930             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16931                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16932                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16933                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16934                 return text;
16935             }
16936
16937             if( score_lo < 0 || score_lo >= 100 ) {
16938                 return text;
16939             }
16940
16941             if(sec >= 0) time = 600*time + 10*sec; else
16942             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16943
16944             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16945
16946             /* [HGM] PV time: now locate end of PV info */
16947             while( *++sep >= '0' && *sep <= '9'); // strip depth
16948             if(time >= 0)
16949             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16950             if(sec >= 0)
16951             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16952             if(deci >= 0)
16953             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16954             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16955         }
16956
16957         if( depth <= 0 ) {
16958             return text;
16959         }
16960
16961         if( time < 0 ) {
16962             time = -1;
16963         }
16964
16965         pvInfoList[index-1].depth = depth;
16966         pvInfoList[index-1].score = score;
16967         pvInfoList[index-1].time  = 10*time; // centi-sec
16968         if(*sep == '}') *sep = 0; else *--sep = '{';
16969         if(p != text) {
16970             while(*p++ = *sep++)
16971                                 ;
16972             sep = text;
16973         } // squeeze out space between PV and comment, and return both
16974     }
16975     return sep;
16976 }
16977
16978 void
16979 SendToProgram (char *message, ChessProgramState *cps)
16980 {
16981     int count, outCount, error;
16982     char buf[MSG_SIZ];
16983
16984     if (cps->pr == NoProc) return;
16985     Attention(cps);
16986
16987     if (appData.debugMode) {
16988         TimeMark now;
16989         GetTimeMark(&now);
16990         fprintf(debugFP, "%ld >%-6s: %s",
16991                 SubtractTimeMarks(&now, &programStartTime),
16992                 cps->which, message);
16993         if(serverFP)
16994             fprintf(serverFP, "%ld >%-6s: %s",
16995                 SubtractTimeMarks(&now, &programStartTime),
16996                 cps->which, message), fflush(serverFP);
16997     }
16998
16999     count = strlen(message);
17000     outCount = OutputToProcess(cps->pr, message, count, &error);
17001     if (outCount < count && !exiting
17002                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
17003       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
17004       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
17005         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17006             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17007                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17008                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17009                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17010             } else {
17011                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17012                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17013                 gameInfo.result = res;
17014             }
17015             gameInfo.resultDetails = StrSave(buf);
17016         }
17017         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17018         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17019     }
17020 }
17021
17022 void
17023 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
17024 {
17025     char *end_str;
17026     char buf[MSG_SIZ];
17027     ChessProgramState *cps = (ChessProgramState *)closure;
17028
17029     if (isr != cps->isr) return; /* Killed intentionally */
17030     if (count <= 0) {
17031         if (count == 0) {
17032             RemoveInputSource(cps->isr);
17033             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
17034                     _(cps->which), cps->program);
17035             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
17036             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17037                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17038                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17039                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17040                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17041                 } else {
17042                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17043                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17044                     gameInfo.result = res;
17045                 }
17046                 gameInfo.resultDetails = StrSave(buf);
17047             }
17048             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17049             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
17050         } else {
17051             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
17052                     _(cps->which), cps->program);
17053             RemoveInputSource(cps->isr);
17054
17055             /* [AS] Program is misbehaving badly... kill it */
17056             if( count == -2 ) {
17057                 DestroyChildProcess( cps->pr, 9 );
17058                 cps->pr = NoProc;
17059             }
17060
17061             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17062         }
17063         return;
17064     }
17065
17066     if ((end_str = strchr(message, '\r')) != NULL)
17067       *end_str = NULLCHAR;
17068     if ((end_str = strchr(message, '\n')) != NULL)
17069       *end_str = NULLCHAR;
17070
17071     if (appData.debugMode) {
17072         TimeMark now; int print = 1;
17073         char *quote = ""; char c; int i;
17074
17075         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17076                 char start = message[0];
17077                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17078                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17079                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17080                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17081                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17082                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17083                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17084                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17085                    sscanf(message, "hint: %c", &c)!=1 &&
17086                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17087                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17088                     print = (appData.engineComments >= 2);
17089                 }
17090                 message[0] = start; // restore original message
17091         }
17092         if(print) {
17093                 GetTimeMark(&now);
17094                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17095                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17096                         quote,
17097                         message);
17098                 if(serverFP)
17099                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17100                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17101                         quote,
17102                         message), fflush(serverFP);
17103         }
17104     }
17105
17106     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17107     if (appData.icsEngineAnalyze) {
17108         if (strstr(message, "whisper") != NULL ||
17109              strstr(message, "kibitz") != NULL ||
17110             strstr(message, "tellics") != NULL) return;
17111     }
17112
17113     HandleMachineMove(message, cps);
17114 }
17115
17116
17117 void
17118 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17119 {
17120     char buf[MSG_SIZ];
17121     int seconds;
17122
17123     if( timeControl_2 > 0 ) {
17124         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17125             tc = timeControl_2;
17126         }
17127     }
17128     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17129     inc /= cps->timeOdds;
17130     st  /= cps->timeOdds;
17131
17132     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17133
17134     if (st > 0) {
17135       /* Set exact time per move, normally using st command */
17136       if (cps->stKludge) {
17137         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17138         seconds = st % 60;
17139         if (seconds == 0) {
17140           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17141         } else {
17142           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17143         }
17144       } else {
17145         snprintf(buf, MSG_SIZ, "st %d\n", st);
17146       }
17147     } else {
17148       /* Set conventional or incremental time control, using level command */
17149       if (seconds == 0) {
17150         /* Note old gnuchess bug -- minutes:seconds used to not work.
17151            Fixed in later versions, but still avoid :seconds
17152            when seconds is 0. */
17153         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17154       } else {
17155         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17156                  seconds, inc/1000.);
17157       }
17158     }
17159     SendToProgram(buf, cps);
17160
17161     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17162     /* Orthogonally, limit search to given depth */
17163     if (sd > 0) {
17164       if (cps->sdKludge) {
17165         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17166       } else {
17167         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17168       }
17169       SendToProgram(buf, cps);
17170     }
17171
17172     if(cps->nps >= 0) { /* [HGM] nps */
17173         if(cps->supportsNPS == FALSE)
17174           cps->nps = -1; // don't use if engine explicitly says not supported!
17175         else {
17176           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17177           SendToProgram(buf, cps);
17178         }
17179     }
17180 }
17181
17182 ChessProgramState *
17183 WhitePlayer ()
17184 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17185 {
17186     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17187        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17188         return &second;
17189     return &first;
17190 }
17191
17192 void
17193 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17194 {
17195     char message[MSG_SIZ];
17196     long time, otime;
17197
17198     /* Note: this routine must be called when the clocks are stopped
17199        or when they have *just* been set or switched; otherwise
17200        it will be off by the time since the current tick started.
17201     */
17202     if (machineWhite) {
17203         time = whiteTimeRemaining / 10;
17204         otime = blackTimeRemaining / 10;
17205     } else {
17206         time = blackTimeRemaining / 10;
17207         otime = whiteTimeRemaining / 10;
17208     }
17209     /* [HGM] translate opponent's time by time-odds factor */
17210     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17211
17212     if (time <= 0) time = 1;
17213     if (otime <= 0) otime = 1;
17214
17215     snprintf(message, MSG_SIZ, "time %ld\n", time);
17216     SendToProgram(message, cps);
17217
17218     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17219     SendToProgram(message, cps);
17220 }
17221
17222 char *
17223 EngineDefinedVariant (ChessProgramState *cps, int n)
17224 {   // return name of n-th unknown variant that engine supports
17225     static char buf[MSG_SIZ];
17226     char *p, *s = cps->variants;
17227     if(!s) return NULL;
17228     do { // parse string from variants feature
17229       VariantClass v;
17230         p = strchr(s, ',');
17231         if(p) *p = NULLCHAR;
17232       v = StringToVariant(s);
17233       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17234         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17235             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17236                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17237                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17238                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17239             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17240         }
17241         if(p) *p++ = ',';
17242         if(n < 0) return buf;
17243     } while(s = p);
17244     return NULL;
17245 }
17246
17247 int
17248 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17249 {
17250   char buf[MSG_SIZ];
17251   int len = strlen(name);
17252   int val;
17253
17254   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17255     (*p) += len + 1;
17256     sscanf(*p, "%d", &val);
17257     *loc = (val != 0);
17258     while (**p && **p != ' ')
17259       (*p)++;
17260     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17261     SendToProgram(buf, cps);
17262     return TRUE;
17263   }
17264   return FALSE;
17265 }
17266
17267 int
17268 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17269 {
17270   char buf[MSG_SIZ];
17271   int len = strlen(name);
17272   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17273     (*p) += len + 1;
17274     sscanf(*p, "%d", loc);
17275     while (**p && **p != ' ') (*p)++;
17276     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17277     SendToProgram(buf, cps);
17278     return TRUE;
17279   }
17280   return FALSE;
17281 }
17282
17283 int
17284 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17285 {
17286   char buf[MSG_SIZ];
17287   int len = strlen(name);
17288   if (strncmp((*p), name, len) == 0
17289       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17290     (*p) += len + 2;
17291     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
17292     FREE(*loc); *loc = malloc(len);
17293     strncpy(*loc, *p, len);
17294     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17295     while (**p && **p != '\"') (*p)++;
17296     if (**p == '\"') (*p)++;
17297     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17298     SendToProgram(buf, cps);
17299     return TRUE;
17300   }
17301   return FALSE;
17302 }
17303
17304 int
17305 ParseOption (Option *opt, ChessProgramState *cps)
17306 // [HGM] options: process the string that defines an engine option, and determine
17307 // name, type, default value, and allowed value range
17308 {
17309         char *p, *q, buf[MSG_SIZ];
17310         int n, min = (-1)<<31, max = 1<<31, def;
17311
17312         opt->target = &opt->value;   // OK for spin/slider and checkbox
17313         if(p = strstr(opt->name, " -spin ")) {
17314             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17315             if(max < min) max = min; // enforce consistency
17316             if(def < min) def = min;
17317             if(def > max) def = max;
17318             opt->value = def;
17319             opt->min = min;
17320             opt->max = max;
17321             opt->type = Spin;
17322         } else if((p = strstr(opt->name, " -slider "))) {
17323             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17324             if((n = sscanf(p, " -slider %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; // Slider;
17332         } else if((p = strstr(opt->name, " -string "))) {
17333             opt->textValue = p+9;
17334             opt->type = TextBox;
17335             opt->target = &opt->textValue;
17336         } else if((p = strstr(opt->name, " -file "))) {
17337             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17338             opt->target = opt->textValue = p+7;
17339             opt->type = FileName; // FileName;
17340             opt->target = &opt->textValue;
17341         } else if((p = strstr(opt->name, " -path "))) {
17342             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17343             opt->target = opt->textValue = p+7;
17344             opt->type = PathName; // PathName;
17345             opt->target = &opt->textValue;
17346         } else if(p = strstr(opt->name, " -check ")) {
17347             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17348             opt->value = (def != 0);
17349             opt->type = CheckBox;
17350         } else if(p = strstr(opt->name, " -combo ")) {
17351             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17352             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17353             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17354             opt->value = n = 0;
17355             while(q = StrStr(q, " /// ")) {
17356                 n++; *q = 0;    // count choices, and null-terminate each of them
17357                 q += 5;
17358                 if(*q == '*') { // remember default, which is marked with * prefix
17359                     q++;
17360                     opt->value = n;
17361                 }
17362                 cps->comboList[cps->comboCnt++] = q;
17363             }
17364             cps->comboList[cps->comboCnt++] = NULL;
17365             opt->max = n + 1;
17366             opt->type = ComboBox;
17367         } else if(p = strstr(opt->name, " -button")) {
17368             opt->type = Button;
17369         } else if(p = strstr(opt->name, " -save")) {
17370             opt->type = SaveButton;
17371         } else return FALSE;
17372         *p = 0; // terminate option name
17373         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17374         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17375         // now look if the command-line options define a setting for this engine option.
17376         if(cps->optionSettings && cps->optionSettings[0])
17377             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17378         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17379           snprintf(buf, MSG_SIZ, "option %s", p);
17380                 if(p = strstr(buf, ",")) *p = 0;
17381                 if(q = strchr(buf, '=')) switch(opt->type) {
17382                     case ComboBox:
17383                         for(n=0; n<opt->max; n++)
17384                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17385                         break;
17386                     case TextBox:
17387                     case FileName:
17388                     case PathName:
17389                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17390                         break;
17391                     case Spin:
17392                     case CheckBox:
17393                         opt->value = atoi(q+1);
17394                     default:
17395                         break;
17396                 }
17397                 strcat(buf, "\n");
17398                 SendToProgram(buf, cps);
17399         }
17400         return TRUE;
17401 }
17402
17403 void
17404 FeatureDone (ChessProgramState *cps, int val)
17405 {
17406   DelayedEventCallback cb = GetDelayedEvent();
17407   if ((cb == InitBackEnd3 && cps == &first) ||
17408       (cb == SettingsMenuIfReady && cps == &second) ||
17409       (cb == LoadEngine) || (cb == StartSecond) ||
17410       (cb == TwoMachinesEventIfReady)) {
17411     CancelDelayedEvent();
17412     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17413   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17414   cps->initDone = val;
17415   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17416 }
17417
17418 /* Parse feature command from engine */
17419 void
17420 ParseFeatures (char *args, ChessProgramState *cps)
17421 {
17422   char *p = args;
17423   char *q = NULL;
17424   int val;
17425   char buf[MSG_SIZ];
17426
17427   for (;;) {
17428     while (*p == ' ') p++;
17429     if (*p == NULLCHAR) return;
17430
17431     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17432     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17433     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17434     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17435     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17436     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17437     if (BoolFeature(&p, "reuse", &val, cps)) {
17438       /* Engine can disable reuse, but can't enable it if user said no */
17439       if (!val) cps->reuse = FALSE;
17440       continue;
17441     }
17442     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17443     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17444       if (gameMode == TwoMachinesPlay) {
17445         DisplayTwoMachinesTitle();
17446       } else {
17447         DisplayTitle("");
17448       }
17449       continue;
17450     }
17451     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17452     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17453     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17454     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17455     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17456     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17457     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17458     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17459     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17460     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17461     if (IntFeature(&p, "done", &val, cps)) {
17462       FeatureDone(cps, val);
17463       continue;
17464     }
17465     /* Added by Tord: */
17466     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17467     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17468     /* End of additions by Tord */
17469
17470     /* [HGM] added features: */
17471     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17472     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17473     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17474     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17475     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17476     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17477     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17478     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17479         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17480         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17481         FREE(cps->option[cps->nrOptions].name);
17482         cps->option[cps->nrOptions].name = q; q = NULL;
17483         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17484           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17485             SendToProgram(buf, cps);
17486             continue;
17487         }
17488         if(cps->nrOptions >= MAX_OPTIONS) {
17489             cps->nrOptions--;
17490             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17491             DisplayError(buf, 0);
17492         }
17493         continue;
17494     }
17495     /* End of additions by HGM */
17496
17497     /* unknown feature: complain and skip */
17498     q = p;
17499     while (*q && *q != '=') q++;
17500     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17501     SendToProgram(buf, cps);
17502     p = q;
17503     if (*p == '=') {
17504       p++;
17505       if (*p == '\"') {
17506         p++;
17507         while (*p && *p != '\"') p++;
17508         if (*p == '\"') p++;
17509       } else {
17510         while (*p && *p != ' ') p++;
17511       }
17512     }
17513   }
17514
17515 }
17516
17517 void
17518 PeriodicUpdatesEvent (int newState)
17519 {
17520     if (newState == appData.periodicUpdates)
17521       return;
17522
17523     appData.periodicUpdates=newState;
17524
17525     /* Display type changes, so update it now */
17526 //    DisplayAnalysis();
17527
17528     /* Get the ball rolling again... */
17529     if (newState) {
17530         AnalysisPeriodicEvent(1);
17531         StartAnalysisClock();
17532     }
17533 }
17534
17535 void
17536 PonderNextMoveEvent (int newState)
17537 {
17538     if (newState == appData.ponderNextMove) return;
17539     if (gameMode == EditPosition) EditPositionDone(TRUE);
17540     if (newState) {
17541         SendToProgram("hard\n", &first);
17542         if (gameMode == TwoMachinesPlay) {
17543             SendToProgram("hard\n", &second);
17544         }
17545     } else {
17546         SendToProgram("easy\n", &first);
17547         thinkOutput[0] = NULLCHAR;
17548         if (gameMode == TwoMachinesPlay) {
17549             SendToProgram("easy\n", &second);
17550         }
17551     }
17552     appData.ponderNextMove = newState;
17553 }
17554
17555 void
17556 NewSettingEvent (int option, int *feature, char *command, int value)
17557 {
17558     char buf[MSG_SIZ];
17559
17560     if (gameMode == EditPosition) EditPositionDone(TRUE);
17561     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17562     if(feature == NULL || *feature) SendToProgram(buf, &first);
17563     if (gameMode == TwoMachinesPlay) {
17564         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17565     }
17566 }
17567
17568 void
17569 ShowThinkingEvent ()
17570 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17571 {
17572     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17573     int newState = appData.showThinking
17574         // [HGM] thinking: other features now need thinking output as well
17575         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17576
17577     if (oldState == newState) return;
17578     oldState = newState;
17579     if (gameMode == EditPosition) EditPositionDone(TRUE);
17580     if (oldState) {
17581         SendToProgram("post\n", &first);
17582         if (gameMode == TwoMachinesPlay) {
17583             SendToProgram("post\n", &second);
17584         }
17585     } else {
17586         SendToProgram("nopost\n", &first);
17587         thinkOutput[0] = NULLCHAR;
17588         if (gameMode == TwoMachinesPlay) {
17589             SendToProgram("nopost\n", &second);
17590         }
17591     }
17592 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17593 }
17594
17595 void
17596 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17597 {
17598   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17599   if (pr == NoProc) return;
17600   AskQuestion(title, question, replyPrefix, pr);
17601 }
17602
17603 void
17604 TypeInEvent (char firstChar)
17605 {
17606     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17607         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17608         gameMode == AnalyzeMode || gameMode == EditGame ||
17609         gameMode == EditPosition || gameMode == IcsExamining ||
17610         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17611         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17612                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17613                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17614         gameMode == Training) PopUpMoveDialog(firstChar);
17615 }
17616
17617 void
17618 TypeInDoneEvent (char *move)
17619 {
17620         Board board;
17621         int n, fromX, fromY, toX, toY;
17622         char promoChar;
17623         ChessMove moveType;
17624
17625         // [HGM] FENedit
17626         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17627                 EditPositionPasteFEN(move);
17628                 return;
17629         }
17630         // [HGM] movenum: allow move number to be typed in any mode
17631         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17632           ToNrEvent(2*n-1);
17633           return;
17634         }
17635         // undocumented kludge: allow command-line option to be typed in!
17636         // (potentially fatal, and does not implement the effect of the option.)
17637         // should only be used for options that are values on which future decisions will be made,
17638         // and definitely not on options that would be used during initialization.
17639         if(strstr(move, "!!! -") == move) {
17640             ParseArgsFromString(move+4);
17641             return;
17642         }
17643
17644       if (gameMode != EditGame && currentMove != forwardMostMove &&
17645         gameMode != Training) {
17646         DisplayMoveError(_("Displayed move is not current"));
17647       } else {
17648         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17649           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17650         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17651         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17652           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17653           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17654         } else {
17655           DisplayMoveError(_("Could not parse move"));
17656         }
17657       }
17658 }
17659
17660 void
17661 DisplayMove (int moveNumber)
17662 {
17663     char message[MSG_SIZ];
17664     char res[MSG_SIZ];
17665     char cpThinkOutput[MSG_SIZ];
17666
17667     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17668
17669     if (moveNumber == forwardMostMove - 1 ||
17670         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17671
17672         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17673
17674         if (strchr(cpThinkOutput, '\n')) {
17675             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17676         }
17677     } else {
17678         *cpThinkOutput = NULLCHAR;
17679     }
17680
17681     /* [AS] Hide thinking from human user */
17682     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17683         *cpThinkOutput = NULLCHAR;
17684         if( thinkOutput[0] != NULLCHAR ) {
17685             int i;
17686
17687             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17688                 cpThinkOutput[i] = '.';
17689             }
17690             cpThinkOutput[i] = NULLCHAR;
17691             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17692         }
17693     }
17694
17695     if (moveNumber == forwardMostMove - 1 &&
17696         gameInfo.resultDetails != NULL) {
17697         if (gameInfo.resultDetails[0] == NULLCHAR) {
17698           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17699         } else {
17700           snprintf(res, MSG_SIZ, " {%s} %s",
17701                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17702         }
17703     } else {
17704         res[0] = NULLCHAR;
17705     }
17706
17707     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17708         DisplayMessage(res, cpThinkOutput);
17709     } else {
17710       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17711                 WhiteOnMove(moveNumber) ? " " : ".. ",
17712                 parseList[moveNumber], res);
17713         DisplayMessage(message, cpThinkOutput);
17714     }
17715 }
17716
17717 void
17718 DisplayComment (int moveNumber, char *text)
17719 {
17720     char title[MSG_SIZ];
17721
17722     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17723       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17724     } else {
17725       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17726               WhiteOnMove(moveNumber) ? " " : ".. ",
17727               parseList[moveNumber]);
17728     }
17729     if (text != NULL && (appData.autoDisplayComment || commentUp))
17730         CommentPopUp(title, text);
17731 }
17732
17733 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17734  * might be busy thinking or pondering.  It can be omitted if your
17735  * gnuchess is configured to stop thinking immediately on any user
17736  * input.  However, that gnuchess feature depends on the FIONREAD
17737  * ioctl, which does not work properly on some flavors of Unix.
17738  */
17739 void
17740 Attention (ChessProgramState *cps)
17741 {
17742 #if ATTENTION
17743     if (!cps->useSigint) return;
17744     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17745     switch (gameMode) {
17746       case MachinePlaysWhite:
17747       case MachinePlaysBlack:
17748       case TwoMachinesPlay:
17749       case IcsPlayingWhite:
17750       case IcsPlayingBlack:
17751       case AnalyzeMode:
17752       case AnalyzeFile:
17753         /* Skip if we know it isn't thinking */
17754         if (!cps->maybeThinking) return;
17755         if (appData.debugMode)
17756           fprintf(debugFP, "Interrupting %s\n", cps->which);
17757         InterruptChildProcess(cps->pr);
17758         cps->maybeThinking = FALSE;
17759         break;
17760       default:
17761         break;
17762     }
17763 #endif /*ATTENTION*/
17764 }
17765
17766 int
17767 CheckFlags ()
17768 {
17769     if (whiteTimeRemaining <= 0) {
17770         if (!whiteFlag) {
17771             whiteFlag = TRUE;
17772             if (appData.icsActive) {
17773                 if (appData.autoCallFlag &&
17774                     gameMode == IcsPlayingBlack && !blackFlag) {
17775                   SendToICS(ics_prefix);
17776                   SendToICS("flag\n");
17777                 }
17778             } else {
17779                 if (blackFlag) {
17780                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17781                 } else {
17782                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17783                     if (appData.autoCallFlag) {
17784                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17785                         return TRUE;
17786                     }
17787                 }
17788             }
17789         }
17790     }
17791     if (blackTimeRemaining <= 0) {
17792         if (!blackFlag) {
17793             blackFlag = TRUE;
17794             if (appData.icsActive) {
17795                 if (appData.autoCallFlag &&
17796                     gameMode == IcsPlayingWhite && !whiteFlag) {
17797                   SendToICS(ics_prefix);
17798                   SendToICS("flag\n");
17799                 }
17800             } else {
17801                 if (whiteFlag) {
17802                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17803                 } else {
17804                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17805                     if (appData.autoCallFlag) {
17806                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17807                         return TRUE;
17808                     }
17809                 }
17810             }
17811         }
17812     }
17813     return FALSE;
17814 }
17815
17816 void
17817 CheckTimeControl ()
17818 {
17819     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17820         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17821
17822     /*
17823      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17824      */
17825     if ( !WhiteOnMove(forwardMostMove) ) {
17826         /* White made time control */
17827         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17828         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17829         /* [HGM] time odds: correct new time quota for time odds! */
17830                                             / WhitePlayer()->timeOdds;
17831         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17832     } else {
17833         lastBlack -= blackTimeRemaining;
17834         /* Black made time control */
17835         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17836                                             / WhitePlayer()->other->timeOdds;
17837         lastWhite = whiteTimeRemaining;
17838     }
17839 }
17840
17841 void
17842 DisplayBothClocks ()
17843 {
17844     int wom = gameMode == EditPosition ?
17845       !blackPlaysFirst : WhiteOnMove(currentMove);
17846     DisplayWhiteClock(whiteTimeRemaining, wom);
17847     DisplayBlackClock(blackTimeRemaining, !wom);
17848 }
17849
17850
17851 /* Timekeeping seems to be a portability nightmare.  I think everyone
17852    has ftime(), but I'm really not sure, so I'm including some ifdefs
17853    to use other calls if you don't.  Clocks will be less accurate if
17854    you have neither ftime nor gettimeofday.
17855 */
17856
17857 /* VS 2008 requires the #include outside of the function */
17858 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17859 #include <sys/timeb.h>
17860 #endif
17861
17862 /* Get the current time as a TimeMark */
17863 void
17864 GetTimeMark (TimeMark *tm)
17865 {
17866 #if HAVE_GETTIMEOFDAY
17867
17868     struct timeval timeVal;
17869     struct timezone timeZone;
17870
17871     gettimeofday(&timeVal, &timeZone);
17872     tm->sec = (long) timeVal.tv_sec;
17873     tm->ms = (int) (timeVal.tv_usec / 1000L);
17874
17875 #else /*!HAVE_GETTIMEOFDAY*/
17876 #if HAVE_FTIME
17877
17878 // include <sys/timeb.h> / moved to just above start of function
17879     struct timeb timeB;
17880
17881     ftime(&timeB);
17882     tm->sec = (long) timeB.time;
17883     tm->ms = (int) timeB.millitm;
17884
17885 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17886     tm->sec = (long) time(NULL);
17887     tm->ms = 0;
17888 #endif
17889 #endif
17890 }
17891
17892 /* Return the difference in milliseconds between two
17893    time marks.  We assume the difference will fit in a long!
17894 */
17895 long
17896 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17897 {
17898     return 1000L*(tm2->sec - tm1->sec) +
17899            (long) (tm2->ms - tm1->ms);
17900 }
17901
17902
17903 /*
17904  * Code to manage the game clocks.
17905  *
17906  * In tournament play, black starts the clock and then white makes a move.
17907  * We give the human user a slight advantage if he is playing white---the
17908  * clocks don't run until he makes his first move, so it takes zero time.
17909  * Also, we don't account for network lag, so we could get out of sync
17910  * with GNU Chess's clock -- but then, referees are always right.
17911  */
17912
17913 static TimeMark tickStartTM;
17914 static long intendedTickLength;
17915
17916 long
17917 NextTickLength (long timeRemaining)
17918 {
17919     long nominalTickLength, nextTickLength;
17920
17921     if (timeRemaining > 0L && timeRemaining <= 10000L)
17922       nominalTickLength = 100L;
17923     else
17924       nominalTickLength = 1000L;
17925     nextTickLength = timeRemaining % nominalTickLength;
17926     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17927
17928     return nextTickLength;
17929 }
17930
17931 /* Adjust clock one minute up or down */
17932 void
17933 AdjustClock (Boolean which, int dir)
17934 {
17935     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17936     if(which) blackTimeRemaining += 60000*dir;
17937     else      whiteTimeRemaining += 60000*dir;
17938     DisplayBothClocks();
17939     adjustedClock = TRUE;
17940 }
17941
17942 /* Stop clocks and reset to a fresh time control */
17943 void
17944 ResetClocks ()
17945 {
17946     (void) StopClockTimer();
17947     if (appData.icsActive) {
17948         whiteTimeRemaining = blackTimeRemaining = 0;
17949     } else if (searchTime) {
17950         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17951         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17952     } else { /* [HGM] correct new time quote for time odds */
17953         whiteTC = blackTC = fullTimeControlString;
17954         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17955         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17956     }
17957     if (whiteFlag || blackFlag) {
17958         DisplayTitle("");
17959         whiteFlag = blackFlag = FALSE;
17960     }
17961     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17962     DisplayBothClocks();
17963     adjustedClock = FALSE;
17964 }
17965
17966 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17967
17968 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17969
17970 /* Decrement running clock by amount of time that has passed */
17971 void
17972 DecrementClocks ()
17973 {
17974     long tRemaining;
17975     long lastTickLength, fudge;
17976     TimeMark now;
17977
17978     if (!appData.clockMode) return;
17979     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17980
17981     GetTimeMark(&now);
17982
17983     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17984
17985     /* Fudge if we woke up a little too soon */
17986     fudge = intendedTickLength - lastTickLength;
17987     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17988
17989     if (WhiteOnMove(forwardMostMove)) {
17990         if(whiteNPS >= 0) lastTickLength = 0;
17991          tRemaining = whiteTimeRemaining -= lastTickLength;
17992         if( tRemaining < 0 && !appData.icsActive) {
17993             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17994             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17995                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17996                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17997             }
17998         }
17999         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
18000         DisplayWhiteClock(whiteTimeRemaining - fudge,
18001                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18002         timeSuffix = 0;
18003     } else {
18004         if(blackNPS >= 0) lastTickLength = 0;
18005          tRemaining = blackTimeRemaining -= lastTickLength;
18006         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
18007             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
18008             if(suddenDeath) {
18009                 blackStartMove = forwardMostMove;
18010                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
18011             }
18012         }
18013         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
18014         DisplayBlackClock(blackTimeRemaining - fudge,
18015                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18016         timeSuffix = 0;
18017     }
18018     if (CheckFlags()) return;
18019
18020     if(twoBoards) { // count down secondary board's clocks as well
18021         activePartnerTime -= lastTickLength;
18022         partnerUp = 1;
18023         if(activePartner == 'W')
18024             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
18025         else
18026             DisplayBlackClock(activePartnerTime, TRUE);
18027         partnerUp = 0;
18028     }
18029
18030     tickStartTM = now;
18031     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
18032     StartClockTimer(intendedTickLength);
18033
18034     /* if the time remaining has fallen below the alarm threshold, sound the
18035      * alarm. if the alarm has sounded and (due to a takeback or time control
18036      * with increment) the time remaining has increased to a level above the
18037      * threshold, reset the alarm so it can sound again.
18038      */
18039
18040     if (appData.icsActive && appData.icsAlarm) {
18041
18042         /* make sure we are dealing with the user's clock */
18043         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
18044                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
18045            )) return;
18046
18047         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
18048             alarmSounded = FALSE;
18049         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
18050             PlayAlarmSound();
18051             alarmSounded = TRUE;
18052         }
18053     }
18054 }
18055
18056
18057 /* A player has just moved, so stop the previously running
18058    clock and (if in clock mode) start the other one.
18059    We redisplay both clocks in case we're in ICS mode, because
18060    ICS gives us an update to both clocks after every move.
18061    Note that this routine is called *after* forwardMostMove
18062    is updated, so the last fractional tick must be subtracted
18063    from the color that is *not* on move now.
18064 */
18065 void
18066 SwitchClocks (int newMoveNr)
18067 {
18068     long lastTickLength;
18069     TimeMark now;
18070     int flagged = FALSE;
18071
18072     GetTimeMark(&now);
18073
18074     if (StopClockTimer() && appData.clockMode) {
18075         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18076         if (!WhiteOnMove(forwardMostMove)) {
18077             if(blackNPS >= 0) lastTickLength = 0;
18078             blackTimeRemaining -= lastTickLength;
18079            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18080 //         if(pvInfoList[forwardMostMove].time == -1)
18081                  pvInfoList[forwardMostMove].time =               // use GUI time
18082                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18083         } else {
18084            if(whiteNPS >= 0) lastTickLength = 0;
18085            whiteTimeRemaining -= lastTickLength;
18086            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18087 //         if(pvInfoList[forwardMostMove].time == -1)
18088                  pvInfoList[forwardMostMove].time =
18089                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18090         }
18091         flagged = CheckFlags();
18092     }
18093     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18094     CheckTimeControl();
18095
18096     if (flagged || !appData.clockMode) return;
18097
18098     switch (gameMode) {
18099       case MachinePlaysBlack:
18100       case MachinePlaysWhite:
18101       case BeginningOfGame:
18102         if (pausing) return;
18103         break;
18104
18105       case EditGame:
18106       case PlayFromGameFile:
18107       case IcsExamining:
18108         return;
18109
18110       default:
18111         break;
18112     }
18113
18114     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18115         if(WhiteOnMove(forwardMostMove))
18116              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18117         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18118     }
18119
18120     tickStartTM = now;
18121     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18122       whiteTimeRemaining : blackTimeRemaining);
18123     StartClockTimer(intendedTickLength);
18124 }
18125
18126
18127 /* Stop both clocks */
18128 void
18129 StopClocks ()
18130 {
18131     long lastTickLength;
18132     TimeMark now;
18133
18134     if (!StopClockTimer()) return;
18135     if (!appData.clockMode) return;
18136
18137     GetTimeMark(&now);
18138
18139     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18140     if (WhiteOnMove(forwardMostMove)) {
18141         if(whiteNPS >= 0) lastTickLength = 0;
18142         whiteTimeRemaining -= lastTickLength;
18143         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18144     } else {
18145         if(blackNPS >= 0) lastTickLength = 0;
18146         blackTimeRemaining -= lastTickLength;
18147         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18148     }
18149     CheckFlags();
18150 }
18151
18152 /* Start clock of player on move.  Time may have been reset, so
18153    if clock is already running, stop and restart it. */
18154 void
18155 StartClocks ()
18156 {
18157     (void) StopClockTimer(); /* in case it was running already */
18158     DisplayBothClocks();
18159     if (CheckFlags()) return;
18160
18161     if (!appData.clockMode) return;
18162     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18163
18164     GetTimeMark(&tickStartTM);
18165     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18166       whiteTimeRemaining : blackTimeRemaining);
18167
18168    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18169     whiteNPS = blackNPS = -1;
18170     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18171        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18172         whiteNPS = first.nps;
18173     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18174        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18175         blackNPS = first.nps;
18176     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18177         whiteNPS = second.nps;
18178     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18179         blackNPS = second.nps;
18180     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18181
18182     StartClockTimer(intendedTickLength);
18183 }
18184
18185 char *
18186 TimeString (long ms)
18187 {
18188     long second, minute, hour, day;
18189     char *sign = "";
18190     static char buf[40], moveTime[8];
18191
18192     if (ms > 0 && ms <= 9900) {
18193       /* convert milliseconds to tenths, rounding up */
18194       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18195
18196       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18197       return buf;
18198     }
18199
18200     /* convert milliseconds to seconds, rounding up */
18201     /* use floating point to avoid strangeness of integer division
18202        with negative dividends on many machines */
18203     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18204
18205     if (second < 0) {
18206         sign = "-";
18207         second = -second;
18208     }
18209
18210     day = second / (60 * 60 * 24);
18211     second = second % (60 * 60 * 24);
18212     hour = second / (60 * 60);
18213     second = second % (60 * 60);
18214     minute = second / 60;
18215     second = second % 60;
18216
18217     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18218     else *moveTime = NULLCHAR;
18219
18220     if (day > 0)
18221       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18222               sign, day, hour, minute, second, moveTime);
18223     else if (hour > 0)
18224       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18225     else
18226       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18227
18228     return buf;
18229 }
18230
18231
18232 /*
18233  * This is necessary because some C libraries aren't ANSI C compliant yet.
18234  */
18235 char *
18236 StrStr (char *string, char *match)
18237 {
18238     int i, length;
18239
18240     length = strlen(match);
18241
18242     for (i = strlen(string) - length; i >= 0; i--, string++)
18243       if (!strncmp(match, string, length))
18244         return string;
18245
18246     return NULL;
18247 }
18248
18249 char *
18250 StrCaseStr (char *string, char *match)
18251 {
18252     int i, j, length;
18253
18254     length = strlen(match);
18255
18256     for (i = strlen(string) - length; i >= 0; i--, string++) {
18257         for (j = 0; j < length; j++) {
18258             if (ToLower(match[j]) != ToLower(string[j]))
18259               break;
18260         }
18261         if (j == length) return string;
18262     }
18263
18264     return NULL;
18265 }
18266
18267 #ifndef _amigados
18268 int
18269 StrCaseCmp (char *s1, char *s2)
18270 {
18271     char c1, c2;
18272
18273     for (;;) {
18274         c1 = ToLower(*s1++);
18275         c2 = ToLower(*s2++);
18276         if (c1 > c2) return 1;
18277         if (c1 < c2) return -1;
18278         if (c1 == NULLCHAR) return 0;
18279     }
18280 }
18281
18282
18283 int
18284 ToLower (int c)
18285 {
18286     return isupper(c) ? tolower(c) : c;
18287 }
18288
18289
18290 int
18291 ToUpper (int c)
18292 {
18293     return islower(c) ? toupper(c) : c;
18294 }
18295 #endif /* !_amigados    */
18296
18297 char *
18298 StrSave (char *s)
18299 {
18300   char *ret;
18301
18302   if ((ret = (char *) malloc(strlen(s) + 1)))
18303     {
18304       safeStrCpy(ret, s, strlen(s)+1);
18305     }
18306   return ret;
18307 }
18308
18309 char *
18310 StrSavePtr (char *s, char **savePtr)
18311 {
18312     if (*savePtr) {
18313         free(*savePtr);
18314     }
18315     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18316       safeStrCpy(*savePtr, s, strlen(s)+1);
18317     }
18318     return(*savePtr);
18319 }
18320
18321 char *
18322 PGNDate ()
18323 {
18324     time_t clock;
18325     struct tm *tm;
18326     char buf[MSG_SIZ];
18327
18328     clock = time((time_t *)NULL);
18329     tm = localtime(&clock);
18330     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18331             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18332     return StrSave(buf);
18333 }
18334
18335
18336 char *
18337 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18338 {
18339     int i, j, fromX, fromY, toX, toY;
18340     int whiteToPlay, haveRights = nrCastlingRights;
18341     char buf[MSG_SIZ];
18342     char *p, *q;
18343     int emptycount;
18344     ChessSquare piece;
18345
18346     whiteToPlay = (gameMode == EditPosition) ?
18347       !blackPlaysFirst : (move % 2 == 0);
18348     p = buf;
18349
18350     /* Piece placement data */
18351     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18352         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18353         emptycount = 0;
18354         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18355             if (boards[move][i][j] == EmptySquare) {
18356                 emptycount++;
18357             } else { ChessSquare piece = boards[move][i][j];
18358                 if (emptycount > 0) {
18359                     if(emptycount<10) /* [HGM] can be >= 10 */
18360                         *p++ = '0' + emptycount;
18361                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18362                     emptycount = 0;
18363                 }
18364                 if(PieceToChar(piece) == '+') {
18365                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18366                     *p++ = '+';
18367                     piece = (ChessSquare)(CHUDEMOTED(piece));
18368                 }
18369                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18370                 if(*p = PieceSuffix(piece)) p++;
18371                 if(p[-1] == '~') {
18372                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18373                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18374                     *p++ = '~';
18375                 }
18376             }
18377         }
18378         if (emptycount > 0) {
18379             if(emptycount<10) /* [HGM] can be >= 10 */
18380                 *p++ = '0' + emptycount;
18381             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18382             emptycount = 0;
18383         }
18384         *p++ = '/';
18385     }
18386     *(p - 1) = ' ';
18387
18388     /* [HGM] print Crazyhouse or Shogi holdings */
18389     if( gameInfo.holdingsWidth ) {
18390         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18391         q = p;
18392         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18393             piece = boards[move][i][BOARD_WIDTH-1];
18394             if( piece != EmptySquare )
18395               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18396                   *p++ = PieceToChar(piece);
18397         }
18398         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18399             piece = boards[move][handSize-i-1][0];
18400             if( piece != EmptySquare )
18401               for(j=0; j<(int) boards[move][handSize-i-1][1]; j++)
18402                   *p++ = PieceToChar(piece);
18403         }
18404
18405         if( q == p ) *p++ = '-';
18406         *p++ = ']';
18407         *p++ = ' ';
18408     }
18409
18410     /* Active color */
18411     *p++ = whiteToPlay ? 'w' : 'b';
18412     *p++ = ' ';
18413
18414   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18415     haveRights = 0; q = p;
18416     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18417       piece = boards[move][0][i];
18418       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18419         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18420       }
18421     }
18422     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18423       piece = boards[move][BOARD_HEIGHT-1][i];
18424       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18425         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18426       }
18427     }
18428     if(p == q) *p++ = '-';
18429     *p++ = ' ';
18430   }
18431
18432   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18433     while(*p++ = *q++)
18434                       ;
18435     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18436   } else {
18437   if(haveRights) {
18438      int handW=0, handB=0;
18439      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18440         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18441         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18442      }
18443      q = p;
18444      if(appData.fischerCastling) {
18445         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18446            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18447                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18448         } else {
18449        /* [HGM] write directly from rights */
18450            if(boards[move][CASTLING][2] != NoRights &&
18451               boards[move][CASTLING][0] != NoRights   )
18452                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18453            if(boards[move][CASTLING][2] != NoRights &&
18454               boards[move][CASTLING][1] != NoRights   )
18455                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18456         }
18457         if(handB) {
18458            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18459                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18460         } else {
18461            if(boards[move][CASTLING][5] != NoRights &&
18462               boards[move][CASTLING][3] != NoRights   )
18463                 *p++ = boards[move][CASTLING][3] + AAA;
18464            if(boards[move][CASTLING][5] != NoRights &&
18465               boards[move][CASTLING][4] != NoRights   )
18466                 *p++ = boards[move][CASTLING][4] + AAA;
18467         }
18468      } else {
18469
18470         /* [HGM] write true castling rights */
18471         if( nrCastlingRights == 6 ) {
18472             int q, k=0;
18473             if(boards[move][CASTLING][0] != NoRights &&
18474                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18475             q = (boards[move][CASTLING][1] != NoRights &&
18476                  boards[move][CASTLING][2] != NoRights  );
18477             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18478                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18479                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18480                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18481             }
18482             if(q) *p++ = 'Q';
18483             k = 0;
18484             if(boards[move][CASTLING][3] != NoRights &&
18485                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18486             q = (boards[move][CASTLING][4] != NoRights &&
18487                  boards[move][CASTLING][5] != NoRights  );
18488             if(handB) {
18489                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18490                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18491                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18492             }
18493             if(q) *p++ = 'q';
18494         }
18495      }
18496      if (q == p) *p++ = '-'; /* No castling rights */
18497      *p++ = ' ';
18498   }
18499
18500   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18501      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18502      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18503     /* En passant target square */
18504     if (move > backwardMostMove) {
18505         fromX = moveList[move - 1][0] - AAA;
18506         fromY = moveList[move - 1][1] - ONE;
18507         toX = moveList[move - 1][2] - AAA;
18508         toY = moveList[move - 1][3] - ONE;
18509         if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18510             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18511             /* 2-square pawn move just happened */
18512             *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18513             *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18514             if(gameInfo.variant == VariantBerolina) {
18515                 *p++ = toX + AAA;
18516                 *p++ = toY + ONE;
18517             }
18518         } else {
18519             *p++ = '-';
18520         }
18521     } else if(move == backwardMostMove) {
18522         // [HGM] perhaps we should always do it like this, and forget the above?
18523         if((signed char)boards[move][EP_STATUS] >= 0) {
18524             *p++ = boards[move][EP_STATUS] + AAA;
18525             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18526         } else {
18527             *p++ = '-';
18528         }
18529     } else {
18530         *p++ = '-';
18531     }
18532     *p++ = ' ';
18533   }
18534   }
18535
18536     i = boards[move][CHECK_COUNT];
18537     if(i) {
18538         sprintf(p, "%d+%d ", i&255, i>>8);
18539         while(*p) p++;
18540     }
18541
18542     if(moveCounts)
18543     {   int i = 0, j=move;
18544
18545         /* [HGM] find reversible plies */
18546         if (appData.debugMode) { int k;
18547             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18548             for(k=backwardMostMove; k<=forwardMostMove; k++)
18549                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18550
18551         }
18552
18553         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18554         if( j == backwardMostMove ) i += initialRulePlies;
18555         sprintf(p, "%d ", i);
18556         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18557
18558         /* Fullmove number */
18559         sprintf(p, "%d", (move / 2) + 1);
18560     } else *--p = NULLCHAR;
18561
18562     return StrSave(buf);
18563 }
18564
18565 Boolean
18566 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18567 {
18568     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18569     char *p, c;
18570     int emptycount, virgin[BOARD_FILES];
18571     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18572
18573     p = fen;
18574
18575     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18576
18577     /* Piece placement data */
18578     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18579         j = 0;
18580         for (;;) {
18581             if (*p == '/' || *p == ' ' || *p == '[' ) {
18582                 if(j > w) w = j;
18583                 emptycount = gameInfo.boardWidth - j;
18584                 while (emptycount--)
18585                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18586                 if (*p == '/') p++;
18587                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18588                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18589                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18590                     }
18591                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18592                 }
18593                 break;
18594 #if(BOARD_FILES >= 10)*0
18595             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18596                 p++; emptycount=10;
18597                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18598                 while (emptycount--)
18599                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18600 #endif
18601             } else if (*p == '*') {
18602                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18603             } else if (isdigit(*p)) {
18604                 emptycount = *p++ - '0';
18605                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18606                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18607                 while (emptycount--)
18608                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18609             } else if (*p == '<') {
18610                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18611                 else if (i != 0 || !shuffle) return FALSE;
18612                 p++;
18613             } else if (shuffle && *p == '>') {
18614                 p++; // for now ignore closing shuffle range, and assume rank-end
18615             } else if (*p == '?') {
18616                 if (j >= gameInfo.boardWidth) return FALSE;
18617                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18618                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18619             } else if (*p == '+' || isalpha(*p)) {
18620                 char *q, *s = SUFFIXES;
18621                 if (j >= gameInfo.boardWidth) return FALSE;
18622                 if(*p=='+') {
18623                     char c = *++p;
18624                     if(q = strchr(s, p[1])) p++;
18625                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18626                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18627                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18628                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18629                 } else {
18630                     char c = *p++;
18631                     if(q = strchr(s, *p)) p++;
18632                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18633                 }
18634
18635                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18636                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18637                     piece = (ChessSquare) (PROMOTED(piece));
18638                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18639                     p++;
18640                 }
18641                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18642                 if(piece == king) wKingRank = i;
18643                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18644             } else {
18645                 return FALSE;
18646             }
18647         }
18648     }
18649     while (*p == '/' || *p == ' ') p++;
18650
18651     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18652
18653     /* [HGM] by default clear Crazyhouse holdings, if present */
18654     if(gameInfo.holdingsWidth) {
18655        for(i=0; i<handSize; i++) {
18656            board[i][0]             = EmptySquare; /* black holdings */
18657            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18658            board[i][1]             = (ChessSquare) 0; /* black counts */
18659            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18660        }
18661     }
18662
18663     /* [HGM] look for Crazyhouse holdings here */
18664     while(*p==' ') p++;
18665     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18666         int swap=0, wcnt=0, bcnt=0;
18667         if(*p == '[') p++;
18668         if(*p == '<') swap++, p++;
18669         if(*p == '-' ) p++; /* empty holdings */ else {
18670             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18671             /* if we would allow FEN reading to set board size, we would   */
18672             /* have to add holdings and shift the board read so far here   */
18673             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18674                 p++;
18675                 if((int) piece >= (int) BlackPawn ) {
18676                     i = (int)piece - (int)BlackPawn;
18677                     i = PieceToNumber((ChessSquare)i);
18678                     if( i >= gameInfo.holdingsSize ) return FALSE;
18679                     board[handSize-1-i][0] = piece; /* black holdings */
18680                     board[handSize-1-i][1]++;       /* black counts   */
18681                     bcnt++;
18682                 } else {
18683                     i = (int)piece - (int)WhitePawn;
18684                     i = PieceToNumber((ChessSquare)i);
18685                     if( i >= gameInfo.holdingsSize ) return FALSE;
18686                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18687                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18688                     wcnt++;
18689                 }
18690             }
18691             if(subst) { // substitute back-rank question marks by holdings pieces
18692                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18693                     int k, m, n = bcnt + 1;
18694                     if(board[0][j] == ClearBoard) {
18695                         if(!wcnt) return FALSE;
18696                         n = rand() % wcnt;
18697                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18698                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18699                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18700                             break;
18701                         }
18702                     }
18703                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18704                         if(!bcnt) return FALSE;
18705                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18706                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[handSize-1-k][1]) < 0) {
18707                             board[BOARD_HEIGHT-1][j] = board[handSize-1-k][0]; bcnt--;
18708                             if(--board[handSize-1-k][1] == 0) board[handSize-1-k][0] = EmptySquare;
18709                             break;
18710                         }
18711                     }
18712                 }
18713                 subst = 0;
18714             }
18715         }
18716         if(*p == ']') p++;
18717     }
18718
18719     if(subst) return FALSE; // substitution requested, but no holdings
18720
18721     while(*p == ' ') p++;
18722
18723     /* Active color */
18724     c = *p++;
18725     if(appData.colorNickNames) {
18726       if( c == appData.colorNickNames[0] ) c = 'w'; else
18727       if( c == appData.colorNickNames[1] ) c = 'b';
18728     }
18729     switch (c) {
18730       case 'w':
18731         *blackPlaysFirst = FALSE;
18732         break;
18733       case 'b':
18734         *blackPlaysFirst = TRUE;
18735         break;
18736       default:
18737         return FALSE;
18738     }
18739
18740     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18741     /* return the extra info in global variiables             */
18742
18743     while(*p==' ') p++;
18744
18745     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18746         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18747         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18748     }
18749
18750     /* set defaults in case FEN is incomplete */
18751     board[EP_STATUS] = EP_UNKNOWN;
18752     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18753     for(i=0; i<nrCastlingRights; i++ ) {
18754         board[CASTLING][i] =
18755             appData.fischerCastling ? NoRights : initialRights[i];
18756     }   /* assume possible unless obviously impossible */
18757     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18758     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18759     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18760                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18761     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18762     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18763     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18764                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18765     FENrulePlies = 0;
18766
18767     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18768       char *q = p;
18769       int w=0, b=0;
18770       while(isalpha(*p)) {
18771         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18772         if(islower(*p)) b |= 1 << (*p++ - 'a');
18773       }
18774       if(*p == '-') p++;
18775       if(p != q) {
18776         board[TOUCHED_W] = ~w;
18777         board[TOUCHED_B] = ~b;
18778         while(*p == ' ') p++;
18779       }
18780     } else
18781
18782     if(nrCastlingRights) {
18783       int fischer = 0;
18784       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18785       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18786           /* castling indicator present, so default becomes no castlings */
18787           for(i=0; i<nrCastlingRights; i++ ) {
18788                  board[CASTLING][i] = NoRights;
18789           }
18790       }
18791       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18792              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18793              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18794              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18795         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18796
18797         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18798             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18799             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18800         }
18801         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18802             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18803         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18804                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18805         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18806                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18807         switch(c) {
18808           case'K':
18809               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18810               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18811               board[CASTLING][2] = whiteKingFile;
18812               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18813               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18814               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18815               break;
18816           case'Q':
18817               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18818               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18819               board[CASTLING][2] = whiteKingFile;
18820               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18821               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18822               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18823               break;
18824           case'k':
18825               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18826               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18827               board[CASTLING][5] = blackKingFile;
18828               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18829               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18830               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18831               break;
18832           case'q':
18833               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18834               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18835               board[CASTLING][5] = blackKingFile;
18836               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18837               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18838               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18839           case '-':
18840               break;
18841           default: /* FRC castlings */
18842               if(c >= 'a') { /* black rights */
18843                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18844                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18845                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18846                   if(i == BOARD_RGHT) break;
18847                   board[CASTLING][5] = i;
18848                   c -= AAA;
18849                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18850                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18851                   if(c > i)
18852                       board[CASTLING][3] = c;
18853                   else
18854                       board[CASTLING][4] = c;
18855               } else { /* white rights */
18856                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18857                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18858                     if(board[0][i] == WhiteKing) break;
18859                   if(i == BOARD_RGHT) break;
18860                   board[CASTLING][2] = i;
18861                   c -= AAA - 'a' + 'A';
18862                   if(board[0][c] >= WhiteKing) break;
18863                   if(c > i)
18864                       board[CASTLING][0] = c;
18865                   else
18866                       board[CASTLING][1] = c;
18867               }
18868         }
18869       }
18870       for(i=0; i<nrCastlingRights; i++)
18871         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18872       if(gameInfo.variant == VariantSChess)
18873         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18874       if(fischer && shuffle) appData.fischerCastling = TRUE;
18875     if (appData.debugMode) {
18876         fprintf(debugFP, "FEN castling rights:");
18877         for(i=0; i<nrCastlingRights; i++)
18878         fprintf(debugFP, " %d", board[CASTLING][i]);
18879         fprintf(debugFP, "\n");
18880     }
18881
18882       while(*p==' ') p++;
18883     }
18884
18885     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18886
18887     /* read e.p. field in games that know e.p. capture */
18888     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18889        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18890        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18891       if(*p=='-') {
18892         p++; board[EP_STATUS] = EP_NONE;
18893       } else {
18894          int d, r, c = *p - AAA;
18895
18896          if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18897              p++;
18898              board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18899              if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18900              d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18901              if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18902              board[LAST_TO] = 256*(r + d) + c;
18903              c = *p++ - AAA;
18904              if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18905                  if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18906                  board[LAST_TO] = 256*r + c;
18907                  if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18908              }
18909          }
18910       }
18911     }
18912
18913     while(*p == ' ') p++;
18914
18915     board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18916     if(sscanf(p, "%d+%d", &i, &j) == 2) {
18917         board[CHECK_COUNT] = i + 256*j;
18918         while(*p && *p != ' ') p++;
18919     }
18920
18921     c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18922     if(c > 0) {
18923         FENrulePlies = i; /* 50-move ply counter */
18924         /* (The move number is still ignored)    */
18925         if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18926     }
18927
18928     return TRUE;
18929 }
18930
18931 void
18932 EditPositionPasteFEN (char *fen)
18933 {
18934   if (fen != NULL) {
18935     Board initial_position;
18936
18937     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18938       DisplayError(_("Bad FEN position in clipboard"), 0);
18939       return ;
18940     } else {
18941       int savedBlackPlaysFirst = blackPlaysFirst;
18942       EditPositionEvent();
18943       blackPlaysFirst = savedBlackPlaysFirst;
18944       CopyBoard(boards[0], initial_position);
18945       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18946       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18947       DisplayBothClocks();
18948       DrawPosition(FALSE, boards[currentMove]);
18949     }
18950   }
18951 }
18952
18953 static char cseq[12] = "\\   ";
18954
18955 Boolean
18956 set_cont_sequence (char *new_seq)
18957 {
18958     int len;
18959     Boolean ret;
18960
18961     // handle bad attempts to set the sequence
18962         if (!new_seq)
18963                 return 0; // acceptable error - no debug
18964
18965     len = strlen(new_seq);
18966     ret = (len > 0) && (len < sizeof(cseq));
18967     if (ret)
18968       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18969     else if (appData.debugMode)
18970       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18971     return ret;
18972 }
18973
18974 /*
18975     reformat a source message so words don't cross the width boundary.  internal
18976     newlines are not removed.  returns the wrapped size (no null character unless
18977     included in source message).  If dest is NULL, only calculate the size required
18978     for the dest buffer.  lp argument indicats line position upon entry, and it's
18979     passed back upon exit.
18980 */
18981 int
18982 wrap (char *dest, char *src, int count, int width, int *lp)
18983 {
18984     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18985
18986     cseq_len = strlen(cseq);
18987     old_line = line = *lp;
18988     ansi = len = clen = 0;
18989
18990     for (i=0; i < count; i++)
18991     {
18992         if (src[i] == '\033')
18993             ansi = 1;
18994
18995         // if we hit the width, back up
18996         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18997         {
18998             // store i & len in case the word is too long
18999             old_i = i, old_len = len;
19000
19001             // find the end of the last word
19002             while (i && src[i] != ' ' && src[i] != '\n')
19003             {
19004                 i--;
19005                 len--;
19006             }
19007
19008             // word too long?  restore i & len before splitting it
19009             if ((old_i-i+clen) >= width)
19010             {
19011                 i = old_i;
19012                 len = old_len;
19013             }
19014
19015             // extra space?
19016             if (i && src[i-1] == ' ')
19017                 len--;
19018
19019             if (src[i] != ' ' && src[i] != '\n')
19020             {
19021                 i--;
19022                 if (len)
19023                     len--;
19024             }
19025
19026             // now append the newline and continuation sequence
19027             if (dest)
19028                 dest[len] = '\n';
19029             len++;
19030             if (dest)
19031                 strncpy(dest+len, cseq, cseq_len);
19032             len += cseq_len;
19033             line = cseq_len;
19034             clen = cseq_len;
19035             continue;
19036         }
19037
19038         if (dest)
19039             dest[len] = src[i];
19040         len++;
19041         if (!ansi)
19042             line++;
19043         if (src[i] == '\n')
19044             line = 0;
19045         if (src[i] == 'm')
19046             ansi = 0;
19047     }
19048     if (dest && appData.debugMode)
19049     {
19050         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
19051             count, width, line, len, *lp);
19052         show_bytes(debugFP, src, count);
19053         fprintf(debugFP, "\ndest: ");
19054         show_bytes(debugFP, dest, len);
19055         fprintf(debugFP, "\n");
19056     }
19057     *lp = dest ? line : old_line;
19058
19059     return len;
19060 }
19061
19062 // [HGM] vari: routines for shelving variations
19063 Boolean modeRestore = FALSE;
19064
19065 void
19066 PushInner (int firstMove, int lastMove)
19067 {
19068         int i, j, nrMoves = lastMove - firstMove;
19069
19070         // push current tail of game on stack
19071         savedResult[storedGames] = gameInfo.result;
19072         savedDetails[storedGames] = gameInfo.resultDetails;
19073         gameInfo.resultDetails = NULL;
19074         savedFirst[storedGames] = firstMove;
19075         savedLast [storedGames] = lastMove;
19076         savedFramePtr[storedGames] = framePtr;
19077         framePtr -= nrMoves; // reserve space for the boards
19078         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19079             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19080             for(j=0; j<MOVE_LEN; j++)
19081                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19082             for(j=0; j<2*MOVE_LEN; j++)
19083                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19084             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19085             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19086             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19087             pvInfoList[firstMove+i-1].depth = 0;
19088             commentList[framePtr+i] = commentList[firstMove+i];
19089             commentList[firstMove+i] = NULL;
19090         }
19091
19092         storedGames++;
19093         forwardMostMove = firstMove; // truncate game so we can start variation
19094 }
19095
19096 void
19097 PushTail (int firstMove, int lastMove)
19098 {
19099         if(appData.icsActive) { // only in local mode
19100                 forwardMostMove = currentMove; // mimic old ICS behavior
19101                 return;
19102         }
19103         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19104
19105         PushInner(firstMove, lastMove);
19106         if(storedGames == 1) GreyRevert(FALSE);
19107         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19108 }
19109
19110 void
19111 PopInner (Boolean annotate)
19112 {
19113         int i, j, nrMoves;
19114         char buf[8000], moveBuf[20];
19115
19116         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19117         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19118         nrMoves = savedLast[storedGames] - currentMove;
19119         if(annotate) {
19120                 int cnt = 10;
19121                 if(!WhiteOnMove(currentMove))
19122                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19123                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19124                 for(i=currentMove; i<forwardMostMove; i++) {
19125                         if(WhiteOnMove(i))
19126                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19127                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19128                         strcat(buf, moveBuf);
19129                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19130                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19131                 }
19132                 strcat(buf, ")");
19133         }
19134         for(i=1; i<=nrMoves; i++) { // copy last variation back
19135             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19136             for(j=0; j<MOVE_LEN; j++)
19137                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19138             for(j=0; j<2*MOVE_LEN; j++)
19139                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19140             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19141             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19142             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19143             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19144             commentList[currentMove+i] = commentList[framePtr+i];
19145             commentList[framePtr+i] = NULL;
19146         }
19147         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19148         framePtr = savedFramePtr[storedGames];
19149         gameInfo.result = savedResult[storedGames];
19150         if(gameInfo.resultDetails != NULL) {
19151             free(gameInfo.resultDetails);
19152       }
19153         gameInfo.resultDetails = savedDetails[storedGames];
19154         forwardMostMove = currentMove + nrMoves;
19155 }
19156
19157 Boolean
19158 PopTail (Boolean annotate)
19159 {
19160         if(appData.icsActive) return FALSE; // only in local mode
19161         if(!storedGames) return FALSE; // sanity
19162         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19163
19164         PopInner(annotate);
19165         if(currentMove < forwardMostMove) ForwardEvent(); else
19166         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19167
19168         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19169         return TRUE;
19170 }
19171
19172 void
19173 CleanupTail ()
19174 {       // remove all shelved variations
19175         int i;
19176         for(i=0; i<storedGames; i++) {
19177             if(savedDetails[i])
19178                 free(savedDetails[i]);
19179             savedDetails[i] = NULL;
19180         }
19181         for(i=framePtr; i<MAX_MOVES; i++) {
19182                 if(commentList[i]) free(commentList[i]);
19183                 commentList[i] = NULL;
19184         }
19185         framePtr = MAX_MOVES-1;
19186         storedGames = 0;
19187 }
19188
19189 void
19190 LoadVariation (int index, char *text)
19191 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19192         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19193         int level = 0, move;
19194
19195         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19196         // first find outermost bracketing variation
19197         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19198             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19199                 if(*p == '{') wait = '}'; else
19200                 if(*p == '[') wait = ']'; else
19201                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19202                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19203             }
19204             if(*p == wait) wait = NULLCHAR; // closing ]} found
19205             p++;
19206         }
19207         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19208         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19209         end[1] = NULLCHAR; // clip off comment beyond variation
19210         ToNrEvent(currentMove-1);
19211         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19212         // kludge: use ParsePV() to append variation to game
19213         move = currentMove;
19214         ParsePV(start, TRUE, TRUE);
19215         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19216         ClearPremoveHighlights();
19217         CommentPopDown();
19218         ToNrEvent(currentMove+1);
19219 }
19220
19221 int transparency[2];
19222
19223 void
19224 LoadTheme ()
19225 {
19226 #define BUF_SIZ (2*MSG_SIZ)
19227     char *p, *q, buf[BUF_SIZ];
19228     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19229         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19230         ParseArgsFromString(buf);
19231         ActivateTheme(TRUE); // also redo colors
19232         return;
19233     }
19234     p = nickName;
19235     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19236     {
19237         int len;
19238         q = appData.themeNames;
19239         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19240       if(appData.useBitmaps) {
19241         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19242                 Shorten(appData.liteBackTextureFile));
19243         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19244                 Shorten(appData.darkBackTextureFile),
19245                 appData.liteBackTextureMode,
19246                 appData.darkBackTextureMode );
19247       } else {
19248         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19249       }
19250       if(!appData.useBitmaps || transparency[0]) {
19251         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19252       }
19253       if(!appData.useBitmaps || transparency[1]) {
19254         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19255       }
19256       if(appData.useBorder) {
19257         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19258                 appData.border);
19259       } else {
19260         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19261       }
19262       if(appData.useFont) {
19263         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19264                 appData.renderPiecesWithFont,
19265                 appData.fontToPieceTable,
19266                 Col2Text(9),    // appData.fontBackColorWhite
19267                 Col2Text(10) ); // appData.fontForeColorBlack
19268       } else {
19269         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19270         if(appData.pieceDirectory[0]) {
19271           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19272           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19273             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19274         }
19275         if(!appData.pieceDirectory[0] || !appData.trueColors)
19276           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19277                 Col2Text(0),   // whitePieceColor
19278                 Col2Text(1) ); // blackPieceColor
19279       }
19280       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19281                 Col2Text(4),   // highlightSquareColor
19282                 Col2Text(5) ); // premoveHighlightColor
19283         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19284         if(insert != q) insert[-1] = NULLCHAR;
19285         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19286         if(q)   free(q);
19287     }
19288     ActivateTheme(FALSE);
19289 }