Implement (unconditional) protocol auto-detection
[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, tryNr;
917
918 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
919 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
920 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
921 static char newEngineCommand[MSG_SIZ];
922
923 void
924 FloatToFront(char **list, char *engineLine)
925 {
926     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
927     int i=0;
928     if(appData.recentEngines <= 0) return;
929     TidyProgramName(engineLine, "localhost", tidy+1);
930     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
931     strncpy(buf+1, *list, MSG_SIZ-50);
932     if(p = strstr(buf, tidy)) { // tidy name appears in list
933         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
934         while(*p++ = *++q); // squeeze out
935     }
936     strcat(tidy, buf+1); // put list behind tidy name
937     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
938     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
939     ASSIGN(*list, tidy+1);
940 }
941
942 void
943 AddToEngineList (int i)
944 {
945         int len;
946         char quote, buf[MSG_SIZ];
947         char *q = firstChessProgramNames, *p = newEngineCommand;
948         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
949         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
950         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
951                         quote, p, quote, appData.directory[i],
952                         useNick ? " -fn \"" : "",
953                         useNick ? nickName : "",
954                         useNick ? "\"" : "",
955                         v1 ? " -firstProtocolVersion 1" : "",
956                         hasBook ? "" : " -fNoOwnBookUCI",
957                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
958                         storeVariant ? " -variant " : "",
959                         storeVariant ? VariantName(gameInfo.variant) : "");
960         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
961         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
962         if(insert != q) insert[-1] = NULLCHAR;
963         snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
964         if(q)   free(q);
965         FloatToFront(&appData.recentEngineList, buf);
966         ASSIGN(currentEngine[i], buf);
967 }
968
969 void
970 LoadEngine ()
971 {
972     int i;
973     if(WaitForEngine(savCps, LoadEngine)) return;
974     if(tryNr == 1 && !isUCI) { SendToProgram("uci\n", savCps); tryNr = 2; ScheduleDelayedEvent(LoadEngine, FEATURE_TIMEOUT); return; }
975     if(tryNr) v1 = (tryNr == 2), tryNr = 0, AddToEngineList(0); // deferred to after protocol determination
976     CommonEngineInit(); // recalculate time odds
977     if(gameInfo.variant != StringToVariant(appData.variant)) {
978         // we changed variant when loading the engine; this forces us to reset
979         Reset(TRUE, savCps != &first);
980         oldMode = BeginningOfGame; // to prevent restoring old mode
981     }
982     InitChessProgram(savCps, FALSE);
983     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
984     DisplayMessage("", "");
985     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
986     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
987     ThawUI();
988     SetGNUMode();
989     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
990 }
991
992 void
993 ReplaceEngine (ChessProgramState *cps, int n)
994 {
995     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
996     keepInfo = 1;
997     if(oldMode != BeginningOfGame) EditGameEvent();
998     keepInfo = 0;
999     UnloadEngine(cps);
1000     appData.noChessProgram = FALSE;
1001     appData.clockMode = TRUE;
1002     InitEngine(cps, n);
1003     UpdateLogos(TRUE);
1004     if(n) return; // only startup first engine immediately; second can wait
1005     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
1006     LoadEngine();
1007 }
1008
1009 static char resetOptions[] =
1010         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
1011         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
1012         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
1013         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
1014
1015 void
1016 Load (ChessProgramState *cps, int i)
1017 {
1018     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
1019     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
1020         ASSIGN(currentEngine[i], engineLine);
1021         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
1022         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
1023         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
1024         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
1025         appData.firstProtocolVersion = PROTOVER;
1026         ParseArgsFromString(buf);
1027         SwapEngines(i);
1028         ReplaceEngine(cps, i);
1029         FloatToFront(&appData.recentEngineList, engineLine);
1030         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1031         return;
1032     }
1033     p = engineName;
1034     while(q = strchr(p, SLASH)) p = q+1;
1035     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1036     if(engineDir[0] != NULLCHAR) {
1037         ASSIGN(appData.directory[i], engineDir); p = engineName;
1038     } else if(p != engineName) { // derive directory from engine path, when not given
1039         p[-1] = 0;
1040         ASSIGN(appData.directory[i], engineName);
1041         p[-1] = SLASH;
1042         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1043     } else { ASSIGN(appData.directory[i], "."); }
1044     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1045     if(params[0]) {
1046         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1047         snprintf(command, MSG_SIZ, "%s %s", p, params);
1048         p = command;
1049     }
1050     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1051     ASSIGN(appData.chessProgram[i], p);
1052     appData.isUCI[i] = isUCI;
1053     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1054     appData.hasOwnBookUCI[i] = hasBook;
1055     if(!nickName[0]) useNick = FALSE;
1056     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1057     safeStrCpy(newEngineCommand, p, MSG_SIZ);
1058     tryNr = 1;
1059     ReplaceEngine(cps, i);
1060 }
1061
1062 void
1063 InitTimeControls ()
1064 {
1065     int matched, min, sec;
1066     /*
1067      * Parse timeControl resource
1068      */
1069     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1070                           appData.movesPerSession)) {
1071         char buf[MSG_SIZ];
1072         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1073         DisplayFatalError(buf, 0, 2);
1074     }
1075
1076     /*
1077      * Parse searchTime resource
1078      */
1079     if (*appData.searchTime != NULLCHAR) {
1080         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1081         if (matched == 1) {
1082             searchTime = min * 60;
1083         } else if (matched == 2) {
1084             searchTime = min * 60 + sec;
1085         } else {
1086             char buf[MSG_SIZ];
1087             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1088             DisplayFatalError(buf, 0, 2);
1089         }
1090     }
1091 }
1092
1093 void
1094 InitBackEnd1 ()
1095 {
1096
1097     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1098     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1099
1100     GetTimeMark(&programStartTime);
1101     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1102     appData.seedBase = random() + (random()<<15);
1103     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1104
1105     ClearProgramStats();
1106     programStats.ok_to_send = 1;
1107     programStats.seen_stat = 0;
1108
1109     /*
1110      * Initialize game list
1111      */
1112     ListNew(&gameList);
1113
1114
1115     /*
1116      * Internet chess server status
1117      */
1118     if (appData.icsActive) {
1119         appData.matchMode = FALSE;
1120         appData.matchGames = 0;
1121 #if ZIPPY
1122         appData.noChessProgram = !appData.zippyPlay;
1123 #else
1124         appData.zippyPlay = FALSE;
1125         appData.zippyTalk = FALSE;
1126         appData.noChessProgram = TRUE;
1127 #endif
1128         if (*appData.icsHelper != NULLCHAR) {
1129             appData.useTelnet = TRUE;
1130             appData.telnetProgram = appData.icsHelper;
1131         }
1132     } else {
1133         appData.zippyTalk = appData.zippyPlay = FALSE;
1134     }
1135
1136     /* [AS] Initialize pv info list [HGM] and game state */
1137     {
1138         int i, j;
1139
1140         for( i=0; i<=framePtr; i++ ) {
1141             pvInfoList[i].depth = -1;
1142             boards[i][EP_STATUS] = EP_NONE;
1143             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1144         }
1145     }
1146
1147     InitTimeControls();
1148
1149     /* [AS] Adjudication threshold */
1150     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1151
1152     InitEngine(&first, 0);
1153     InitEngine(&second, 1);
1154     CommonEngineInit();
1155
1156     pairing.which = "pairing"; // pairing engine
1157     pairing.pr = NoProc;
1158     pairing.isr = NULL;
1159     pairing.program = appData.pairingEngine;
1160     pairing.host = "localhost";
1161     pairing.dir = ".";
1162
1163     if (appData.icsActive) {
1164         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1165     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1166         appData.clockMode = FALSE;
1167         first.sendTime = second.sendTime = 0;
1168     }
1169
1170 #if ZIPPY
1171     /* Override some settings from environment variables, for backward
1172        compatibility.  Unfortunately it's not feasible to have the env
1173        vars just set defaults, at least in xboard.  Ugh.
1174     */
1175     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1176       ZippyInit();
1177     }
1178 #endif
1179
1180     if (!appData.icsActive) {
1181       char buf[MSG_SIZ];
1182       int len;
1183
1184       /* Check for variants that are supported only in ICS mode,
1185          or not at all.  Some that are accepted here nevertheless
1186          have bugs; see comments below.
1187       */
1188       VariantClass variant = StringToVariant(appData.variant);
1189       switch (variant) {
1190       case VariantBughouse:     /* need four players and two boards */
1191       case VariantKriegspiel:   /* need to hide pieces and move details */
1192         /* case VariantFischeRandom: (Fabien: moved below) */
1193         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1194         if( (len >= MSG_SIZ) && appData.debugMode )
1195           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1196
1197         DisplayFatalError(buf, 0, 2);
1198         return;
1199
1200       case VariantUnknown:
1201       case VariantLoadable:
1202       case Variant29:
1203       case Variant30:
1204       case Variant31:
1205       case Variant32:
1206       case Variant33:
1207       case Variant34:
1208       case Variant35:
1209       case Variant36:
1210       default:
1211         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1212         if( (len >= MSG_SIZ) && appData.debugMode )
1213           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1214
1215         DisplayFatalError(buf, 0, 2);
1216         return;
1217
1218       case VariantNormal:     /* definitely works! */
1219         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1220           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1221           return;
1222         }
1223       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1224       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1225       case VariantGothic:     /* [HGM] should work */
1226       case VariantCapablanca: /* [HGM] should work */
1227       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1228       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1229       case VariantChu:        /* [HGM] experimental */
1230       case VariantKnightmate: /* [HGM] should work */
1231       case VariantCylinder:   /* [HGM] untested */
1232       case VariantFalcon:     /* [HGM] untested */
1233       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1234                                  offboard interposition not understood */
1235       case VariantWildCastle: /* pieces not automatically shuffled */
1236       case VariantNoCastle:   /* pieces not automatically shuffled */
1237       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1238       case VariantLosers:     /* should work except for win condition,
1239                                  and doesn't know captures are mandatory */
1240       case VariantSuicide:    /* should work except for win condition,
1241                                  and doesn't know captures are mandatory */
1242       case VariantGiveaway:   /* should work except for win condition,
1243                                  and doesn't know captures are mandatory */
1244       case VariantTwoKings:   /* should work */
1245       case VariantAtomic:     /* should work except for win condition */
1246       case Variant3Check:     /* should work except for win condition */
1247       case VariantShatranj:   /* should work except for all win conditions */
1248       case VariantMakruk:     /* should work except for draw countdown */
1249       case VariantASEAN :     /* should work except for draw countdown */
1250       case VariantBerolina:   /* might work if TestLegality is off */
1251       case VariantCapaRandom: /* should work */
1252       case VariantJanus:      /* should work */
1253       case VariantSuper:      /* experimental */
1254       case VariantGreat:      /* experimental, requires legality testing to be off */
1255       case VariantSChess:     /* S-Chess, should work */
1256       case VariantGrand:      /* should work */
1257       case VariantSpartan:    /* should work */
1258       case VariantLion:       /* should work */
1259       case VariantChuChess:   /* should work */
1260         break;
1261       }
1262     }
1263
1264 }
1265
1266 int
1267 NextIntegerFromString (char ** str, long * value)
1268 {
1269     int result = -1;
1270     char * s = *str;
1271
1272     while( *s == ' ' || *s == '\t' ) {
1273         s++;
1274     }
1275
1276     *value = 0;
1277
1278     if( *s >= '0' && *s <= '9' ) {
1279         while( *s >= '0' && *s <= '9' ) {
1280             *value = *value * 10 + (*s - '0');
1281             s++;
1282         }
1283
1284         result = 0;
1285     }
1286
1287     *str = s;
1288
1289     return result;
1290 }
1291
1292 int
1293 NextTimeControlFromString (char ** str, long * value)
1294 {
1295     long temp;
1296     int result = NextIntegerFromString( str, &temp );
1297
1298     if( result == 0 ) {
1299         *value = temp * 60; /* Minutes */
1300         if( **str == ':' ) {
1301             (*str)++;
1302             result = NextIntegerFromString( str, &temp );
1303             *value += temp; /* Seconds */
1304         }
1305     }
1306
1307     return result;
1308 }
1309
1310 int
1311 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1312 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1313     int result = -1, type = 0; long temp, temp2;
1314
1315     if(**str != ':') return -1; // old params remain in force!
1316     (*str)++;
1317     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1318     if( NextIntegerFromString( str, &temp ) ) return -1;
1319     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1320
1321     if(**str != '/') {
1322         /* time only: incremental or sudden-death time control */
1323         if(**str == '+') { /* increment follows; read it */
1324             (*str)++;
1325             if(**str == '!') type = *(*str)++; // Bronstein TC
1326             if(result = NextIntegerFromString( str, &temp2)) return -1;
1327             *inc = temp2 * 1000;
1328             if(**str == '.') { // read fraction of increment
1329                 char *start = ++(*str);
1330                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1331                 temp2 *= 1000;
1332                 while(start++ < *str) temp2 /= 10;
1333                 *inc += temp2;
1334             }
1335         } else *inc = 0;
1336         *moves = 0; *tc = temp * 1000; *incType = type;
1337         return 0;
1338     }
1339
1340     (*str)++; /* classical time control */
1341     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1342
1343     if(result == 0) {
1344         *moves = temp;
1345         *tc    = temp2 * 1000;
1346         *inc   = 0;
1347         *incType = type;
1348     }
1349     return result;
1350 }
1351
1352 int
1353 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1354 {   /* [HGM] get time to add from the multi-session time-control string */
1355     int incType, moves=1; /* kludge to force reading of first session */
1356     long time, increment;
1357     char *s = tcString;
1358
1359     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1360     do {
1361         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1362         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1363         if(movenr == -1) return time;    /* last move before new session     */
1364         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1365         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1366         if(!moves) return increment;     /* current session is incremental   */
1367         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1368     } while(movenr >= -1);               /* try again for next session       */
1369
1370     return 0; // no new time quota on this move
1371 }
1372
1373 int
1374 ParseTimeControl (char *tc, float ti, int mps)
1375 {
1376   long tc1;
1377   long tc2;
1378   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1379   int min, sec=0;
1380
1381   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1382   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1383       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1384   if(ti > 0) {
1385
1386     if(mps)
1387       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1388     else
1389       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1390   } else {
1391     if(mps)
1392       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1393     else
1394       snprintf(buf, MSG_SIZ, ":%s", mytc);
1395   }
1396   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1397
1398   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1399     return FALSE;
1400   }
1401
1402   if( *tc == '/' ) {
1403     /* Parse second time control */
1404     tc++;
1405
1406     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1407       return FALSE;
1408     }
1409
1410     if( tc2 == 0 ) {
1411       return FALSE;
1412     }
1413
1414     timeControl_2 = tc2 * 1000;
1415   }
1416   else {
1417     timeControl_2 = 0;
1418   }
1419
1420   if( tc1 == 0 ) {
1421     return FALSE;
1422   }
1423
1424   timeControl = tc1 * 1000;
1425
1426   if (ti >= 0) {
1427     timeIncrement = ti * 1000;  /* convert to ms */
1428     movesPerSession = 0;
1429   } else {
1430     timeIncrement = 0;
1431     movesPerSession = mps;
1432   }
1433   return TRUE;
1434 }
1435
1436 void
1437 InitBackEnd2 ()
1438 {
1439     if (appData.debugMode) {
1440 #    ifdef __GIT_VERSION
1441       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1442 #    else
1443       fprintf(debugFP, "Version: %s\n", programVersion);
1444 #    endif
1445     }
1446     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1447
1448     set_cont_sequence(appData.wrapContSeq);
1449     if (appData.matchGames > 0) {
1450         appData.matchMode = TRUE;
1451     } else if (appData.matchMode) {
1452         appData.matchGames = 1;
1453     }
1454     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1455         appData.matchGames = appData.sameColorGames;
1456     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1457         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1458         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1459     }
1460     Reset(TRUE, FALSE);
1461     if (appData.noChessProgram || first.protocolVersion == 1) {
1462       InitBackEnd3();
1463     } else {
1464       /* kludge: allow timeout for initial "feature" commands */
1465       FreezeUI();
1466       DisplayMessage("", _("Starting chess program"));
1467       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1468     }
1469 }
1470
1471 int
1472 CalculateIndex (int index, int gameNr)
1473 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1474     int res;
1475     if(index > 0) return index; // fixed nmber
1476     if(index == 0) return 1;
1477     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1478     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1479     return res;
1480 }
1481
1482 int
1483 LoadGameOrPosition (int gameNr)
1484 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1485     if (*appData.loadGameFile != NULLCHAR) {
1486         if (!LoadGameFromFile(appData.loadGameFile,
1487                 CalculateIndex(appData.loadGameIndex, gameNr),
1488                               appData.loadGameFile, FALSE)) {
1489             DisplayFatalError(_("Bad game file"), 0, 1);
1490             return 0;
1491         }
1492     } else if (*appData.loadPositionFile != NULLCHAR) {
1493         if (!LoadPositionFromFile(appData.loadPositionFile,
1494                 CalculateIndex(appData.loadPositionIndex, gameNr),
1495                                   appData.loadPositionFile)) {
1496             DisplayFatalError(_("Bad position file"), 0, 1);
1497             return 0;
1498         }
1499     }
1500     return 1;
1501 }
1502
1503 void
1504 ReserveGame (int gameNr, char resChar)
1505 {
1506     FILE *tf = fopen(appData.tourneyFile, "r+");
1507     char *p, *q, c, buf[MSG_SIZ];
1508     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1509     safeStrCpy(buf, lastMsg, MSG_SIZ);
1510     DisplayMessage(_("Pick new game"), "");
1511     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1512     ParseArgsFromFile(tf);
1513     p = q = appData.results;
1514     if(appData.debugMode) {
1515       char *r = appData.participants;
1516       fprintf(debugFP, "results = '%s'\n", p);
1517       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1518       fprintf(debugFP, "\n");
1519     }
1520     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1521     nextGame = q - p;
1522     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1523     safeStrCpy(q, p, strlen(p) + 2);
1524     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1525     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1526     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1527         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1528         q[nextGame] = '*';
1529     }
1530     fseek(tf, -(strlen(p)+4), SEEK_END);
1531     c = fgetc(tf);
1532     if(c != '"') // depending on DOS or Unix line endings we can be one off
1533          fseek(tf, -(strlen(p)+2), SEEK_END);
1534     else fseek(tf, -(strlen(p)+3), SEEK_END);
1535     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1536     DisplayMessage(buf, "");
1537     free(p); appData.results = q;
1538     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1539        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1540       int round = appData.defaultMatchGames * appData.tourneyType;
1541       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1542          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1543         UnloadEngine(&first);  // next game belongs to other pairing;
1544         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1545     }
1546     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1547 }
1548
1549 void
1550 MatchEvent (int mode)
1551 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1552         int dummy;
1553         if(matchMode) { // already in match mode: switch it off
1554             abortMatch = TRUE;
1555             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1556             return;
1557         }
1558 //      if(gameMode != BeginningOfGame) {
1559 //          DisplayError(_("You can only start a match from the initial position."), 0);
1560 //          return;
1561 //      }
1562         abortMatch = FALSE;
1563         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1564         /* Set up machine vs. machine match */
1565         nextGame = 0;
1566         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1567         if(appData.tourneyFile[0]) {
1568             ReserveGame(-1, 0);
1569             if(nextGame > appData.matchGames) {
1570                 char buf[MSG_SIZ];
1571                 if(strchr(appData.results, '*') == NULL) {
1572                     FILE *f;
1573                     appData.tourneyCycles++;
1574                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1575                         fclose(f);
1576                         NextTourneyGame(-1, &dummy);
1577                         ReserveGame(-1, 0);
1578                         if(nextGame <= appData.matchGames) {
1579                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1580                             matchMode = mode;
1581                             ScheduleDelayedEvent(NextMatchGame, 10000);
1582                             return;
1583                         }
1584                     }
1585                 }
1586                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1587                 DisplayError(buf, 0);
1588                 appData.tourneyFile[0] = 0;
1589                 return;
1590             }
1591         } else
1592         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1593             DisplayFatalError(_("Can't have a match with no chess programs"),
1594                               0, 2);
1595             return;
1596         }
1597         matchMode = mode;
1598         matchGame = roundNr = 1;
1599         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1600         NextMatchGame();
1601 }
1602
1603 void
1604 InitBackEnd3 P((void))
1605 {
1606     GameMode initialMode;
1607     char buf[MSG_SIZ];
1608     int err, len;
1609
1610     ParseFeatures(appData.features[0], &first);
1611     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1612        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1613         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1614        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1615        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1616         char c, *q = first.variants, *p = strchr(q, ',');
1617         if(p) *p = NULLCHAR;
1618         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1619             int w, h, s;
1620             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1621                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1622             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1623             Reset(TRUE, FALSE);         // and re-initialize
1624         }
1625         if(p) *p = ',';
1626     }
1627
1628     InitChessProgram(&first, startedFromSetupPosition);
1629
1630     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1631         free(programVersion);
1632         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1633         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1634         FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1635     }
1636
1637     if (appData.icsActive) {
1638 #ifdef WIN32
1639         /* [DM] Make a console window if needed [HGM] merged ifs */
1640         ConsoleCreate();
1641 #endif
1642         err = establish();
1643         if (err != 0)
1644           {
1645             if (*appData.icsCommPort != NULLCHAR)
1646               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1647                              appData.icsCommPort);
1648             else
1649               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1650                         appData.icsHost, appData.icsPort);
1651
1652             if( (len >= MSG_SIZ) && appData.debugMode )
1653               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1654
1655             DisplayFatalError(buf, err, 1);
1656             return;
1657         }
1658         SetICSMode();
1659         telnetISR =
1660           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1661         fromUserISR =
1662           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1663         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1664             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1665     } else if (appData.noChessProgram) {
1666         SetNCPMode();
1667     } else {
1668         SetGNUMode();
1669     }
1670
1671     if (*appData.cmailGameName != NULLCHAR) {
1672         SetCmailMode();
1673         OpenLoopback(&cmailPR);
1674         cmailISR =
1675           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1676     }
1677
1678     ThawUI();
1679     DisplayMessage("", "");
1680     if (StrCaseCmp(appData.initialMode, "") == 0) {
1681       initialMode = BeginningOfGame;
1682       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1683         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1684         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1685         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1686         ModeHighlight();
1687       }
1688     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1689       initialMode = TwoMachinesPlay;
1690     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1691       initialMode = AnalyzeFile;
1692     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1693       initialMode = AnalyzeMode;
1694     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1695       initialMode = MachinePlaysWhite;
1696     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1697       initialMode = MachinePlaysBlack;
1698     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1699       initialMode = EditGame;
1700     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1701       initialMode = EditPosition;
1702     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1703       initialMode = Training;
1704     } else {
1705       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1706       if( (len >= MSG_SIZ) && appData.debugMode )
1707         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1708
1709       DisplayFatalError(buf, 0, 2);
1710       return;
1711     }
1712
1713     if (appData.matchMode) {
1714         if(appData.tourneyFile[0]) { // start tourney from command line
1715             FILE *f;
1716             if(f = fopen(appData.tourneyFile, "r")) {
1717                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1718                 fclose(f);
1719                 appData.clockMode = TRUE;
1720                 SetGNUMode();
1721             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1722         }
1723         MatchEvent(TRUE);
1724     } else if (*appData.cmailGameName != NULLCHAR) {
1725         /* Set up cmail mode */
1726         ReloadCmailMsgEvent(TRUE);
1727     } else {
1728         /* Set up other modes */
1729         if (initialMode == AnalyzeFile) {
1730           if (*appData.loadGameFile == NULLCHAR) {
1731             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1732             return;
1733           }
1734         }
1735         if (*appData.loadGameFile != NULLCHAR) {
1736             (void) LoadGameFromFile(appData.loadGameFile,
1737                                     appData.loadGameIndex,
1738                                     appData.loadGameFile, TRUE);
1739         } else if (*appData.loadPositionFile != NULLCHAR) {
1740             (void) LoadPositionFromFile(appData.loadPositionFile,
1741                                         appData.loadPositionIndex,
1742                                         appData.loadPositionFile);
1743             /* [HGM] try to make self-starting even after FEN load */
1744             /* to allow automatic setup of fairy variants with wtm */
1745             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1746                 gameMode = BeginningOfGame;
1747                 setboardSpoiledMachineBlack = 1;
1748             }
1749             /* [HGM] loadPos: make that every new game uses the setup */
1750             /* from file as long as we do not switch variant          */
1751             if(!blackPlaysFirst) {
1752                 startedFromPositionFile = TRUE;
1753                 CopyBoard(filePosition, boards[0]);
1754                 CopyBoard(initialPosition, boards[0]);
1755             }
1756         } else if(*appData.fen != NULLCHAR) {
1757             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1758                 startedFromPositionFile = TRUE;
1759                 Reset(TRUE, TRUE);
1760             }
1761         }
1762         if (initialMode == AnalyzeMode) {
1763           if (appData.noChessProgram) {
1764             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1765             return;
1766           }
1767           if (appData.icsActive) {
1768             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1769             return;
1770           }
1771           AnalyzeModeEvent();
1772         } else if (initialMode == AnalyzeFile) {
1773           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1774           ShowThinkingEvent();
1775           AnalyzeFileEvent();
1776           AnalysisPeriodicEvent(1);
1777         } else if (initialMode == MachinePlaysWhite) {
1778           if (appData.noChessProgram) {
1779             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1780                               0, 2);
1781             return;
1782           }
1783           if (appData.icsActive) {
1784             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1785                               0, 2);
1786             return;
1787           }
1788           MachineWhiteEvent();
1789         } else if (initialMode == MachinePlaysBlack) {
1790           if (appData.noChessProgram) {
1791             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1792                               0, 2);
1793             return;
1794           }
1795           if (appData.icsActive) {
1796             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1797                               0, 2);
1798             return;
1799           }
1800           MachineBlackEvent();
1801         } else if (initialMode == TwoMachinesPlay) {
1802           if (appData.noChessProgram) {
1803             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1804                               0, 2);
1805             return;
1806           }
1807           if (appData.icsActive) {
1808             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1809                               0, 2);
1810             return;
1811           }
1812           TwoMachinesEvent();
1813         } else if (initialMode == EditGame) {
1814           EditGameEvent();
1815         } else if (initialMode == EditPosition) {
1816           EditPositionEvent();
1817         } else if (initialMode == Training) {
1818           if (*appData.loadGameFile == NULLCHAR) {
1819             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1820             return;
1821           }
1822           TrainingEvent();
1823         }
1824     }
1825 }
1826
1827 void
1828 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1829 {
1830     DisplayBook(current+1);
1831
1832     MoveHistorySet( movelist, first, last, current, pvInfoList );
1833
1834     EvalGraphSet( first, last, current, pvInfoList );
1835
1836     MakeEngineOutputTitle();
1837 }
1838
1839 /*
1840  * Establish will establish a contact to a remote host.port.
1841  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1842  *  used to talk to the host.
1843  * Returns 0 if okay, error code if not.
1844  */
1845 int
1846 establish ()
1847 {
1848     char buf[MSG_SIZ];
1849
1850     if (*appData.icsCommPort != NULLCHAR) {
1851         /* Talk to the host through a serial comm port */
1852         return OpenCommPort(appData.icsCommPort, &icsPR);
1853
1854     } else if (*appData.gateway != NULLCHAR) {
1855         if (*appData.remoteShell == NULLCHAR) {
1856             /* Use the rcmd protocol to run telnet program on a gateway host */
1857             snprintf(buf, sizeof(buf), "%s %s %s",
1858                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1859             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1860
1861         } else {
1862             /* Use the rsh program to run telnet program on a gateway host */
1863             if (*appData.remoteUser == NULLCHAR) {
1864                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1865                         appData.gateway, appData.telnetProgram,
1866                         appData.icsHost, appData.icsPort);
1867             } else {
1868                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1869                         appData.remoteShell, appData.gateway,
1870                         appData.remoteUser, appData.telnetProgram,
1871                         appData.icsHost, appData.icsPort);
1872             }
1873             return StartChildProcess(buf, "", &icsPR);
1874
1875         }
1876     } else if (appData.useTelnet) {
1877         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1878
1879     } else {
1880         /* TCP socket interface differs somewhat between
1881            Unix and NT; handle details in the front end.
1882            */
1883         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1884     }
1885 }
1886
1887 void
1888 EscapeExpand (char *p, char *q)
1889 {       // [HGM] initstring: routine to shape up string arguments
1890         while(*p++ = *q++) if(p[-1] == '\\')
1891             switch(*q++) {
1892                 case 'n': p[-1] = '\n'; break;
1893                 case 'r': p[-1] = '\r'; break;
1894                 case 't': p[-1] = '\t'; break;
1895                 case '\\': p[-1] = '\\'; break;
1896                 case 0: *p = 0; return;
1897                 default: p[-1] = q[-1]; break;
1898             }
1899 }
1900
1901 void
1902 show_bytes (FILE *fp, char *buf, int count)
1903 {
1904     while (count--) {
1905         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1906             fprintf(fp, "\\%03o", *buf & 0xff);
1907         } else {
1908             putc(*buf, fp);
1909         }
1910         buf++;
1911     }
1912     fflush(fp);
1913 }
1914
1915 /* Returns an errno value */
1916 int
1917 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1918 {
1919     char buf[8192], *p, *q, *buflim;
1920     int left, newcount, outcount;
1921
1922     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1923         *appData.gateway != NULLCHAR) {
1924         if (appData.debugMode) {
1925             fprintf(debugFP, ">ICS: ");
1926             show_bytes(debugFP, message, count);
1927             fprintf(debugFP, "\n");
1928         }
1929         return OutputToProcess(pr, message, count, outError);
1930     }
1931
1932     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1933     p = message;
1934     q = buf;
1935     left = count;
1936     newcount = 0;
1937     while (left) {
1938         if (q >= buflim) {
1939             if (appData.debugMode) {
1940                 fprintf(debugFP, ">ICS: ");
1941                 show_bytes(debugFP, buf, newcount);
1942                 fprintf(debugFP, "\n");
1943             }
1944             outcount = OutputToProcess(pr, buf, newcount, outError);
1945             if (outcount < newcount) return -1; /* to be sure */
1946             q = buf;
1947             newcount = 0;
1948         }
1949         if (*p == '\n') {
1950             *q++ = '\r';
1951             newcount++;
1952         } else if (((unsigned char) *p) == TN_IAC) {
1953             *q++ = (char) TN_IAC;
1954             newcount ++;
1955         }
1956         *q++ = *p++;
1957         newcount++;
1958         left--;
1959     }
1960     if (appData.debugMode) {
1961         fprintf(debugFP, ">ICS: ");
1962         show_bytes(debugFP, buf, newcount);
1963         fprintf(debugFP, "\n");
1964     }
1965     outcount = OutputToProcess(pr, buf, newcount, outError);
1966     if (outcount < newcount) return -1; /* to be sure */
1967     return count;
1968 }
1969
1970 void
1971 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1972 {
1973     int outError, outCount;
1974     static int gotEof = 0;
1975     static FILE *ini;
1976
1977     /* Pass data read from player on to ICS */
1978     if (count > 0) {
1979         gotEof = 0;
1980         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1981         if (outCount < count) {
1982             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1983         }
1984         if(have_sent_ICS_logon == 2) {
1985           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1986             fprintf(ini, "%s", message);
1987             have_sent_ICS_logon = 3;
1988           } else
1989             have_sent_ICS_logon = 1;
1990         } else if(have_sent_ICS_logon == 3) {
1991             fprintf(ini, "%s", message);
1992             fclose(ini);
1993           have_sent_ICS_logon = 1;
1994         }
1995     } else if (count < 0) {
1996         RemoveInputSource(isr);
1997         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1998     } else if (gotEof++ > 0) {
1999         RemoveInputSource(isr);
2000         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
2001     }
2002 }
2003
2004 void
2005 KeepAlive ()
2006 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2007     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2008     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2009     SendToICS("date\n");
2010     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2011 }
2012
2013 /* added routine for printf style output to ics */
2014 void
2015 ics_printf (char *format, ...)
2016 {
2017     char buffer[MSG_SIZ];
2018     va_list args;
2019
2020     va_start(args, format);
2021     vsnprintf(buffer, sizeof(buffer), format, args);
2022     buffer[sizeof(buffer)-1] = '\0';
2023     SendToICS(buffer);
2024     va_end(args);
2025 }
2026
2027 void
2028 SendToICS (char *s)
2029 {
2030     int count, outCount, outError;
2031
2032     if (icsPR == NoProc) return;
2033
2034     count = strlen(s);
2035     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2036     if (outCount < count) {
2037         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2038     }
2039 }
2040
2041 /* This is used for sending logon scripts to the ICS. Sending
2042    without a delay causes problems when using timestamp on ICC
2043    (at least on my machine). */
2044 void
2045 SendToICSDelayed (char *s, long msdelay)
2046 {
2047     int count, outCount, outError;
2048
2049     if (icsPR == NoProc) return;
2050
2051     count = strlen(s);
2052     if (appData.debugMode) {
2053         fprintf(debugFP, ">ICS: ");
2054         show_bytes(debugFP, s, count);
2055         fprintf(debugFP, "\n");
2056     }
2057     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2058                                       msdelay);
2059     if (outCount < count) {
2060         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2061     }
2062 }
2063
2064
2065 /* Remove all highlighting escape sequences in s
2066    Also deletes any suffix starting with '('
2067    */
2068 char *
2069 StripHighlightAndTitle (char *s)
2070 {
2071     static char retbuf[MSG_SIZ];
2072     char *p = retbuf;
2073
2074     while (*s != NULLCHAR) {
2075         while (*s == '\033') {
2076             while (*s != NULLCHAR && !isalpha(*s)) s++;
2077             if (*s != NULLCHAR) s++;
2078         }
2079         while (*s != NULLCHAR && *s != '\033') {
2080             if (*s == '(' || *s == '[') {
2081                 *p = NULLCHAR;
2082                 return retbuf;
2083             }
2084             *p++ = *s++;
2085         }
2086     }
2087     *p = NULLCHAR;
2088     return retbuf;
2089 }
2090
2091 /* Remove all highlighting escape sequences in s */
2092 char *
2093 StripHighlight (char *s)
2094 {
2095     static char retbuf[MSG_SIZ];
2096     char *p = retbuf;
2097
2098     while (*s != NULLCHAR) {
2099         while (*s == '\033') {
2100             while (*s != NULLCHAR && !isalpha(*s)) s++;
2101             if (*s != NULLCHAR) s++;
2102         }
2103         while (*s != NULLCHAR && *s != '\033') {
2104             *p++ = *s++;
2105         }
2106     }
2107     *p = NULLCHAR;
2108     return retbuf;
2109 }
2110
2111 char engineVariant[MSG_SIZ];
2112 char *variantNames[] = VARIANT_NAMES;
2113 char *
2114 VariantName (VariantClass v)
2115 {
2116     if(v == VariantUnknown || *engineVariant) return engineVariant;
2117     return variantNames[v];
2118 }
2119
2120
2121 /* Identify a variant from the strings the chess servers use or the
2122    PGN Variant tag names we use. */
2123 VariantClass
2124 StringToVariant (char *e)
2125 {
2126     char *p;
2127     int wnum = -1;
2128     VariantClass v = VariantNormal;
2129     int i, found = FALSE;
2130     char buf[MSG_SIZ], c;
2131     int len;
2132
2133     if (!e) return v;
2134
2135     /* [HGM] skip over optional board-size prefixes */
2136     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2137         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2138         while( *e++ != '_');
2139     }
2140
2141     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2142         v = VariantNormal;
2143         found = TRUE;
2144     } else
2145     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2146       if (p = StrCaseStr(e, variantNames[i])) {
2147         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2148         v = (VariantClass) i;
2149         found = TRUE;
2150         break;
2151       }
2152     }
2153
2154     if (!found) {
2155       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2156           || StrCaseStr(e, "wild/fr")
2157           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2158         v = VariantFischeRandom;
2159       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2160                  (i = 1, p = StrCaseStr(e, "w"))) {
2161         p += i;
2162         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2163         if (isdigit(*p)) {
2164           wnum = atoi(p);
2165         } else {
2166           wnum = -1;
2167         }
2168         switch (wnum) {
2169         case 0: /* FICS only, actually */
2170         case 1:
2171           /* Castling legal even if K starts on d-file */
2172           v = VariantWildCastle;
2173           break;
2174         case 2:
2175         case 3:
2176         case 4:
2177           /* Castling illegal even if K & R happen to start in
2178              normal positions. */
2179           v = VariantNoCastle;
2180           break;
2181         case 5:
2182         case 7:
2183         case 8:
2184         case 10:
2185         case 11:
2186         case 12:
2187         case 13:
2188         case 14:
2189         case 15:
2190         case 18:
2191         case 19:
2192           /* Castling legal iff K & R start in normal positions */
2193           v = VariantNormal;
2194           break;
2195         case 6:
2196         case 20:
2197         case 21:
2198           /* Special wilds for position setup; unclear what to do here */
2199           v = VariantLoadable;
2200           break;
2201         case 9:
2202           /* Bizarre ICC game */
2203           v = VariantTwoKings;
2204           break;
2205         case 16:
2206           v = VariantKriegspiel;
2207           break;
2208         case 17:
2209           v = VariantLosers;
2210           break;
2211         case 22:
2212           v = VariantFischeRandom;
2213           break;
2214         case 23:
2215           v = VariantCrazyhouse;
2216           break;
2217         case 24:
2218           v = VariantBughouse;
2219           break;
2220         case 25:
2221           v = Variant3Check;
2222           break;
2223         case 26:
2224           /* Not quite the same as FICS suicide! */
2225           v = VariantGiveaway;
2226           break;
2227         case 27:
2228           v = VariantAtomic;
2229           break;
2230         case 28:
2231           v = VariantShatranj;
2232           break;
2233
2234         /* Temporary names for future ICC types.  The name *will* change in
2235            the next xboard/WinBoard release after ICC defines it. */
2236         case 29:
2237           v = Variant29;
2238           break;
2239         case 30:
2240           v = Variant30;
2241           break;
2242         case 31:
2243           v = Variant31;
2244           break;
2245         case 32:
2246           v = Variant32;
2247           break;
2248         case 33:
2249           v = Variant33;
2250           break;
2251         case 34:
2252           v = Variant34;
2253           break;
2254         case 35:
2255           v = Variant35;
2256           break;
2257         case 36:
2258           v = Variant36;
2259           break;
2260         case 37:
2261           v = VariantShogi;
2262           break;
2263         case 38:
2264           v = VariantXiangqi;
2265           break;
2266         case 39:
2267           v = VariantCourier;
2268           break;
2269         case 40:
2270           v = VariantGothic;
2271           break;
2272         case 41:
2273           v = VariantCapablanca;
2274           break;
2275         case 42:
2276           v = VariantKnightmate;
2277           break;
2278         case 43:
2279           v = VariantFairy;
2280           break;
2281         case 44:
2282           v = VariantCylinder;
2283           break;
2284         case 45:
2285           v = VariantFalcon;
2286           break;
2287         case 46:
2288           v = VariantCapaRandom;
2289           break;
2290         case 47:
2291           v = VariantBerolina;
2292           break;
2293         case 48:
2294           v = VariantJanus;
2295           break;
2296         case 49:
2297           v = VariantSuper;
2298           break;
2299         case 50:
2300           v = VariantGreat;
2301           break;
2302         case -1:
2303           /* Found "wild" or "w" in the string but no number;
2304              must assume it's normal chess. */
2305           v = VariantNormal;
2306           break;
2307         default:
2308           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2309           if( (len >= MSG_SIZ) && appData.debugMode )
2310             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2311
2312           DisplayError(buf, 0);
2313           v = VariantUnknown;
2314           break;
2315         }
2316       }
2317     }
2318     if (appData.debugMode) {
2319       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2320               e, wnum, VariantName(v));
2321     }
2322     return v;
2323 }
2324
2325 static int leftover_start = 0, leftover_len = 0;
2326 char star_match[STAR_MATCH_N][MSG_SIZ];
2327
2328 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2329    advance *index beyond it, and set leftover_start to the new value of
2330    *index; else return FALSE.  If pattern contains the character '*', it
2331    matches any sequence of characters not containing '\r', '\n', or the
2332    character following the '*' (if any), and the matched sequence(s) are
2333    copied into star_match.
2334    */
2335 int
2336 looking_at ( char *buf, int *index, char *pattern)
2337 {
2338     char *bufp = &buf[*index], *patternp = pattern;
2339     int star_count = 0;
2340     char *matchp = star_match[0];
2341
2342     for (;;) {
2343         if (*patternp == NULLCHAR) {
2344             *index = leftover_start = bufp - buf;
2345             *matchp = NULLCHAR;
2346             return TRUE;
2347         }
2348         if (*bufp == NULLCHAR) return FALSE;
2349         if (*patternp == '*') {
2350             if (*bufp == *(patternp + 1)) {
2351                 *matchp = NULLCHAR;
2352                 matchp = star_match[++star_count];
2353                 patternp += 2;
2354                 bufp++;
2355                 continue;
2356             } else if (*bufp == '\n' || *bufp == '\r') {
2357                 patternp++;
2358                 if (*patternp == NULLCHAR)
2359                   continue;
2360                 else
2361                   return FALSE;
2362             } else {
2363                 *matchp++ = *bufp++;
2364                 continue;
2365             }
2366         }
2367         if (*patternp != *bufp) return FALSE;
2368         patternp++;
2369         bufp++;
2370     }
2371 }
2372
2373 void
2374 SendToPlayer (char *data, int length)
2375 {
2376     int error, outCount;
2377     outCount = OutputToProcess(NoProc, data, length, &error);
2378     if (outCount < length) {
2379         DisplayFatalError(_("Error writing to display"), error, 1);
2380     }
2381 }
2382
2383 void
2384 PackHolding (char packed[], char *holding)
2385 {
2386     char *p = holding;
2387     char *q = packed;
2388     int runlength = 0;
2389     int curr = 9999;
2390     do {
2391         if (*p == curr) {
2392             runlength++;
2393         } else {
2394             switch (runlength) {
2395               case 0:
2396                 break;
2397               case 1:
2398                 *q++ = curr;
2399                 break;
2400               case 2:
2401                 *q++ = curr;
2402                 *q++ = curr;
2403                 break;
2404               default:
2405                 sprintf(q, "%d", runlength);
2406                 while (*q) q++;
2407                 *q++ = curr;
2408                 break;
2409             }
2410             runlength = 1;
2411             curr = *p;
2412         }
2413     } while (*p++);
2414     *q = NULLCHAR;
2415 }
2416
2417 /* Telnet protocol requests from the front end */
2418 void
2419 TelnetRequest (unsigned char ddww, unsigned char option)
2420 {
2421     unsigned char msg[3];
2422     int outCount, outError;
2423
2424     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2425
2426     if (appData.debugMode) {
2427         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2428         switch (ddww) {
2429           case TN_DO:
2430             ddwwStr = "DO";
2431             break;
2432           case TN_DONT:
2433             ddwwStr = "DONT";
2434             break;
2435           case TN_WILL:
2436             ddwwStr = "WILL";
2437             break;
2438           case TN_WONT:
2439             ddwwStr = "WONT";
2440             break;
2441           default:
2442             ddwwStr = buf1;
2443             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2444             break;
2445         }
2446         switch (option) {
2447           case TN_ECHO:
2448             optionStr = "ECHO";
2449             break;
2450           default:
2451             optionStr = buf2;
2452             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2453             break;
2454         }
2455         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2456     }
2457     msg[0] = TN_IAC;
2458     msg[1] = ddww;
2459     msg[2] = option;
2460     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2461     if (outCount < 3) {
2462         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2463     }
2464 }
2465
2466 void
2467 DoEcho ()
2468 {
2469     if (!appData.icsActive) return;
2470     TelnetRequest(TN_DO, TN_ECHO);
2471 }
2472
2473 void
2474 DontEcho ()
2475 {
2476     if (!appData.icsActive) return;
2477     TelnetRequest(TN_DONT, TN_ECHO);
2478 }
2479
2480 void
2481 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2482 {
2483     /* put the holdings sent to us by the server on the board holdings area */
2484     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2485     char p;
2486     ChessSquare piece;
2487
2488     if(gameInfo.holdingsWidth < 2)  return;
2489     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2490         return; // prevent overwriting by pre-board holdings
2491
2492     if( (int)lowestPiece >= BlackPawn ) {
2493         holdingsColumn = 0;
2494         countsColumn = 1;
2495         holdingsStartRow = handSize-1;
2496         direction = -1;
2497     } else {
2498         holdingsColumn = BOARD_WIDTH-1;
2499         countsColumn = BOARD_WIDTH-2;
2500         holdingsStartRow = 0;
2501         direction = 1;
2502     }
2503
2504     for(i=0; i<BOARD_RANKS-1; i++) { /* clear holdings */
2505         board[i][holdingsColumn] = EmptySquare;
2506         board[i][countsColumn]   = (ChessSquare) 0;
2507     }
2508     while( (p=*holdings++) != NULLCHAR ) {
2509         piece = CharToPiece( ToUpper(p) );
2510         if(piece == EmptySquare) continue;
2511         /*j = (int) piece - (int) WhitePawn;*/
2512         j = PieceToNumber(piece);
2513         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2514         if(j < 0) continue;               /* should not happen */
2515         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2516         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2517         board[holdingsStartRow+j*direction][countsColumn]++;
2518     }
2519 }
2520
2521
2522 void
2523 VariantSwitch (Board board, VariantClass newVariant)
2524 {
2525    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2526    static Board oldBoard;
2527
2528    startedFromPositionFile = FALSE;
2529    if(gameInfo.variant == newVariant) return;
2530
2531    /* [HGM] This routine is called each time an assignment is made to
2532     * gameInfo.variant during a game, to make sure the board sizes
2533     * are set to match the new variant. If that means adding or deleting
2534     * holdings, we shift the playing board accordingly
2535     * This kludge is needed because in ICS observe mode, we get boards
2536     * of an ongoing game without knowing the variant, and learn about the
2537     * latter only later. This can be because of the move list we requested,
2538     * in which case the game history is refilled from the beginning anyway,
2539     * but also when receiving holdings of a crazyhouse game. In the latter
2540     * case we want to add those holdings to the already received position.
2541     */
2542
2543
2544    if (appData.debugMode) {
2545      fprintf(debugFP, "Switch board from %s to %s\n",
2546              VariantName(gameInfo.variant), VariantName(newVariant));
2547      setbuf(debugFP, NULL);
2548    }
2549    shuffleOpenings = 0;       /* [HGM] shuffle */
2550    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2551    switch(newVariant)
2552      {
2553      case VariantShogi:
2554        newWidth = 9;  newHeight = 9;
2555        gameInfo.holdingsSize = 7;
2556      case VariantBughouse:
2557      case VariantCrazyhouse:
2558        newHoldingsWidth = 2; break;
2559      case VariantGreat:
2560        newWidth = 10;
2561      case VariantSuper:
2562        newHoldingsWidth = 2;
2563        gameInfo.holdingsSize = 8;
2564        break;
2565      case VariantGothic:
2566      case VariantCapablanca:
2567      case VariantCapaRandom:
2568        newWidth = 10;
2569      default:
2570        newHoldingsWidth = gameInfo.holdingsSize = 0;
2571      };
2572
2573    if(newWidth  != gameInfo.boardWidth  ||
2574       newHeight != gameInfo.boardHeight ||
2575       newHoldingsWidth != gameInfo.holdingsWidth ) {
2576
2577      /* shift position to new playing area, if needed */
2578      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2579        for(i=0; i<BOARD_HEIGHT; i++)
2580          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2581            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2582              board[i][j];
2583        for(i=0; i<newHeight; i++) {
2584          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2585          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2586        }
2587      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2588        for(i=0; i<BOARD_HEIGHT; i++)
2589          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2590            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2591              board[i][j];
2592      }
2593      board[HOLDINGS_SET] = 0;
2594      gameInfo.boardWidth  = newWidth;
2595      gameInfo.boardHeight = newHeight;
2596      gameInfo.holdingsWidth = newHoldingsWidth;
2597      gameInfo.variant = newVariant;
2598      InitDrawingSizes(-2, 0);
2599    } else gameInfo.variant = newVariant;
2600    CopyBoard(oldBoard, board);   // remember correctly formatted board
2601      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2602    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2603 }
2604
2605 static int loggedOn = FALSE;
2606
2607 /*-- Game start info cache: --*/
2608 int gs_gamenum;
2609 char gs_kind[MSG_SIZ];
2610 static char player1Name[128] = "";
2611 static char player2Name[128] = "";
2612 static char cont_seq[] = "\n\\   ";
2613 static int player1Rating = -1;
2614 static int player2Rating = -1;
2615 /*----------------------------*/
2616
2617 ColorClass curColor = ColorNormal;
2618 int suppressKibitz = 0;
2619
2620 // [HGM] seekgraph
2621 Boolean soughtPending = FALSE;
2622 Boolean seekGraphUp;
2623 #define MAX_SEEK_ADS 200
2624 #define SQUARE 0x80
2625 char *seekAdList[MAX_SEEK_ADS];
2626 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2627 float tcList[MAX_SEEK_ADS];
2628 char colorList[MAX_SEEK_ADS];
2629 int nrOfSeekAds = 0;
2630 int minRating = 1010, maxRating = 2800;
2631 int hMargin = 10, vMargin = 20, h, w;
2632 extern int squareSize, lineGap;
2633
2634 void
2635 PlotSeekAd (int i)
2636 {
2637         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2638         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2639         if(r < minRating+100 && r >=0 ) r = minRating+100;
2640         if(r > maxRating) r = maxRating;
2641         if(tc < 1.f) tc = 1.f;
2642         if(tc > 95.f) tc = 95.f;
2643         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2644         y = ((double)r - minRating)/(maxRating - minRating)
2645             * (h-vMargin-squareSize/8-1) + vMargin;
2646         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2647         if(strstr(seekAdList[i], " u ")) color = 1;
2648         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2649            !strstr(seekAdList[i], "bullet") &&
2650            !strstr(seekAdList[i], "blitz") &&
2651            !strstr(seekAdList[i], "standard") ) color = 2;
2652         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2653         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2654 }
2655
2656 void
2657 PlotSingleSeekAd (int i)
2658 {
2659         PlotSeekAd(i);
2660 }
2661
2662 void
2663 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2664 {
2665         char buf[MSG_SIZ], *ext = "";
2666         VariantClass v = StringToVariant(type);
2667         if(strstr(type, "wild")) {
2668             ext = type + 4; // append wild number
2669             if(v == VariantFischeRandom) type = "chess960"; else
2670             if(v == VariantLoadable) type = "setup"; else
2671             type = VariantName(v);
2672         }
2673         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2674         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2675             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2676             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2677             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2678             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2679             seekNrList[nrOfSeekAds] = nr;
2680             zList[nrOfSeekAds] = 0;
2681             seekAdList[nrOfSeekAds++] = StrSave(buf);
2682             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2683         }
2684 }
2685
2686 void
2687 EraseSeekDot (int i)
2688 {
2689     int x = xList[i], y = yList[i], d=squareSize/4, k;
2690     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2691     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2692     // now replot every dot that overlapped
2693     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2694         int xx = xList[k], yy = yList[k];
2695         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2696             DrawSeekDot(xx, yy, colorList[k]);
2697     }
2698 }
2699
2700 void
2701 RemoveSeekAd (int nr)
2702 {
2703         int i;
2704         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2705             EraseSeekDot(i);
2706             if(seekAdList[i]) free(seekAdList[i]);
2707             seekAdList[i] = seekAdList[--nrOfSeekAds];
2708             seekNrList[i] = seekNrList[nrOfSeekAds];
2709             ratingList[i] = ratingList[nrOfSeekAds];
2710             colorList[i]  = colorList[nrOfSeekAds];
2711             tcList[i] = tcList[nrOfSeekAds];
2712             xList[i]  = xList[nrOfSeekAds];
2713             yList[i]  = yList[nrOfSeekAds];
2714             zList[i]  = zList[nrOfSeekAds];
2715             seekAdList[nrOfSeekAds] = NULL;
2716             break;
2717         }
2718 }
2719
2720 Boolean
2721 MatchSoughtLine (char *line)
2722 {
2723     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2724     int nr, base, inc, u=0; char dummy;
2725
2726     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2727        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2728        (u=1) &&
2729        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2730         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2731         // match: compact and save the line
2732         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2733         return TRUE;
2734     }
2735     return FALSE;
2736 }
2737
2738 int
2739 DrawSeekGraph ()
2740 {
2741     int i;
2742     if(!seekGraphUp) return FALSE;
2743     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2744     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2745
2746     DrawSeekBackground(0, 0, w, h);
2747     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2748     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2749     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2750         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2751         yy = h-1-yy;
2752         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2753         if(i%500 == 0) {
2754             char buf[MSG_SIZ];
2755             snprintf(buf, MSG_SIZ, "%d", i);
2756             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2757         }
2758     }
2759     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2760     for(i=1; i<100; i+=(i<10?1:5)) {
2761         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2762         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2763         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2764             char buf[MSG_SIZ];
2765             snprintf(buf, MSG_SIZ, "%d", i);
2766             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2767         }
2768     }
2769     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2770     return TRUE;
2771 }
2772
2773 int
2774 SeekGraphClick (ClickType click, int x, int y, int moving)
2775 {
2776     static int lastDown = 0, displayed = 0, lastSecond;
2777     if(y < 0) return FALSE;
2778     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2779         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2780         if(!seekGraphUp) return FALSE;
2781         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2782         DrawPosition(TRUE, NULL);
2783         return TRUE;
2784     }
2785     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2786         if(click == Release || moving) return FALSE;
2787         nrOfSeekAds = 0;
2788         soughtPending = TRUE;
2789         SendToICS(ics_prefix);
2790         SendToICS("sought\n"); // should this be "sought all"?
2791     } else { // issue challenge based on clicked ad
2792         int dist = 10000; int i, closest = 0, second = 0;
2793         for(i=0; i<nrOfSeekAds; i++) {
2794             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2795             if(d < dist) { dist = d; closest = i; }
2796             second += (d - zList[i] < 120); // count in-range ads
2797             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2798         }
2799         if(dist < 120) {
2800             char buf[MSG_SIZ];
2801             second = (second > 1);
2802             if(displayed != closest || second != lastSecond) {
2803                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2804                 lastSecond = second; displayed = closest;
2805             }
2806             if(click == Press) {
2807                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2808                 lastDown = closest;
2809                 return TRUE;
2810             } // on press 'hit', only show info
2811             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2812             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2813             SendToICS(ics_prefix);
2814             SendToICS(buf);
2815             return TRUE; // let incoming board of started game pop down the graph
2816         } else if(click == Release) { // release 'miss' is ignored
2817             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2818             if(moving == 2) { // right up-click
2819                 nrOfSeekAds = 0; // refresh graph
2820                 soughtPending = TRUE;
2821                 SendToICS(ics_prefix);
2822                 SendToICS("sought\n"); // should this be "sought all"?
2823             }
2824             return TRUE;
2825         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2826         // press miss or release hit 'pop down' seek graph
2827         seekGraphUp = FALSE;
2828         DrawPosition(TRUE, NULL);
2829     }
2830     return TRUE;
2831 }
2832
2833 void
2834 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2835 {
2836 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2837 #define STARTED_NONE 0
2838 #define STARTED_MOVES 1
2839 #define STARTED_BOARD 2
2840 #define STARTED_OBSERVE 3
2841 #define STARTED_HOLDINGS 4
2842 #define STARTED_CHATTER 5
2843 #define STARTED_COMMENT 6
2844 #define STARTED_MOVES_NOHIDE 7
2845
2846     static int started = STARTED_NONE;
2847     static char parse[20000];
2848     static int parse_pos = 0;
2849     static char buf[BUF_SIZE + 1];
2850     static int firstTime = TRUE, intfSet = FALSE;
2851     static ColorClass prevColor = ColorNormal;
2852     static int savingComment = FALSE;
2853     static int cmatch = 0; // continuation sequence match
2854     char *bp;
2855     char str[MSG_SIZ];
2856     int i, oldi;
2857     int buf_len;
2858     int next_out;
2859     int tkind;
2860     int backup;    /* [DM] For zippy color lines */
2861     char *p;
2862     char talker[MSG_SIZ]; // [HGM] chat
2863     int channel, collective=0;
2864
2865     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2866
2867     if (appData.debugMode) {
2868       if (!error) {
2869         fprintf(debugFP, "<ICS: ");
2870         show_bytes(debugFP, data, count);
2871         fprintf(debugFP, "\n");
2872       }
2873     }
2874
2875     if (appData.debugMode) { int f = forwardMostMove;
2876         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2877                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2878                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2879     }
2880     if (count > 0) {
2881         /* If last read ended with a partial line that we couldn't parse,
2882            prepend it to the new read and try again. */
2883         if (leftover_len > 0) {
2884             for (i=0; i<leftover_len; i++)
2885               buf[i] = buf[leftover_start + i];
2886         }
2887
2888     /* copy new characters into the buffer */
2889     bp = buf + leftover_len;
2890     buf_len=leftover_len;
2891     for (i=0; i<count; i++)
2892     {
2893         // ignore these
2894         if (data[i] == '\r')
2895             continue;
2896
2897         // join lines split by ICS?
2898         if (!appData.noJoin)
2899         {
2900             /*
2901                 Joining just consists of finding matches against the
2902                 continuation sequence, and discarding that sequence
2903                 if found instead of copying it.  So, until a match
2904                 fails, there's nothing to do since it might be the
2905                 complete sequence, and thus, something we don't want
2906                 copied.
2907             */
2908             if (data[i] == cont_seq[cmatch])
2909             {
2910                 cmatch++;
2911                 if (cmatch == strlen(cont_seq))
2912                 {
2913                     cmatch = 0; // complete match.  just reset the counter
2914
2915                     /*
2916                         it's possible for the ICS to not include the space
2917                         at the end of the last word, making our [correct]
2918                         join operation fuse two separate words.  the server
2919                         does this when the space occurs at the width setting.
2920                     */
2921                     if (!buf_len || buf[buf_len-1] != ' ')
2922                     {
2923                         *bp++ = ' ';
2924                         buf_len++;
2925                     }
2926                 }
2927                 continue;
2928             }
2929             else if (cmatch)
2930             {
2931                 /*
2932                     match failed, so we have to copy what matched before
2933                     falling through and copying this character.  In reality,
2934                     this will only ever be just the newline character, but
2935                     it doesn't hurt to be precise.
2936                 */
2937                 strncpy(bp, cont_seq, cmatch);
2938                 bp += cmatch;
2939                 buf_len += cmatch;
2940                 cmatch = 0;
2941             }
2942         }
2943
2944         // copy this char
2945         *bp++ = data[i];
2946         buf_len++;
2947     }
2948
2949         buf[buf_len] = NULLCHAR;
2950 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2951         next_out = 0;
2952         leftover_start = 0;
2953
2954         i = 0;
2955         while (i < buf_len) {
2956             /* Deal with part of the TELNET option negotiation
2957                protocol.  We refuse to do anything beyond the
2958                defaults, except that we allow the WILL ECHO option,
2959                which ICS uses to turn off password echoing when we are
2960                directly connected to it.  We reject this option
2961                if localLineEditing mode is on (always on in xboard)
2962                and we are talking to port 23, which might be a real
2963                telnet server that will try to keep WILL ECHO on permanently.
2964              */
2965             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2966                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2967                 unsigned char option;
2968                 oldi = i;
2969                 switch ((unsigned char) buf[++i]) {
2970                   case TN_WILL:
2971                     if (appData.debugMode)
2972                       fprintf(debugFP, "\n<WILL ");
2973                     switch (option = (unsigned char) buf[++i]) {
2974                       case TN_ECHO:
2975                         if (appData.debugMode)
2976                           fprintf(debugFP, "ECHO ");
2977                         /* Reply only if this is a change, according
2978                            to the protocol rules. */
2979                         if (remoteEchoOption) break;
2980                         if (appData.localLineEditing &&
2981                             atoi(appData.icsPort) == TN_PORT) {
2982                             TelnetRequest(TN_DONT, TN_ECHO);
2983                         } else {
2984                             EchoOff();
2985                             TelnetRequest(TN_DO, TN_ECHO);
2986                             remoteEchoOption = TRUE;
2987                         }
2988                         break;
2989                       default:
2990                         if (appData.debugMode)
2991                           fprintf(debugFP, "%d ", option);
2992                         /* Whatever this is, we don't want it. */
2993                         TelnetRequest(TN_DONT, option);
2994                         break;
2995                     }
2996                     break;
2997                   case TN_WONT:
2998                     if (appData.debugMode)
2999                       fprintf(debugFP, "\n<WONT ");
3000                     switch (option = (unsigned char) buf[++i]) {
3001                       case TN_ECHO:
3002                         if (appData.debugMode)
3003                           fprintf(debugFP, "ECHO ");
3004                         /* Reply only if this is a change, according
3005                            to the protocol rules. */
3006                         if (!remoteEchoOption) break;
3007                         EchoOn();
3008                         TelnetRequest(TN_DONT, TN_ECHO);
3009                         remoteEchoOption = FALSE;
3010                         break;
3011                       default:
3012                         if (appData.debugMode)
3013                           fprintf(debugFP, "%d ", (unsigned char) option);
3014                         /* Whatever this is, it must already be turned
3015                            off, because we never agree to turn on
3016                            anything non-default, so according to the
3017                            protocol rules, we don't reply. */
3018                         break;
3019                     }
3020                     break;
3021                   case TN_DO:
3022                     if (appData.debugMode)
3023                       fprintf(debugFP, "\n<DO ");
3024                     switch (option = (unsigned char) buf[++i]) {
3025                       default:
3026                         /* Whatever this is, we refuse to do it. */
3027                         if (appData.debugMode)
3028                           fprintf(debugFP, "%d ", option);
3029                         TelnetRequest(TN_WONT, option);
3030                         break;
3031                     }
3032                     break;
3033                   case TN_DONT:
3034                     if (appData.debugMode)
3035                       fprintf(debugFP, "\n<DONT ");
3036                     switch (option = (unsigned char) buf[++i]) {
3037                       default:
3038                         if (appData.debugMode)
3039                           fprintf(debugFP, "%d ", option);
3040                         /* Whatever this is, we are already not doing
3041                            it, because we never agree to do anything
3042                            non-default, so according to the protocol
3043                            rules, we don't reply. */
3044                         break;
3045                     }
3046                     break;
3047                   case TN_IAC:
3048                     if (appData.debugMode)
3049                       fprintf(debugFP, "\n<IAC ");
3050                     /* Doubled IAC; pass it through */
3051                     i--;
3052                     break;
3053                   default:
3054                     if (appData.debugMode)
3055                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3056                     /* Drop all other telnet commands on the floor */
3057                     break;
3058                 }
3059                 if (oldi > next_out)
3060                   SendToPlayer(&buf[next_out], oldi - next_out);
3061                 if (++i > next_out)
3062                   next_out = i;
3063                 continue;
3064             }
3065
3066             /* OK, this at least will *usually* work */
3067             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3068                 loggedOn = TRUE;
3069             }
3070
3071             if (loggedOn && !intfSet) {
3072                 if (ics_type == ICS_ICC) {
3073                   snprintf(str, MSG_SIZ,
3074                           "/set-quietly interface %s\n/set-quietly style 12\n",
3075                           programVersion);
3076                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3077                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3078                 } else if (ics_type == ICS_CHESSNET) {
3079                   snprintf(str, MSG_SIZ, "/style 12\n");
3080                 } else {
3081                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3082                   strcat(str, programVersion);
3083                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3084                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3085                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3086 #ifdef WIN32
3087                   strcat(str, "$iset nohighlight 1\n");
3088 #endif
3089                   strcat(str, "$iset lock 1\n$style 12\n");
3090                 }
3091                 SendToICS(str);
3092                 NotifyFrontendLogin();
3093                 intfSet = TRUE;
3094             }
3095
3096             if (started == STARTED_COMMENT) {
3097                 /* Accumulate characters in comment */
3098                 parse[parse_pos++] = buf[i];
3099                 if (buf[i] == '\n') {
3100                     parse[parse_pos] = NULLCHAR;
3101                     if(chattingPartner>=0) {
3102                         char mess[MSG_SIZ];
3103                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3104                         OutputChatMessage(chattingPartner, mess);
3105                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3106                             int p;
3107                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3108                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3109                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3110                                 OutputChatMessage(p, mess);
3111                                 break;
3112                             }
3113                         }
3114                         chattingPartner = -1;
3115                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3116                         collective = 0;
3117                     } else
3118                     if(!suppressKibitz) // [HGM] kibitz
3119                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3120                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3121                         int nrDigit = 0, nrAlph = 0, j;
3122                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3123                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3124                         parse[parse_pos] = NULLCHAR;
3125                         // try to be smart: if it does not look like search info, it should go to
3126                         // ICS interaction window after all, not to engine-output window.
3127                         for(j=0; j<parse_pos; j++) { // count letters and digits
3128                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3129                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3130                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3131                         }
3132                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3133                             int depth=0; float score;
3134                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3135                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3136                                 pvInfoList[forwardMostMove-1].depth = depth;
3137                                 pvInfoList[forwardMostMove-1].score = 100*score;
3138                             }
3139                             OutputKibitz(suppressKibitz, parse);
3140                         } else {
3141                             char tmp[MSG_SIZ];
3142                             if(gameMode == IcsObserving) // restore original ICS messages
3143                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3144                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3145                             else
3146                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3147                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3148                             SendToPlayer(tmp, strlen(tmp));
3149                         }
3150                         next_out = i+1; // [HGM] suppress printing in ICS window
3151                     }
3152                     started = STARTED_NONE;
3153                 } else {
3154                     /* Don't match patterns against characters in comment */
3155                     i++;
3156                     continue;
3157                 }
3158             }
3159             if (started == STARTED_CHATTER) {
3160                 if (buf[i] != '\n') {
3161                     /* Don't match patterns against characters in chatter */
3162                     i++;
3163                     continue;
3164                 }
3165                 started = STARTED_NONE;
3166                 if(suppressKibitz) next_out = i+1;
3167             }
3168
3169             /* Kludge to deal with rcmd protocol */
3170             if (firstTime && looking_at(buf, &i, "\001*")) {
3171                 DisplayFatalError(&buf[1], 0, 1);
3172                 continue;
3173             } else {
3174                 firstTime = FALSE;
3175             }
3176
3177             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3178                 ics_type = ICS_ICC;
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, "freechess.org")) {
3185                 ics_type = ICS_FICS;
3186                 ics_prefix = "$";
3187                 if (appData.debugMode)
3188                   fprintf(debugFP, "ics_type %d\n", ics_type);
3189                 continue;
3190             }
3191             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3192                 ics_type = ICS_CHESSNET;
3193                 ics_prefix = "/";
3194                 if (appData.debugMode)
3195                   fprintf(debugFP, "ics_type %d\n", ics_type);
3196                 continue;
3197             }
3198
3199             if (!loggedOn &&
3200                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3201                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3202                  looking_at(buf, &i, "will be \"*\""))) {
3203               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3204               continue;
3205             }
3206
3207             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3208               char buf[MSG_SIZ];
3209               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3210               DisplayIcsInteractionTitle(buf);
3211               have_set_title = TRUE;
3212             }
3213
3214             /* skip finger notes */
3215             if (started == STARTED_NONE &&
3216                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3217                  (buf[i] == '1' && buf[i+1] == '0')) &&
3218                 buf[i+2] == ':' && buf[i+3] == ' ') {
3219               started = STARTED_CHATTER;
3220               i += 3;
3221               continue;
3222             }
3223
3224             oldi = i;
3225             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3226             if(appData.seekGraph) {
3227                 if(soughtPending && MatchSoughtLine(buf+i)) {
3228                     i = strstr(buf+i, "rated") - buf;
3229                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3230                     next_out = leftover_start = i;
3231                     started = STARTED_CHATTER;
3232                     suppressKibitz = TRUE;
3233                     continue;
3234                 }
3235                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3236                         && looking_at(buf, &i, "* ads displayed")) {
3237                     soughtPending = FALSE;
3238                     seekGraphUp = TRUE;
3239                     DrawSeekGraph();
3240                     continue;
3241                 }
3242                 if(appData.autoRefresh) {
3243                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3244                         int s = (ics_type == ICS_ICC); // ICC format differs
3245                         if(seekGraphUp)
3246                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3247                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3248                         looking_at(buf, &i, "*% "); // eat prompt
3249                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3250                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3251                         next_out = i; // suppress
3252                         continue;
3253                     }
3254                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3255                         char *p = star_match[0];
3256                         while(*p) {
3257                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3258                             while(*p && *p++ != ' '); // next
3259                         }
3260                         looking_at(buf, &i, "*% "); // eat prompt
3261                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3262                         next_out = i;
3263                         continue;
3264                     }
3265                 }
3266             }
3267
3268             /* skip formula vars */
3269             if (started == STARTED_NONE &&
3270                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3271               started = STARTED_CHATTER;
3272               i += 3;
3273               continue;
3274             }
3275
3276             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3277             if (appData.autoKibitz && started == STARTED_NONE &&
3278                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3279                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3280                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3281                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3282                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3283                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3284                         suppressKibitz = TRUE;
3285                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3286                         next_out = i;
3287                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3288                                 && (gameMode == IcsPlayingWhite)) ||
3289                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3290                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3291                             started = STARTED_CHATTER; // own kibitz we simply discard
3292                         else {
3293                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3294                             parse_pos = 0; parse[0] = NULLCHAR;
3295                             savingComment = TRUE;
3296                             suppressKibitz = gameMode != IcsObserving ? 2 :
3297                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3298                         }
3299                         continue;
3300                 } else
3301                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3302                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3303                          && atoi(star_match[0])) {
3304                     // suppress the acknowledgements of our own autoKibitz
3305                     char *p;
3306                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3307                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3308                     SendToPlayer(star_match[0], strlen(star_match[0]));
3309                     if(looking_at(buf, &i, "*% ")) // eat prompt
3310                         suppressKibitz = FALSE;
3311                     next_out = i;
3312                     continue;
3313                 }
3314             } // [HGM] kibitz: end of patch
3315
3316             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3317
3318             // [HGM] chat: intercept tells by users for which we have an open chat window
3319             channel = -1;
3320             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3321                                            looking_at(buf, &i, "* whispers:") ||
3322                                            looking_at(buf, &i, "* kibitzes:") ||
3323                                            looking_at(buf, &i, "* shouts:") ||
3324                                            looking_at(buf, &i, "* c-shouts:") ||
3325                                            looking_at(buf, &i, "--> * ") ||
3326                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3327                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3328                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3329                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3330                 int p;
3331                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3332                 chattingPartner = -1; collective = 0;
3333
3334                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3335                 for(p=0; p<MAX_CHAT; p++) {
3336                     collective = 1;
3337                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3338                     talker[0] = '['; strcat(talker, "] ");
3339                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3340                     chattingPartner = p; break;
3341                     }
3342                 } else
3343                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3344                 for(p=0; p<MAX_CHAT; p++) {
3345                     collective = 1;
3346                     if(!strcmp("kibitzes", chatPartner[p])) {
3347                         talker[0] = '['; strcat(talker, "] ");
3348                         chattingPartner = p; break;
3349                     }
3350                 } else
3351                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3352                 for(p=0; p<MAX_CHAT; p++) {
3353                     collective = 1;
3354                     if(!strcmp("whispers", chatPartner[p])) {
3355                         talker[0] = '['; strcat(talker, "] ");
3356                         chattingPartner = p; break;
3357                     }
3358                 } else
3359                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3360                   if(buf[i-8] == '-' && buf[i-3] == 't')
3361                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3362                     collective = 1;
3363                     if(!strcmp("c-shouts", chatPartner[p])) {
3364                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3365                         chattingPartner = p; break;
3366                     }
3367                   }
3368                   if(chattingPartner < 0)
3369                   for(p=0; p<MAX_CHAT; p++) {
3370                     collective = 1;
3371                     if(!strcmp("shouts", chatPartner[p])) {
3372                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3373                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3374                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3375                         chattingPartner = p; break;
3376                     }
3377                   }
3378                 }
3379                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3380                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3381                     talker[0] = 0;
3382                     Colorize(ColorTell, FALSE);
3383                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3384                     collective |= 2;
3385                     chattingPartner = p; break;
3386                 }
3387                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3388                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3389                     started = STARTED_COMMENT;
3390                     parse_pos = 0; parse[0] = NULLCHAR;
3391                     savingComment = 3 + chattingPartner; // counts as TRUE
3392                     if(collective == 3) i = oldi; else {
3393                         suppressKibitz = TRUE;
3394                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3395                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3396                         continue;
3397                     }
3398                 }
3399             } // [HGM] chat: end of patch
3400
3401           backup = i;
3402             if (appData.zippyTalk || appData.zippyPlay) {
3403                 /* [DM] Backup address for color zippy lines */
3404 #if ZIPPY
3405                if (loggedOn == TRUE)
3406                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3407                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3408                        ;
3409 #endif
3410             } // [DM] 'else { ' deleted
3411                 if (
3412                     /* Regular tells and says */
3413                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3414                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3415                     looking_at(buf, &i, "* says: ") ||
3416                     /* Don't color "message" or "messages" output */
3417                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3418                     looking_at(buf, &i, "*. * at *:*: ") ||
3419                     looking_at(buf, &i, "--* (*:*): ") ||
3420                     /* Message notifications (same color as tells) */
3421                     looking_at(buf, &i, "* has left a message ") ||
3422                     looking_at(buf, &i, "* just sent you a message:\n") ||
3423                     /* Whispers and kibitzes */
3424                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3425                     looking_at(buf, &i, "* kibitzes: ") ||
3426                     /* Channel tells */
3427                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3428
3429                   if (tkind == 1 && strchr(star_match[0], ':')) {
3430                       /* Avoid "tells you:" spoofs in channels */
3431                      tkind = 3;
3432                   }
3433                   if (star_match[0][0] == NULLCHAR ||
3434                       strchr(star_match[0], ' ') ||
3435                       (tkind == 3 && strchr(star_match[1], ' '))) {
3436                     /* Reject bogus matches */
3437                     i = oldi;
3438                   } else {
3439                     if (appData.colorize) {
3440                       if (oldi > next_out) {
3441                         SendToPlayer(&buf[next_out], oldi - next_out);
3442                         next_out = oldi;
3443                       }
3444                       switch (tkind) {
3445                       case 1:
3446                         Colorize(ColorTell, FALSE);
3447                         curColor = ColorTell;
3448                         break;
3449                       case 2:
3450                         Colorize(ColorKibitz, FALSE);
3451                         curColor = ColorKibitz;
3452                         break;
3453                       case 3:
3454                         p = strrchr(star_match[1], '(');
3455                         if (p == NULL) {
3456                           p = star_match[1];
3457                         } else {
3458                           p++;
3459                         }
3460                         if (atoi(p) == 1) {
3461                           Colorize(ColorChannel1, FALSE);
3462                           curColor = ColorChannel1;
3463                         } else {
3464                           Colorize(ColorChannel, FALSE);
3465                           curColor = ColorChannel;
3466                         }
3467                         break;
3468                       case 5:
3469                         curColor = ColorNormal;
3470                         break;
3471                       }
3472                     }
3473                     if (started == STARTED_NONE && appData.autoComment &&
3474                         (gameMode == IcsObserving ||
3475                          gameMode == IcsPlayingWhite ||
3476                          gameMode == IcsPlayingBlack)) {
3477                       parse_pos = i - oldi;
3478                       memcpy(parse, &buf[oldi], parse_pos);
3479                       parse[parse_pos] = NULLCHAR;
3480                       started = STARTED_COMMENT;
3481                       savingComment = TRUE;
3482                     } else if(collective != 3) {
3483                       started = STARTED_CHATTER;
3484                       savingComment = FALSE;
3485                     }
3486                     loggedOn = TRUE;
3487                     continue;
3488                   }
3489                 }
3490
3491                 if (looking_at(buf, &i, "* s-shouts: ") ||
3492                     looking_at(buf, &i, "* c-shouts: ")) {
3493                     if (appData.colorize) {
3494                         if (oldi > next_out) {
3495                             SendToPlayer(&buf[next_out], oldi - next_out);
3496                             next_out = oldi;
3497                         }
3498                         Colorize(ColorSShout, FALSE);
3499                         curColor = ColorSShout;
3500                     }
3501                     loggedOn = TRUE;
3502                     started = STARTED_CHATTER;
3503                     continue;
3504                 }
3505
3506                 if (looking_at(buf, &i, "--->")) {
3507                     loggedOn = TRUE;
3508                     continue;
3509                 }
3510
3511                 if (looking_at(buf, &i, "* shouts: ") ||
3512                     looking_at(buf, &i, "--> ")) {
3513                     if (appData.colorize) {
3514                         if (oldi > next_out) {
3515                             SendToPlayer(&buf[next_out], oldi - next_out);
3516                             next_out = oldi;
3517                         }
3518                         Colorize(ColorShout, FALSE);
3519                         curColor = ColorShout;
3520                     }
3521                     loggedOn = TRUE;
3522                     started = STARTED_CHATTER;
3523                     continue;
3524                 }
3525
3526                 if (looking_at( buf, &i, "Challenge:")) {
3527                     if (appData.colorize) {
3528                         if (oldi > next_out) {
3529                             SendToPlayer(&buf[next_out], oldi - next_out);
3530                             next_out = oldi;
3531                         }
3532                         Colorize(ColorChallenge, FALSE);
3533                         curColor = ColorChallenge;
3534                     }
3535                     loggedOn = TRUE;
3536                     continue;
3537                 }
3538
3539                 if (looking_at(buf, &i, "* offers you") ||
3540                     looking_at(buf, &i, "* offers to be") ||
3541                     looking_at(buf, &i, "* would like to") ||
3542                     looking_at(buf, &i, "* requests to") ||
3543                     looking_at(buf, &i, "Your opponent offers") ||
3544                     looking_at(buf, &i, "Your opponent requests")) {
3545
3546                     if (appData.colorize) {
3547                         if (oldi > next_out) {
3548                             SendToPlayer(&buf[next_out], oldi - next_out);
3549                             next_out = oldi;
3550                         }
3551                         Colorize(ColorRequest, FALSE);
3552                         curColor = ColorRequest;
3553                     }
3554                     continue;
3555                 }
3556
3557                 if (looking_at(buf, &i, "* (*) seeking")) {
3558                     if (appData.colorize) {
3559                         if (oldi > next_out) {
3560                             SendToPlayer(&buf[next_out], oldi - next_out);
3561                             next_out = oldi;
3562                         }
3563                         Colorize(ColorSeek, FALSE);
3564                         curColor = ColorSeek;
3565                     }
3566                     continue;
3567             }
3568
3569           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3570
3571             if (looking_at(buf, &i, "\\   ")) {
3572                 if (prevColor != ColorNormal) {
3573                     if (oldi > next_out) {
3574                         SendToPlayer(&buf[next_out], oldi - next_out);
3575                         next_out = oldi;
3576                     }
3577                     Colorize(prevColor, TRUE);
3578                     curColor = prevColor;
3579                 }
3580                 if (savingComment) {
3581                     parse_pos = i - oldi;
3582                     memcpy(parse, &buf[oldi], parse_pos);
3583                     parse[parse_pos] = NULLCHAR;
3584                     started = STARTED_COMMENT;
3585                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3586                         chattingPartner = savingComment - 3; // kludge to remember the box
3587                 } else {
3588                     started = STARTED_CHATTER;
3589                 }
3590                 continue;
3591             }
3592
3593             if (looking_at(buf, &i, "Black Strength :") ||
3594                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3595                 looking_at(buf, &i, "<10>") ||
3596                 looking_at(buf, &i, "#@#")) {
3597                 /* Wrong board style */
3598                 loggedOn = TRUE;
3599                 SendToICS(ics_prefix);
3600                 SendToICS("set style 12\n");
3601                 SendToICS(ics_prefix);
3602                 SendToICS("refresh\n");
3603                 continue;
3604             }
3605
3606             if (looking_at(buf, &i, "login:")) {
3607               if (!have_sent_ICS_logon) {
3608                 if(ICSInitScript())
3609                   have_sent_ICS_logon = 1;
3610                 else // no init script was found
3611                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3612               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3613                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3614               }
3615                 continue;
3616             }
3617
3618             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3619                 (looking_at(buf, &i, "\n<12> ") ||
3620                  looking_at(buf, &i, "<12> "))) {
3621                 loggedOn = TRUE;
3622                 if (oldi > next_out) {
3623                     SendToPlayer(&buf[next_out], oldi - next_out);
3624                 }
3625                 next_out = i;
3626                 started = STARTED_BOARD;
3627                 parse_pos = 0;
3628                 continue;
3629             }
3630
3631             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3632                 looking_at(buf, &i, "<b1> ")) {
3633                 if (oldi > next_out) {
3634                     SendToPlayer(&buf[next_out], oldi - next_out);
3635                 }
3636                 next_out = i;
3637                 started = STARTED_HOLDINGS;
3638                 parse_pos = 0;
3639                 continue;
3640             }
3641
3642             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3643                 loggedOn = TRUE;
3644                 /* Header for a move list -- first line */
3645
3646                 switch (ics_getting_history) {
3647                   case H_FALSE:
3648                     switch (gameMode) {
3649                       case IcsIdle:
3650                       case BeginningOfGame:
3651                         /* User typed "moves" or "oldmoves" while we
3652                            were idle.  Pretend we asked for these
3653                            moves and soak them up so user can step
3654                            through them and/or save them.
3655                            */
3656                         Reset(FALSE, TRUE);
3657                         gameMode = IcsObserving;
3658                         ModeHighlight();
3659                         ics_gamenum = -1;
3660                         ics_getting_history = H_GOT_UNREQ_HEADER;
3661                         break;
3662                       case EditGame: /*?*/
3663                       case EditPosition: /*?*/
3664                         /* Should above feature work in these modes too? */
3665                         /* For now it doesn't */
3666                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3667                         break;
3668                       default:
3669                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3670                         break;
3671                     }
3672                     break;
3673                   case H_REQUESTED:
3674                     /* Is this the right one? */
3675                     if (gameInfo.white && gameInfo.black &&
3676                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3677                         strcmp(gameInfo.black, star_match[2]) == 0) {
3678                         /* All is well */
3679                         ics_getting_history = H_GOT_REQ_HEADER;
3680                     }
3681                     break;
3682                   case H_GOT_REQ_HEADER:
3683                   case H_GOT_UNREQ_HEADER:
3684                   case H_GOT_UNWANTED_HEADER:
3685                   case H_GETTING_MOVES:
3686                     /* Should not happen */
3687                     DisplayError(_("Error gathering move list: two headers"), 0);
3688                     ics_getting_history = H_FALSE;
3689                     break;
3690                 }
3691
3692                 /* Save player ratings into gameInfo if needed */
3693                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3694                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3695                     (gameInfo.whiteRating == -1 ||
3696                      gameInfo.blackRating == -1)) {
3697
3698                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3699                     gameInfo.blackRating = string_to_rating(star_match[3]);
3700                     if (appData.debugMode)
3701                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3702                               gameInfo.whiteRating, gameInfo.blackRating);
3703                 }
3704                 continue;
3705             }
3706
3707             if (looking_at(buf, &i,
3708               "* * match, initial time: * minute*, increment: * second")) {
3709                 /* Header for a move list -- second line */
3710                 /* Initial board will follow if this is a wild game */
3711                 if (gameInfo.event != NULL) free(gameInfo.event);
3712                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3713                 gameInfo.event = StrSave(str);
3714                 /* [HGM] we switched variant. Translate boards if needed. */
3715                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3716                 continue;
3717             }
3718
3719             if (looking_at(buf, &i, "Move  ")) {
3720                 /* Beginning of a move list */
3721                 switch (ics_getting_history) {
3722                   case H_FALSE:
3723                     /* Normally should not happen */
3724                     /* Maybe user hit reset while we were parsing */
3725                     break;
3726                   case H_REQUESTED:
3727                     /* Happens if we are ignoring a move list that is not
3728                      * the one we just requested.  Common if the user
3729                      * tries to observe two games without turning off
3730                      * getMoveList */
3731                     break;
3732                   case H_GETTING_MOVES:
3733                     /* Should not happen */
3734                     DisplayError(_("Error gathering move list: nested"), 0);
3735                     ics_getting_history = H_FALSE;
3736                     break;
3737                   case H_GOT_REQ_HEADER:
3738                     ics_getting_history = H_GETTING_MOVES;
3739                     started = STARTED_MOVES;
3740                     parse_pos = 0;
3741                     if (oldi > next_out) {
3742                         SendToPlayer(&buf[next_out], oldi - next_out);
3743                     }
3744                     break;
3745                   case H_GOT_UNREQ_HEADER:
3746                     ics_getting_history = H_GETTING_MOVES;
3747                     started = STARTED_MOVES_NOHIDE;
3748                     parse_pos = 0;
3749                     break;
3750                   case H_GOT_UNWANTED_HEADER:
3751                     ics_getting_history = H_FALSE;
3752                     break;
3753                 }
3754                 continue;
3755             }
3756
3757             if (looking_at(buf, &i, "% ") ||
3758                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3759                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3760                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3761                     soughtPending = FALSE;
3762                     seekGraphUp = TRUE;
3763                     DrawSeekGraph();
3764                 }
3765                 if(suppressKibitz) next_out = i;
3766                 savingComment = FALSE;
3767                 suppressKibitz = 0;
3768                 switch (started) {
3769                   case STARTED_MOVES:
3770                   case STARTED_MOVES_NOHIDE:
3771                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3772                     parse[parse_pos + i - oldi] = NULLCHAR;
3773                     ParseGameHistory(parse);
3774 #if ZIPPY
3775                     if (appData.zippyPlay && first.initDone) {
3776                         FeedMovesToProgram(&first, forwardMostMove);
3777                         if (gameMode == IcsPlayingWhite) {
3778                             if (WhiteOnMove(forwardMostMove)) {
3779                                 if (first.sendTime) {
3780                                   if (first.useColors) {
3781                                     SendToProgram("black\n", &first);
3782                                   }
3783                                   SendTimeRemaining(&first, TRUE);
3784                                 }
3785                                 if (first.useColors) {
3786                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3787                                 }
3788                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3789                                 first.maybeThinking = TRUE;
3790                             } else {
3791                                 if (first.usePlayother) {
3792                                   if (first.sendTime) {
3793                                     SendTimeRemaining(&first, TRUE);
3794                                   }
3795                                   SendToProgram("playother\n", &first);
3796                                   firstMove = FALSE;
3797                                 } else {
3798                                   firstMove = TRUE;
3799                                 }
3800                             }
3801                         } else if (gameMode == IcsPlayingBlack) {
3802                             if (!WhiteOnMove(forwardMostMove)) {
3803                                 if (first.sendTime) {
3804                                   if (first.useColors) {
3805                                     SendToProgram("white\n", &first);
3806                                   }
3807                                   SendTimeRemaining(&first, FALSE);
3808                                 }
3809                                 if (first.useColors) {
3810                                   SendToProgram("black\n", &first);
3811                                 }
3812                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3813                                 first.maybeThinking = TRUE;
3814                             } else {
3815                                 if (first.usePlayother) {
3816                                   if (first.sendTime) {
3817                                     SendTimeRemaining(&first, FALSE);
3818                                   }
3819                                   SendToProgram("playother\n", &first);
3820                                   firstMove = FALSE;
3821                                 } else {
3822                                   firstMove = TRUE;
3823                                 }
3824                             }
3825                         }
3826                     }
3827 #endif
3828                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3829                         /* Moves came from oldmoves or moves command
3830                            while we weren't doing anything else.
3831                            */
3832                         currentMove = forwardMostMove;
3833                         ClearHighlights();/*!!could figure this out*/
3834                         flipView = appData.flipView;
3835                         DrawPosition(TRUE, boards[currentMove]);
3836                         DisplayBothClocks();
3837                         snprintf(str, MSG_SIZ, "%s %s %s",
3838                                 gameInfo.white, _("vs."),  gameInfo.black);
3839                         DisplayTitle(str);
3840                         gameMode = IcsIdle;
3841                     } else {
3842                         /* Moves were history of an active game */
3843                         if (gameInfo.resultDetails != NULL) {
3844                             free(gameInfo.resultDetails);
3845                             gameInfo.resultDetails = NULL;
3846                         }
3847                     }
3848                     HistorySet(parseList, backwardMostMove,
3849                                forwardMostMove, currentMove-1);
3850                     DisplayMove(currentMove - 1);
3851                     if (started == STARTED_MOVES) next_out = i;
3852                     started = STARTED_NONE;
3853                     ics_getting_history = H_FALSE;
3854                     break;
3855
3856                   case STARTED_OBSERVE:
3857                     started = STARTED_NONE;
3858                     SendToICS(ics_prefix);
3859                     SendToICS("refresh\n");
3860                     break;
3861
3862                   default:
3863                     break;
3864                 }
3865                 if(bookHit) { // [HGM] book: simulate book reply
3866                     static char bookMove[MSG_SIZ]; // a bit generous?
3867
3868                     programStats.nodes = programStats.depth = programStats.time =
3869                     programStats.score = programStats.got_only_move = 0;
3870                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3871
3872                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3873                     strcat(bookMove, bookHit);
3874                     HandleMachineMove(bookMove, &first);
3875                 }
3876                 continue;
3877             }
3878
3879             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3880                  started == STARTED_HOLDINGS ||
3881                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3882                 /* Accumulate characters in move list or board */
3883                 parse[parse_pos++] = buf[i];
3884             }
3885
3886             /* Start of game messages.  Mostly we detect start of game
3887                when the first board image arrives.  On some versions
3888                of the ICS, though, we need to do a "refresh" after starting
3889                to observe in order to get the current board right away. */
3890             if (looking_at(buf, &i, "Adding game * to observation list")) {
3891                 started = STARTED_OBSERVE;
3892                 continue;
3893             }
3894
3895             /* Handle auto-observe */
3896             if (appData.autoObserve &&
3897                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3898                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3899                 char *player;
3900                 /* Choose the player that was highlighted, if any. */
3901                 if (star_match[0][0] == '\033' ||
3902                     star_match[1][0] != '\033') {
3903                     player = star_match[0];
3904                 } else {
3905                     player = star_match[2];
3906                 }
3907                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3908                         ics_prefix, StripHighlightAndTitle(player));
3909                 SendToICS(str);
3910
3911                 /* Save ratings from notify string */
3912                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3913                 player1Rating = string_to_rating(star_match[1]);
3914                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3915                 player2Rating = string_to_rating(star_match[3]);
3916
3917                 if (appData.debugMode)
3918                   fprintf(debugFP,
3919                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3920                           player1Name, player1Rating,
3921                           player2Name, player2Rating);
3922
3923                 continue;
3924             }
3925
3926             /* Deal with automatic examine mode after a game,
3927                and with IcsObserving -> IcsExamining transition */
3928             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3929                 looking_at(buf, &i, "has made you an examiner of game *")) {
3930
3931                 int gamenum = atoi(star_match[0]);
3932                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3933                     gamenum == ics_gamenum) {
3934                     /* We were already playing or observing this game;
3935                        no need to refetch history */
3936                     gameMode = IcsExamining;
3937                     if (pausing) {
3938                         pauseExamForwardMostMove = forwardMostMove;
3939                     } else if (currentMove < forwardMostMove) {
3940                         ForwardInner(forwardMostMove);
3941                     }
3942                 } else {
3943                     /* I don't think this case really can happen */
3944                     SendToICS(ics_prefix);
3945                     SendToICS("refresh\n");
3946                 }
3947                 continue;
3948             }
3949
3950             /* Error messages */
3951 //          if (ics_user_moved) {
3952             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3953                 if (looking_at(buf, &i, "Illegal move") ||
3954                     looking_at(buf, &i, "Not a legal move") ||
3955                     looking_at(buf, &i, "Your king is in check") ||
3956                     looking_at(buf, &i, "It isn't your turn") ||
3957                     looking_at(buf, &i, "It is not your move")) {
3958                     /* Illegal move */
3959                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3960                         currentMove = forwardMostMove-1;
3961                         DisplayMove(currentMove - 1); /* before DMError */
3962                         DrawPosition(FALSE, boards[currentMove]);
3963                         SwitchClocks(forwardMostMove-1); // [HGM] race
3964                         DisplayBothClocks();
3965                     }
3966                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3967                     ics_user_moved = 0;
3968                     continue;
3969                 }
3970             }
3971
3972             if (looking_at(buf, &i, "still have time") ||
3973                 looking_at(buf, &i, "not out of time") ||
3974                 looking_at(buf, &i, "either player is out of time") ||
3975                 looking_at(buf, &i, "has timeseal; checking")) {
3976                 /* We must have called his flag a little too soon */
3977                 whiteFlag = blackFlag = FALSE;
3978                 continue;
3979             }
3980
3981             if (looking_at(buf, &i, "added * seconds to") ||
3982                 looking_at(buf, &i, "seconds were added to")) {
3983                 /* Update the clocks */
3984                 SendToICS(ics_prefix);
3985                 SendToICS("refresh\n");
3986                 continue;
3987             }
3988
3989             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3990                 ics_clock_paused = TRUE;
3991                 StopClocks();
3992                 continue;
3993             }
3994
3995             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3996                 ics_clock_paused = FALSE;
3997                 StartClocks();
3998                 continue;
3999             }
4000
4001             /* Grab player ratings from the Creating: message.
4002                Note we have to check for the special case when
4003                the ICS inserts things like [white] or [black]. */
4004             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
4005                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
4006                 /* star_matches:
4007                    0    player 1 name (not necessarily white)
4008                    1    player 1 rating
4009                    2    empty, white, or black (IGNORED)
4010                    3    player 2 name (not necessarily black)
4011                    4    player 2 rating
4012
4013                    The names/ratings are sorted out when the game
4014                    actually starts (below).
4015                 */
4016                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4017                 player1Rating = string_to_rating(star_match[1]);
4018                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4019                 player2Rating = string_to_rating(star_match[4]);
4020
4021                 if (appData.debugMode)
4022                   fprintf(debugFP,
4023                           "Ratings from 'Creating:' %s %d, %s %d\n",
4024                           player1Name, player1Rating,
4025                           player2Name, player2Rating);
4026
4027                 continue;
4028             }
4029
4030             /* Improved generic start/end-of-game messages */
4031             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4032                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4033                 /* If tkind == 0: */
4034                 /* star_match[0] is the game number */
4035                 /*           [1] is the white player's name */
4036                 /*           [2] is the black player's name */
4037                 /* For end-of-game: */
4038                 /*           [3] is the reason for the game end */
4039                 /*           [4] is a PGN end game-token, preceded by " " */
4040                 /* For start-of-game: */
4041                 /*           [3] begins with "Creating" or "Continuing" */
4042                 /*           [4] is " *" or empty (don't care). */
4043                 int gamenum = atoi(star_match[0]);
4044                 char *whitename, *blackname, *why, *endtoken;
4045                 ChessMove endtype = EndOfFile;
4046
4047                 if (tkind == 0) {
4048                   whitename = star_match[1];
4049                   blackname = star_match[2];
4050                   why = star_match[3];
4051                   endtoken = star_match[4];
4052                 } else {
4053                   whitename = star_match[1];
4054                   blackname = star_match[3];
4055                   why = star_match[5];
4056                   endtoken = star_match[6];
4057                 }
4058
4059                 /* Game start messages */
4060                 if (strncmp(why, "Creating ", 9) == 0 ||
4061                     strncmp(why, "Continuing ", 11) == 0) {
4062                     gs_gamenum = gamenum;
4063                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4064                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4065                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4066 #if ZIPPY
4067                     if (appData.zippyPlay) {
4068                         ZippyGameStart(whitename, blackname);
4069                     }
4070 #endif /*ZIPPY*/
4071                     partnerBoardValid = FALSE; // [HGM] bughouse
4072                     continue;
4073                 }
4074
4075                 /* Game end messages */
4076                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4077                     ics_gamenum != gamenum) {
4078                     continue;
4079                 }
4080                 while (endtoken[0] == ' ') endtoken++;
4081                 switch (endtoken[0]) {
4082                   case '*':
4083                   default:
4084                     endtype = GameUnfinished;
4085                     break;
4086                   case '0':
4087                     endtype = BlackWins;
4088                     break;
4089                   case '1':
4090                     if (endtoken[1] == '/')
4091                       endtype = GameIsDrawn;
4092                     else
4093                       endtype = WhiteWins;
4094                     break;
4095                 }
4096                 GameEnds(endtype, why, GE_ICS);
4097 #if ZIPPY
4098                 if (appData.zippyPlay && first.initDone) {
4099                     ZippyGameEnd(endtype, why);
4100                     if (first.pr == NoProc) {
4101                       /* Start the next process early so that we'll
4102                          be ready for the next challenge */
4103                       StartChessProgram(&first);
4104                     }
4105                     /* Send "new" early, in case this command takes
4106                        a long time to finish, so that we'll be ready
4107                        for the next challenge. */
4108                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4109                     Reset(TRUE, TRUE);
4110                 }
4111 #endif /*ZIPPY*/
4112                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4113                 continue;
4114             }
4115
4116             if (looking_at(buf, &i, "Removing game * from observation") ||
4117                 looking_at(buf, &i, "no longer observing game *") ||
4118                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4119                 if (gameMode == IcsObserving &&
4120                     atoi(star_match[0]) == ics_gamenum)
4121                   {
4122                       /* icsEngineAnalyze */
4123                       if (appData.icsEngineAnalyze) {
4124                             ExitAnalyzeMode();
4125                             ModeHighlight();
4126                       }
4127                       StopClocks();
4128                       gameMode = IcsIdle;
4129                       ics_gamenum = -1;
4130                       ics_user_moved = FALSE;
4131                   }
4132                 continue;
4133             }
4134
4135             if (looking_at(buf, &i, "no longer examining game *")) {
4136                 if (gameMode == IcsExamining &&
4137                     atoi(star_match[0]) == ics_gamenum)
4138                   {
4139                       gameMode = IcsIdle;
4140                       ics_gamenum = -1;
4141                       ics_user_moved = FALSE;
4142                   }
4143                 continue;
4144             }
4145
4146             /* Advance leftover_start past any newlines we find,
4147                so only partial lines can get reparsed */
4148             if (looking_at(buf, &i, "\n")) {
4149                 prevColor = curColor;
4150                 if (curColor != ColorNormal) {
4151                     if (oldi > next_out) {
4152                         SendToPlayer(&buf[next_out], oldi - next_out);
4153                         next_out = oldi;
4154                     }
4155                     Colorize(ColorNormal, FALSE);
4156                     curColor = ColorNormal;
4157                 }
4158                 if (started == STARTED_BOARD) {
4159                     started = STARTED_NONE;
4160                     parse[parse_pos] = NULLCHAR;
4161                     ParseBoard12(parse);
4162                     ics_user_moved = 0;
4163
4164                     /* Send premove here */
4165                     if (appData.premove) {
4166                       char str[MSG_SIZ];
4167                       if (currentMove == 0 &&
4168                           gameMode == IcsPlayingWhite &&
4169                           appData.premoveWhite) {
4170                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4171                         if (appData.debugMode)
4172                           fprintf(debugFP, "Sending premove:\n");
4173                         SendToICS(str);
4174                       } else if (currentMove == 1 &&
4175                                  gameMode == IcsPlayingBlack &&
4176                                  appData.premoveBlack) {
4177                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4178                         if (appData.debugMode)
4179                           fprintf(debugFP, "Sending premove:\n");
4180                         SendToICS(str);
4181                       } else if (gotPremove) {
4182                         int oldFMM = forwardMostMove;
4183                         gotPremove = 0;
4184                         ClearPremoveHighlights();
4185                         if (appData.debugMode)
4186                           fprintf(debugFP, "Sending premove:\n");
4187                           UserMoveEvent(premoveFromX, premoveFromY,
4188                                         premoveToX, premoveToY,
4189                                         premovePromoChar);
4190                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4191                           if(moveList[oldFMM-1][1] != '@')
4192                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4193                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4194                           else // (drop)
4195                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4196                         }
4197                       }
4198                     }
4199
4200                     /* Usually suppress following prompt */
4201                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4202                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4203                         if (looking_at(buf, &i, "*% ")) {
4204                             savingComment = FALSE;
4205                             suppressKibitz = 0;
4206                         }
4207                     }
4208                     next_out = i;
4209                 } else if (started == STARTED_HOLDINGS) {
4210                     int gamenum;
4211                     char new_piece[MSG_SIZ];
4212                     started = STARTED_NONE;
4213                     parse[parse_pos] = NULLCHAR;
4214                     if (appData.debugMode)
4215                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4216                                                         parse, currentMove);
4217                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4218                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4219                         new_piece[0] = NULLCHAR;
4220                         sscanf(parse, "game %d white [%s black [%s <- %s",
4221                                &gamenum, white_holding, black_holding,
4222                                new_piece);
4223                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4224                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4225                         if (gameInfo.variant == VariantNormal) {
4226                           /* [HGM] We seem to switch variant during a game!
4227                            * Presumably no holdings were displayed, so we have
4228                            * to move the position two files to the right to
4229                            * create room for them!
4230                            */
4231                           VariantClass newVariant;
4232                           switch(gameInfo.boardWidth) { // base guess on board width
4233                                 case 9:  newVariant = VariantShogi; break;
4234                                 case 10: newVariant = VariantGreat; break;
4235                                 default: newVariant = VariantCrazyhouse;
4236                                      if(strchr(white_holding, 'E') || strchr(black_holding, 'E') || 
4237                                         strchr(white_holding, 'H') || strchr(black_holding, 'H')   )
4238                                          newVariant = VariantSChess;
4239                           }
4240                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4241                           /* Get a move list just to see the header, which
4242                              will tell us whether this is really bug or zh */
4243                           if (ics_getting_history == H_FALSE) {
4244                             ics_getting_history = H_REQUESTED;
4245                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4246                             SendToICS(str);
4247                           }
4248                         }
4249                         /* [HGM] copy holdings to board holdings area */
4250                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4251                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4252                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4253 #if ZIPPY
4254                         if (appData.zippyPlay && first.initDone) {
4255                             ZippyHoldings(white_holding, black_holding,
4256                                           new_piece);
4257                         }
4258 #endif /*ZIPPY*/
4259                         if (tinyLayout || smallLayout) {
4260                             char wh[16], bh[16];
4261                             PackHolding(wh, white_holding);
4262                             PackHolding(bh, black_holding);
4263                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4264                                     gameInfo.white, gameInfo.black);
4265                         } else {
4266                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4267                                     gameInfo.white, white_holding, _("vs."),
4268                                     gameInfo.black, black_holding);
4269                         }
4270                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4271                         DrawPosition(FALSE, boards[currentMove]);
4272                         DisplayTitle(str);
4273                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4274                         sscanf(parse, "game %d white [%s black [%s <- %s",
4275                                &gamenum, white_holding, black_holding,
4276                                new_piece);
4277                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4278                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4279                         /* [HGM] copy holdings to partner-board holdings area */
4280                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4281                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4282                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4283                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4284                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4285                       }
4286                     }
4287                     /* Suppress following prompt */
4288                     if (looking_at(buf, &i, "*% ")) {
4289                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4290                         savingComment = FALSE;
4291                         suppressKibitz = 0;
4292                     }
4293                     next_out = i;
4294                 }
4295                 continue;
4296             }
4297
4298             i++;                /* skip unparsed character and loop back */
4299         }
4300
4301         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4302 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4303 //          SendToPlayer(&buf[next_out], i - next_out);
4304             started != STARTED_HOLDINGS && leftover_start > next_out) {
4305             SendToPlayer(&buf[next_out], leftover_start - next_out);
4306             next_out = i;
4307         }
4308
4309         leftover_len = buf_len - leftover_start;
4310         /* if buffer ends with something we couldn't parse,
4311            reparse it after appending the next read */
4312
4313     } else if (count == 0) {
4314         RemoveInputSource(isr);
4315         DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4316     } else {
4317         DisplayFatalError(_("Error reading from ICS"), error, 1);
4318     }
4319 }
4320
4321
4322 /* Board style 12 looks like this:
4323
4324    <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
4325
4326  * The "<12> " is stripped before it gets to this routine.  The two
4327  * trailing 0's (flip state and clock ticking) are later addition, and
4328  * some chess servers may not have them, or may have only the first.
4329  * Additional trailing fields may be added in the future.
4330  */
4331
4332 #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"
4333
4334 #define RELATION_OBSERVING_PLAYED    0
4335 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4336 #define RELATION_PLAYING_MYMOVE      1
4337 #define RELATION_PLAYING_NOTMYMOVE  -1
4338 #define RELATION_EXAMINING           2
4339 #define RELATION_ISOLATED_BOARD     -3
4340 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4341
4342 void
4343 ParseBoard12 (char *string)
4344 {
4345 #if ZIPPY
4346     int i, takeback;
4347     char *bookHit = NULL; // [HGM] book
4348 #endif
4349     GameMode newGameMode;
4350     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4351     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4352     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4353     char to_play, board_chars[200];
4354     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4355     char black[32], white[32];
4356     Board board;
4357     int prevMove = currentMove;
4358     int ticking = 2;
4359     ChessMove moveType;
4360     int fromX, fromY, toX, toY;
4361     char promoChar;
4362     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4363     Boolean weird = FALSE, reqFlag = FALSE;
4364
4365     fromX = fromY = toX = toY = -1;
4366
4367     newGame = FALSE;
4368
4369     if (appData.debugMode)
4370       fprintf(debugFP, "Parsing board: %s\n", string);
4371
4372     move_str[0] = NULLCHAR;
4373     elapsed_time[0] = NULLCHAR;
4374     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4375         int  i = 0, j;
4376         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4377             if(string[i] == ' ') { ranks++; files = 0; }
4378             else files++;
4379             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4380             i++;
4381         }
4382         for(j = 0; j <i; j++) board_chars[j] = string[j];
4383         board_chars[i] = '\0';
4384         string += i + 1;
4385     }
4386     n = sscanf(string, PATTERN, &to_play, &double_push,
4387                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4388                &gamenum, white, black, &relation, &basetime, &increment,
4389                &white_stren, &black_stren, &white_time, &black_time,
4390                &moveNum, str, elapsed_time, move_str, &ics_flip,
4391                &ticking);
4392
4393     if (n < 21) {
4394         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4395         DisplayError(str, 0);
4396         return;
4397     }
4398
4399     /* Convert the move number to internal form */
4400     moveNum = (moveNum - 1) * 2;
4401     if (to_play == 'B') moveNum++;
4402     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4403       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4404                         0, 1);
4405       return;
4406     }
4407
4408     switch (relation) {
4409       case RELATION_OBSERVING_PLAYED:
4410       case RELATION_OBSERVING_STATIC:
4411         if (gamenum == -1) {
4412             /* Old ICC buglet */
4413             relation = RELATION_OBSERVING_STATIC;
4414         }
4415         newGameMode = IcsObserving;
4416         break;
4417       case RELATION_PLAYING_MYMOVE:
4418       case RELATION_PLAYING_NOTMYMOVE:
4419         newGameMode =
4420           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4421             IcsPlayingWhite : IcsPlayingBlack;
4422         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4423         break;
4424       case RELATION_EXAMINING:
4425         newGameMode = IcsExamining;
4426         break;
4427       case RELATION_ISOLATED_BOARD:
4428       default:
4429         /* Just display this board.  If user was doing something else,
4430            we will forget about it until the next board comes. */
4431         newGameMode = IcsIdle;
4432         break;
4433       case RELATION_STARTING_POSITION:
4434         newGameMode = gameMode;
4435         break;
4436     }
4437
4438     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4439         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4440          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4441       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4442       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4443       static int lastBgGame = -1;
4444       char *toSqr;
4445       for (k = 0; k < ranks; k++) {
4446         for (j = 0; j < files; j++)
4447           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4448         if(gameInfo.holdingsWidth > 1) {
4449              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4450              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4451         }
4452       }
4453       CopyBoard(partnerBoard, board);
4454       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4455         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4456         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4457       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4458       if(toSqr = strchr(str, '-')) {
4459         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4460         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4461       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4462       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4463       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4464       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4465       if(twoBoards) {
4466           DisplayWhiteClock(white_time*fac, to_play == 'W');
4467           DisplayBlackClock(black_time*fac, to_play != 'W');
4468           activePartner = to_play;
4469           if(gamenum != lastBgGame) {
4470               char buf[MSG_SIZ];
4471               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4472               DisplayTitle(buf);
4473           }
4474           lastBgGame = gamenum;
4475           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4476                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4477       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4478                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4479       if(!twoBoards) DisplayMessage(partnerStatus, "");
4480         partnerBoardValid = TRUE;
4481       return;
4482     }
4483
4484     if(appData.dualBoard && appData.bgObserve) {
4485         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4486             SendToICS(ics_prefix), SendToICS("pobserve\n");
4487         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4488             char buf[MSG_SIZ];
4489             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4490             SendToICS(buf);
4491         }
4492     }
4493
4494     /* Modify behavior for initial board display on move listing
4495        of wild games.
4496        */
4497     switch (ics_getting_history) {
4498       case H_FALSE:
4499       case H_REQUESTED:
4500         break;
4501       case H_GOT_REQ_HEADER:
4502       case H_GOT_UNREQ_HEADER:
4503         /* This is the initial position of the current game */
4504         gamenum = ics_gamenum;
4505         moveNum = 0;            /* old ICS bug workaround */
4506         if (to_play == 'B') {
4507           startedFromSetupPosition = TRUE;
4508           blackPlaysFirst = TRUE;
4509           moveNum = 1;
4510           if (forwardMostMove == 0) forwardMostMove = 1;
4511           if (backwardMostMove == 0) backwardMostMove = 1;
4512           if (currentMove == 0) currentMove = 1;
4513         }
4514         newGameMode = gameMode;
4515         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4516         break;
4517       case H_GOT_UNWANTED_HEADER:
4518         /* This is an initial board that we don't want */
4519         return;
4520       case H_GETTING_MOVES:
4521         /* Should not happen */
4522         DisplayError(_("Error gathering move list: extra board"), 0);
4523         ics_getting_history = H_FALSE;
4524         return;
4525     }
4526
4527    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4528                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4529                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4530      /* [HGM] We seem to have switched variant unexpectedly
4531       * Try to guess new variant from board size
4532       */
4533           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4534           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4535           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4536           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4537           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4538           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4539           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4540           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4541           /* Get a move list just to see the header, which
4542              will tell us whether this is really bug or zh */
4543           if (ics_getting_history == H_FALSE) {
4544             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4545             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4546             SendToICS(str);
4547           }
4548     }
4549
4550     /* Take action if this is the first board of a new game, or of a
4551        different game than is currently being displayed.  */
4552     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4553         relation == RELATION_ISOLATED_BOARD) {
4554
4555         /* Forget the old game and get the history (if any) of the new one */
4556         if (gameMode != BeginningOfGame) {
4557           Reset(TRUE, TRUE);
4558         }
4559         newGame = TRUE;
4560         if (appData.autoRaiseBoard) BoardToTop();
4561         prevMove = -3;
4562         if (gamenum == -1) {
4563             newGameMode = IcsIdle;
4564         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4565                    appData.getMoveList && !reqFlag) {
4566             /* Need to get game history */
4567             ics_getting_history = H_REQUESTED;
4568             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4569             SendToICS(str);
4570         }
4571
4572         /* Initially flip the board to have black on the bottom if playing
4573            black or if the ICS flip flag is set, but let the user change
4574            it with the Flip View button. */
4575         flipView = appData.autoFlipView ?
4576           (newGameMode == IcsPlayingBlack) || ics_flip :
4577           appData.flipView;
4578
4579         /* Done with values from previous mode; copy in new ones */
4580         gameMode = newGameMode;
4581         ModeHighlight();
4582         ics_gamenum = gamenum;
4583         if (gamenum == gs_gamenum) {
4584             int klen = strlen(gs_kind);
4585             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4586             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4587             gameInfo.event = StrSave(str);
4588         } else {
4589             gameInfo.event = StrSave("ICS game");
4590         }
4591         gameInfo.site = StrSave(appData.icsHost);
4592         gameInfo.date = PGNDate();
4593         gameInfo.round = StrSave("-");
4594         gameInfo.white = StrSave(white);
4595         gameInfo.black = StrSave(black);
4596         timeControl = basetime * 60 * 1000;
4597         timeControl_2 = 0;
4598         timeIncrement = increment * 1000;
4599         movesPerSession = 0;
4600         gameInfo.timeControl = TimeControlTagValue();
4601         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4602   if (appData.debugMode) {
4603     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4604     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4605     setbuf(debugFP, NULL);
4606   }
4607
4608         gameInfo.outOfBook = NULL;
4609
4610         /* Do we have the ratings? */
4611         if (strcmp(player1Name, white) == 0 &&
4612             strcmp(player2Name, black) == 0) {
4613             if (appData.debugMode)
4614               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4615                       player1Rating, player2Rating);
4616             gameInfo.whiteRating = player1Rating;
4617             gameInfo.blackRating = player2Rating;
4618         } else if (strcmp(player2Name, white) == 0 &&
4619                    strcmp(player1Name, black) == 0) {
4620             if (appData.debugMode)
4621               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4622                       player2Rating, player1Rating);
4623             gameInfo.whiteRating = player2Rating;
4624             gameInfo.blackRating = player1Rating;
4625         }
4626         player1Name[0] = player2Name[0] = NULLCHAR;
4627
4628         /* Silence shouts if requested */
4629         if (appData.quietPlay &&
4630             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4631             SendToICS(ics_prefix);
4632             SendToICS("set shout 0\n");
4633         }
4634     }
4635
4636     /* Deal with midgame name changes */
4637     if (!newGame) {
4638         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4639             if (gameInfo.white) free(gameInfo.white);
4640             gameInfo.white = StrSave(white);
4641         }
4642         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4643             if (gameInfo.black) free(gameInfo.black);
4644             gameInfo.black = StrSave(black);
4645         }
4646     }
4647
4648     /* Throw away game result if anything actually changes in examine mode */
4649     if (gameMode == IcsExamining && !newGame) {
4650         gameInfo.result = GameUnfinished;
4651         if (gameInfo.resultDetails != NULL) {
4652             free(gameInfo.resultDetails);
4653             gameInfo.resultDetails = NULL;
4654         }
4655     }
4656
4657     /* In pausing && IcsExamining mode, we ignore boards coming
4658        in if they are in a different variation than we are. */
4659     if (pauseExamInvalid) return;
4660     if (pausing && gameMode == IcsExamining) {
4661         if (moveNum <= pauseExamForwardMostMove) {
4662             pauseExamInvalid = TRUE;
4663             forwardMostMove = pauseExamForwardMostMove;
4664             return;
4665         }
4666     }
4667
4668   if (appData.debugMode) {
4669     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4670   }
4671     /* Parse the board */
4672     for (k = 0; k < ranks; k++) {
4673       for (j = 0; j < files; j++)
4674         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4675       if(gameInfo.holdingsWidth > 1) {
4676            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4677            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4678       }
4679     }
4680     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4681       board[5][BOARD_RGHT+1] = WhiteAngel;
4682       board[6][BOARD_RGHT+1] = WhiteMarshall;
4683       board[1][0] = BlackMarshall;
4684       board[2][0] = BlackAngel;
4685       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4686     }
4687     CopyBoard(boards[moveNum], board);
4688     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4689     if (moveNum == 0) {
4690         startedFromSetupPosition =
4691           !CompareBoards(board, initialPosition);
4692         if(startedFromSetupPosition)
4693             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4694     }
4695
4696     /* [HGM] Set castling rights. Take the outermost Rooks,
4697        to make it also work for FRC opening positions. Note that board12
4698        is really defective for later FRC positions, as it has no way to
4699        indicate which Rook can castle if they are on the same side of King.
4700        For the initial position we grant rights to the outermost Rooks,
4701        and remember thos rights, and we then copy them on positions
4702        later in an FRC game. This means WB might not recognize castlings with
4703        Rooks that have moved back to their original position as illegal,
4704        but in ICS mode that is not its job anyway.
4705     */
4706     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4707     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4708
4709         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4710             if(board[0][i] == WhiteRook) j = i;
4711         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4712         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4713             if(board[0][i] == WhiteRook) j = i;
4714         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4715         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4716             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4717         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4718         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4719             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4720         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4721
4722         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4723         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4724         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4725             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4726         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4727             if(board[BOARD_HEIGHT-1][k] == bKing)
4728                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4729         if(gameInfo.variant == VariantTwoKings) {
4730             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4731             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4732             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4733         }
4734     } else { int r;
4735         r = boards[moveNum][CASTLING][0] = initialRights[0];
4736         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4737         r = boards[moveNum][CASTLING][1] = initialRights[1];
4738         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4739         r = boards[moveNum][CASTLING][3] = initialRights[3];
4740         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4741         r = boards[moveNum][CASTLING][4] = initialRights[4];
4742         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4743         /* wildcastle kludge: always assume King has rights */
4744         r = boards[moveNum][CASTLING][2] = initialRights[2];
4745         r = boards[moveNum][CASTLING][5] = initialRights[5];
4746     }
4747     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4748     boards[moveNum][EP_STATUS] = EP_NONE;
4749     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4750     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4751     if(double_push !=  -1) {
4752         int dir = WhiteOnMove(moveNum) ? 1 : -1, last = BOARD_HEIGHT-1;
4753         boards[moveNum][EP_FILE] = // also set new e.p. variables
4754         boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4755         boards[moveNum][EP_RANK] = (last + 3*dir)/2;
4756         boards[moveNum][LAST_TO] = 128*(last + dir) + boards[moveNum][EP_FILE];
4757     } else boards[moveNum][EP_FILE] = boards[moveNum][EP_RANK] = 100;
4758
4759
4760     if (ics_getting_history == H_GOT_REQ_HEADER ||
4761         ics_getting_history == H_GOT_UNREQ_HEADER) {
4762         /* This was an initial position from a move list, not
4763            the current position */
4764         return;
4765     }
4766
4767     /* Update currentMove and known move number limits */
4768     newMove = newGame || moveNum > forwardMostMove;
4769
4770     if (newGame) {
4771         forwardMostMove = backwardMostMove = currentMove = moveNum;
4772         if (gameMode == IcsExamining && moveNum == 0) {
4773           /* Workaround for ICS limitation: we are not told the wild
4774              type when starting to examine a game.  But if we ask for
4775              the move list, the move list header will tell us */
4776             ics_getting_history = H_REQUESTED;
4777             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4778             SendToICS(str);
4779         }
4780     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4781                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4782 #if ZIPPY
4783         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4784         /* [HGM] applied this also to an engine that is silently watching        */
4785         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4786             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4787             gameInfo.variant == currentlyInitializedVariant) {
4788           takeback = forwardMostMove - moveNum;
4789           for (i = 0; i < takeback; i++) {
4790             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4791             SendToProgram("undo\n", &first);
4792           }
4793         }
4794 #endif
4795
4796         forwardMostMove = moveNum;
4797         if (!pausing || currentMove > forwardMostMove)
4798           currentMove = forwardMostMove;
4799     } else {
4800         /* New part of history that is not contiguous with old part */
4801         if (pausing && gameMode == IcsExamining) {
4802             pauseExamInvalid = TRUE;
4803             forwardMostMove = pauseExamForwardMostMove;
4804             return;
4805         }
4806         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4807 #if ZIPPY
4808             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4809                 // [HGM] when we will receive the move list we now request, it will be
4810                 // fed to the engine from the first move on. So if the engine is not
4811                 // in the initial position now, bring it there.
4812                 InitChessProgram(&first, 0);
4813             }
4814 #endif
4815             ics_getting_history = H_REQUESTED;
4816             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4817             SendToICS(str);
4818         }
4819         forwardMostMove = backwardMostMove = currentMove = moveNum;
4820     }
4821
4822     /* Update the clocks */
4823     if (strchr(elapsed_time, '.')) {
4824       /* Time is in ms */
4825       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4826       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4827     } else {
4828       /* Time is in seconds */
4829       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4830       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4831     }
4832
4833
4834 #if ZIPPY
4835     if (appData.zippyPlay && newGame &&
4836         gameMode != IcsObserving && gameMode != IcsIdle &&
4837         gameMode != IcsExamining)
4838       ZippyFirstBoard(moveNum, basetime, increment);
4839 #endif
4840
4841     /* Put the move on the move list, first converting
4842        to canonical algebraic form. */
4843     if (moveNum > 0) {
4844   if (appData.debugMode) {
4845     int f = forwardMostMove;
4846     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4847             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4848             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4849     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4850     fprintf(debugFP, "moveNum = %d\n", moveNum);
4851     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4852     setbuf(debugFP, NULL);
4853   }
4854         if (moveNum <= backwardMostMove) {
4855             /* We don't know what the board looked like before
4856                this move.  Punt. */
4857           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4858             strcat(parseList[moveNum - 1], " ");
4859             strcat(parseList[moveNum - 1], elapsed_time);
4860             moveList[moveNum - 1][0] = NULLCHAR;
4861         } else if (strcmp(move_str, "none") == 0) {
4862             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4863             /* Again, we don't know what the board looked like;
4864                this is really the start of the game. */
4865             parseList[moveNum - 1][0] = NULLCHAR;
4866             moveList[moveNum - 1][0] = NULLCHAR;
4867             backwardMostMove = moveNum;
4868             startedFromSetupPosition = TRUE;
4869             fromX = fromY = toX = toY = -1;
4870         } else {
4871           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4872           //                 So we parse the long-algebraic move string in stead of the SAN move
4873           int valid; char buf[MSG_SIZ], *prom;
4874
4875           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4876                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4877           // str looks something like "Q/a1-a2"; kill the slash
4878           if(str[1] == '/')
4879             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4880           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4881           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4882                 strcat(buf, prom); // long move lacks promo specification!
4883           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4884                 if(appData.debugMode)
4885                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4886                 safeStrCpy(move_str, buf, MSG_SIZ);
4887           }
4888           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4889                                 &fromX, &fromY, &toX, &toY, &promoChar)
4890                || ParseOneMove(buf, moveNum - 1, &moveType,
4891                                 &fromX, &fromY, &toX, &toY, &promoChar);
4892           // end of long SAN patch
4893           if (valid) {
4894             (void) CoordsToAlgebraic(boards[moveNum - 1],
4895                                      PosFlags(moveNum - 1),
4896                                      fromY, fromX, toY, toX, promoChar,
4897                                      parseList[moveNum-1]);
4898             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4899               case MT_NONE:
4900               case MT_STALEMATE:
4901               default:
4902                 break;
4903               case MT_CHECK:
4904                 if(!IS_SHOGI(gameInfo.variant))
4905                     strcat(parseList[moveNum - 1], "+");
4906                 break;
4907               case MT_CHECKMATE:
4908               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4909                 strcat(parseList[moveNum - 1], "#");
4910                 break;
4911             }
4912             strcat(parseList[moveNum - 1], " ");
4913             strcat(parseList[moveNum - 1], elapsed_time);
4914             /* currentMoveString is set as a side-effect of ParseOneMove */
4915             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4916             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4917             strcat(moveList[moveNum - 1], "\n");
4918
4919             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4920                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4921               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4922                 ChessSquare old, new = boards[moveNum][k][j];
4923                   if(new == EmptySquare || fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4924                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4925                   if(old == new) continue;
4926                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4927                   else if(new == WhiteWazir || new == BlackWazir) {
4928                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4929                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4930                       else boards[moveNum][k][j] = old; // preserve type of Gold
4931                   } else if(old == WhitePawn || old == BlackPawn) // Pawn promotions (but not e.p.capture!)
4932                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4933               }
4934           } else {
4935             /* Move from ICS was illegal!?  Punt. */
4936             if (appData.debugMode) {
4937               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4938               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4939             }
4940             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4941             strcat(parseList[moveNum - 1], " ");
4942             strcat(parseList[moveNum - 1], elapsed_time);
4943             moveList[moveNum - 1][0] = NULLCHAR;
4944             fromX = fromY = toX = toY = -1;
4945           }
4946         }
4947   if (appData.debugMode) {
4948     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4949     setbuf(debugFP, NULL);
4950   }
4951
4952 #if ZIPPY
4953         /* Send move to chess program (BEFORE animating it). */
4954         if (appData.zippyPlay && !newGame && newMove &&
4955            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4956
4957             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4958                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4959                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4960                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4961                             move_str);
4962                     DisplayError(str, 0);
4963                 } else {
4964                     if (first.sendTime) {
4965                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4966                     }
4967                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4968                     if (firstMove && !bookHit) {
4969                         firstMove = FALSE;
4970                         if (first.useColors) {
4971                           SendToProgram(gameMode == IcsPlayingWhite ?
4972                                         "white\ngo\n" :
4973                                         "black\ngo\n", &first);
4974                         } else {
4975                           SendToProgram("go\n", &first);
4976                         }
4977                         first.maybeThinking = TRUE;
4978                     }
4979                 }
4980             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4981               if (moveList[moveNum - 1][0] == NULLCHAR) {
4982                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4983                 DisplayError(str, 0);
4984               } else {
4985                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4986                 SendMoveToProgram(moveNum - 1, &first);
4987               }
4988             }
4989         }
4990 #endif
4991     }
4992
4993     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4994         /* If move comes from a remote source, animate it.  If it
4995            isn't remote, it will have already been animated. */
4996         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4997             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4998         }
4999         if (!pausing && appData.highlightLastMove) {
5000             SetHighlights(fromX, fromY, toX, toY);
5001         }
5002     }
5003
5004     /* Start the clocks */
5005     whiteFlag = blackFlag = FALSE;
5006     appData.clockMode = !(basetime == 0 && increment == 0);
5007     if (ticking == 0) {
5008       ics_clock_paused = TRUE;
5009       StopClocks();
5010     } else if (ticking == 1) {
5011       ics_clock_paused = FALSE;
5012     }
5013     if (gameMode == IcsIdle ||
5014         relation == RELATION_OBSERVING_STATIC ||
5015         relation == RELATION_EXAMINING ||
5016         ics_clock_paused)
5017       DisplayBothClocks();
5018     else
5019       StartClocks();
5020
5021     /* Display opponents and material strengths */
5022     if (gameInfo.variant != VariantBughouse &&
5023         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5024         if (tinyLayout || smallLayout) {
5025             if(gameInfo.variant == VariantNormal)
5026               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5027                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5028                     basetime, increment);
5029             else
5030               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5031                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5032                     basetime, increment, (int) gameInfo.variant);
5033         } else {
5034             if(gameInfo.variant == VariantNormal)
5035               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5036                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5037                     basetime, increment);
5038             else
5039               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5040                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5041                     basetime, increment, VariantName(gameInfo.variant));
5042         }
5043         DisplayTitle(str);
5044   if (appData.debugMode) {
5045     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5046   }
5047     }
5048
5049
5050     /* Display the board */
5051     if (!pausing && !appData.noGUI) {
5052
5053       if (appData.premove)
5054           if (!gotPremove ||
5055              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5056              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5057               ClearPremoveHighlights();
5058
5059       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5060         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5061       DrawPosition(j, boards[currentMove]);
5062
5063       DisplayMove(moveNum - 1);
5064       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5065             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5066               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5067         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5068       }
5069     }
5070
5071     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5072 #if ZIPPY
5073     if(bookHit) { // [HGM] book: simulate book reply
5074         static char bookMove[MSG_SIZ]; // a bit generous?
5075
5076         programStats.nodes = programStats.depth = programStats.time =
5077         programStats.score = programStats.got_only_move = 0;
5078         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5079
5080         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5081         strcat(bookMove, bookHit);
5082         HandleMachineMove(bookMove, &first);
5083     }
5084 #endif
5085 }
5086
5087 void
5088 GetMoveListEvent ()
5089 {
5090     char buf[MSG_SIZ];
5091     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5092         ics_getting_history = H_REQUESTED;
5093         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5094         SendToICS(buf);
5095     }
5096 }
5097
5098 void
5099 SendToBoth (char *msg)
5100 {   // to make it easy to keep two engines in step in dual analysis
5101     SendToProgram(msg, &first);
5102     if(second.analyzing) SendToProgram(msg, &second);
5103 }
5104
5105 void
5106 AnalysisPeriodicEvent (int force)
5107 {
5108     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5109          && !force) || !appData.periodicUpdates)
5110       return;
5111
5112     /* Send . command to Crafty to collect stats */
5113     SendToBoth(".\n");
5114
5115     /* Don't send another until we get a response (this makes
5116        us stop sending to old Crafty's which don't understand
5117        the "." command (sending illegal cmds resets node count & time,
5118        which looks bad)) */
5119     programStats.ok_to_send = 0;
5120 }
5121
5122 void
5123 ics_update_width (int new_width)
5124 {
5125         ics_printf("set width %d\n", new_width);
5126 }
5127
5128 void
5129 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5130 {
5131     char buf[MSG_SIZ];
5132
5133     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5134         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5135             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5136             SendToProgram(buf, cps);
5137             return;
5138         }
5139         // null move in variant where engine does not understand it (for analysis purposes)
5140         SendBoard(cps, moveNum + 1); // send position after move in stead.
5141         return;
5142     }
5143     if (cps->useUsermove) {
5144       SendToProgram("usermove ", cps);
5145     }
5146     if (cps->useSAN) {
5147       char *space;
5148       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5149         int len = space - parseList[moveNum];
5150         memcpy(buf, parseList[moveNum], len);
5151         buf[len++] = '\n';
5152         buf[len] = NULLCHAR;
5153       } else {
5154         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5155       }
5156       SendToProgram(buf, cps);
5157     } else {
5158       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5159         AlphaRank(moveList[moveNum], 4);
5160         SendToProgram(moveList[moveNum], cps);
5161         AlphaRank(moveList[moveNum], 4); // and back
5162       } else
5163       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5164        * the engine. It would be nice to have a better way to identify castle
5165        * moves here. */
5166       if(appData.fischerCastling && cps->useOOCastle) {
5167         int fromX = moveList[moveNum][0] - AAA;
5168         int fromY = moveList[moveNum][1] - ONE;
5169         int toX = moveList[moveNum][2] - AAA;
5170         int toY = moveList[moveNum][3] - ONE;
5171         if((boards[moveNum][fromY][fromX] == WhiteKing
5172             && boards[moveNum][toY][toX] == WhiteRook)
5173            || (boards[moveNum][fromY][fromX] == BlackKing
5174                && boards[moveNum][toY][toX] == BlackRook)) {
5175           if(toX > fromX) SendToProgram("O-O\n", cps);
5176           else SendToProgram("O-O-O\n", cps);
5177         }
5178         else SendToProgram(moveList[moveNum], cps);
5179       } else
5180       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5181         char *m = moveList[moveNum];
5182         static char c[2];
5183         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5184         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
5185           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5186                                                m[2], m[3] - '0',
5187                                                m[5], m[6] - '0',
5188                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5189         else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5190           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5191           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
5192                                                m[7], m[8] - '0',
5193                                                m[7], m[8] - '0',
5194                                                m[5], m[6] - '0',
5195                                                m[5], m[6] - '0',
5196                                                m[2], m[3] - '0', c);
5197         } else
5198           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5199                                                m[5], m[6] - '0',
5200                                                m[5], m[6] - '0',
5201                                                m[2], m[3] - '0', c);
5202           SendToProgram(buf, cps);
5203       } else
5204       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5205         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5206           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5207           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5208                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5209         } else
5210           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5211                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5212         SendToProgram(buf, cps);
5213       }
5214       else SendToProgram(moveList[moveNum], cps);
5215       /* End of additions by Tord */
5216     }
5217
5218     /* [HGM] setting up the opening has brought engine in force mode! */
5219     /*       Send 'go' if we are in a mode where machine should play. */
5220     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5221         (gameMode == TwoMachinesPlay   ||
5222 #if ZIPPY
5223          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5224 #endif
5225          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5226         SendToProgram("go\n", cps);
5227   if (appData.debugMode) {
5228     fprintf(debugFP, "(extra)\n");
5229   }
5230     }
5231     setboardSpoiledMachineBlack = 0;
5232 }
5233
5234 void
5235 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5236 {
5237     char user_move[MSG_SIZ];
5238     char suffix[4];
5239
5240     if(gameInfo.variant == VariantSChess && promoChar) {
5241         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5242         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5243     } else suffix[0] = NULLCHAR;
5244
5245     switch (moveType) {
5246       default:
5247         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5248                 (int)moveType, fromX, fromY, toX, toY);
5249         DisplayError(user_move + strlen("say "), 0);
5250         break;
5251       case WhiteKingSideCastle:
5252       case BlackKingSideCastle:
5253       case WhiteQueenSideCastleWild:
5254       case BlackQueenSideCastleWild:
5255       /* PUSH Fabien */
5256       case WhiteHSideCastleFR:
5257       case BlackHSideCastleFR:
5258       /* POP Fabien */
5259         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5260         break;
5261       case WhiteQueenSideCastle:
5262       case BlackQueenSideCastle:
5263       case WhiteKingSideCastleWild:
5264       case BlackKingSideCastleWild:
5265       /* PUSH Fabien */
5266       case WhiteASideCastleFR:
5267       case BlackASideCastleFR:
5268       /* POP Fabien */
5269         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5270         break;
5271       case WhiteNonPromotion:
5272       case BlackNonPromotion:
5273         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5274         break;
5275       case WhitePromotion:
5276       case BlackPromotion:
5277         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5278            gameInfo.variant == VariantMakruk)
5279           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5280                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5281                 PieceToChar(WhiteFerz));
5282         else if(gameInfo.variant == VariantGreat)
5283           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5284                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5285                 PieceToChar(WhiteMan));
5286         else
5287           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5288                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5289                 promoChar);
5290         break;
5291       case WhiteDrop:
5292       case BlackDrop:
5293       drop:
5294         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5295                  ToUpper(PieceToChar((ChessSquare) fromX)),
5296                  AAA + toX, ONE + toY);
5297         break;
5298       case IllegalMove:  /* could be a variant we don't quite understand */
5299         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5300       case NormalMove:
5301       case WhiteCapturesEnPassant:
5302       case BlackCapturesEnPassant:
5303         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5304                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5305         break;
5306     }
5307     SendToICS(user_move);
5308     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5309         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5310 }
5311
5312 void
5313 UploadGameEvent ()
5314 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5315     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5316     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5317     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5318       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5319       return;
5320     }
5321     if(gameMode != IcsExamining) { // is this ever not the case?
5322         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5323
5324         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5325           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5326         } else { // on FICS we must first go to general examine mode
5327           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5328         }
5329         if(gameInfo.variant != VariantNormal) {
5330             // try figure out wild number, as xboard names are not always valid on ICS
5331             for(i=1; i<=36; i++) {
5332               snprintf(buf, MSG_SIZ, "wild/%d", i);
5333                 if(StringToVariant(buf) == gameInfo.variant) break;
5334             }
5335             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5336             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5337             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5338         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5339         SendToICS(ics_prefix);
5340         SendToICS(buf);
5341         if(startedFromSetupPosition || backwardMostMove != 0) {
5342           fen = PositionToFEN(backwardMostMove, NULL, 1);
5343           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5344             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5345             SendToICS(buf);
5346           } else { // FICS: everything has to set by separate bsetup commands
5347             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5348             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5349             SendToICS(buf);
5350             if(!WhiteOnMove(backwardMostMove)) {
5351                 SendToICS("bsetup tomove black\n");
5352             }
5353             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5354             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5355             SendToICS(buf);
5356             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5357             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5358             SendToICS(buf);
5359             i = boards[backwardMostMove][EP_STATUS];
5360             if(i >= 0) { // set e.p.
5361               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5362                 SendToICS(buf);
5363             }
5364             bsetup++;
5365           }
5366         }
5367       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5368             SendToICS("bsetup done\n"); // switch to normal examining.
5369     }
5370     for(i = backwardMostMove; i<last; i++) {
5371         char buf[20];
5372         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5373         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5374             int len = strlen(moveList[i]);
5375             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5376             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5377         }
5378         SendToICS(buf);
5379     }
5380     SendToICS(ics_prefix);
5381     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5382 }
5383
5384 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5385 int legNr = 1;
5386
5387 void
5388 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5389 {
5390     if (rf == DROP_RANK) {
5391       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5392       sprintf(move, "%c@%c%c\n",
5393                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5394     } else {
5395         if (promoChar == 'x' || promoChar == NULLCHAR) {
5396           sprintf(move, "%c%c%c%c\n",
5397                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5398           if(killX >= 0 && killY >= 0) {
5399             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5400             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5401           }
5402         } else {
5403             sprintf(move, "%c%c%c%c%c\n",
5404                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5405           if(killX >= 0 && killY >= 0) {
5406             sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5407             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5408           }
5409         }
5410     }
5411 }
5412
5413 void
5414 ProcessICSInitScript (FILE *f)
5415 {
5416     char buf[MSG_SIZ];
5417
5418     while (fgets(buf, MSG_SIZ, f)) {
5419         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5420     }
5421
5422     fclose(f);
5423 }
5424
5425
5426 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5427 int dragging;
5428 static ClickType lastClickType;
5429
5430 int
5431 PieceInString (char *s, ChessSquare piece)
5432 {
5433   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5434   while((p = strchr(s, ID))) {
5435     if(!suffix || p[1] == suffix) return TRUE;
5436     s = p;
5437   }
5438   return FALSE;
5439 }
5440
5441 int
5442 Partner (ChessSquare *p)
5443 { // change piece into promotion partner if one shogi-promotes to the other
5444   ChessSquare partner = promoPartner[*p];
5445   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5446   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5447   *p = partner;
5448   return 1;
5449 }
5450
5451 void
5452 Sweep (int step)
5453 {
5454     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5455     static int toggleFlag;
5456     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5457     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5458     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5459     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5460     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5461     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5462     do {
5463         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5464         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5465         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5466         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5467         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5468         if(!step) step = -1;
5469     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5470             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5471             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5472             promoSweep == pawn ||
5473             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5474             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5475     if(toX >= 0) {
5476         int victim = boards[currentMove][toY][toX];
5477         boards[currentMove][toY][toX] = promoSweep;
5478         DrawPosition(FALSE, boards[currentMove]);
5479         boards[currentMove][toY][toX] = victim;
5480     } else
5481     ChangeDragPiece(promoSweep);
5482 }
5483
5484 int
5485 PromoScroll (int x, int y)
5486 {
5487   int step = 0;
5488
5489   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5490   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5491   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5492   if(!step) return FALSE;
5493   lastX = x; lastY = y;
5494   if((promoSweep < BlackPawn) == flipView) step = -step;
5495   if(step > 0) selectFlag = 1;
5496   if(!selectFlag) Sweep(step);
5497   return FALSE;
5498 }
5499
5500 void
5501 NextPiece (int step)
5502 {
5503     ChessSquare piece = boards[currentMove][toY][toX];
5504     do {
5505         pieceSweep -= step;
5506         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5507         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5508         if(!step) step = -1;
5509     } while(PieceToChar(pieceSweep) == '.');
5510     boards[currentMove][toY][toX] = pieceSweep;
5511     DrawPosition(FALSE, boards[currentMove]);
5512     boards[currentMove][toY][toX] = piece;
5513 }
5514 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5515 void
5516 AlphaRank (char *move, int n)
5517 {
5518 //    char *p = move, c; int x, y;
5519
5520     if (appData.debugMode) {
5521         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5522     }
5523
5524     if(move[1]=='*' &&
5525        move[2]>='0' && move[2]<='9' &&
5526        move[3]>='a' && move[3]<='x'    ) {
5527         move[1] = '@';
5528         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5529         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5530     } else
5531     if(move[0]>='0' && move[0]<='9' &&
5532        move[1]>='a' && move[1]<='x' &&
5533        move[2]>='0' && move[2]<='9' &&
5534        move[3]>='a' && move[3]<='x'    ) {
5535         /* input move, Shogi -> normal */
5536         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5537         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5538         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5539         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5540     } else
5541     if(move[1]=='@' &&
5542        move[3]>='0' && move[3]<='9' &&
5543        move[2]>='a' && move[2]<='x'    ) {
5544         move[1] = '*';
5545         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5546         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5547     } else
5548     if(
5549        move[0]>='a' && move[0]<='x' &&
5550        move[3]>='0' && move[3]<='9' &&
5551        move[2]>='a' && move[2]<='x'    ) {
5552          /* output move, normal -> Shogi */
5553         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5554         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5555         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5556         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5557         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5558     }
5559     if (appData.debugMode) {
5560         fprintf(debugFP, "   out = '%s'\n", move);
5561     }
5562 }
5563
5564 char yy_textstr[8000];
5565
5566 /* Parser for moves from gnuchess, ICS, or user typein box */
5567 Boolean
5568 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5569 {
5570     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5571
5572     switch (*moveType) {
5573       case WhitePromotion:
5574       case BlackPromotion:
5575       case WhiteNonPromotion:
5576       case BlackNonPromotion:
5577       case NormalMove:
5578       case FirstLeg:
5579       case WhiteCapturesEnPassant:
5580       case BlackCapturesEnPassant:
5581       case WhiteKingSideCastle:
5582       case WhiteQueenSideCastle:
5583       case BlackKingSideCastle:
5584       case BlackQueenSideCastle:
5585       case WhiteKingSideCastleWild:
5586       case WhiteQueenSideCastleWild:
5587       case BlackKingSideCastleWild:
5588       case BlackQueenSideCastleWild:
5589       /* Code added by Tord: */
5590       case WhiteHSideCastleFR:
5591       case WhiteASideCastleFR:
5592       case BlackHSideCastleFR:
5593       case BlackASideCastleFR:
5594       /* End of code added by Tord */
5595       case IllegalMove:         /* bug or odd chess variant */
5596         if(currentMoveString[1] == '@') { // illegal drop
5597           *fromX = WhiteOnMove(moveNum) ?
5598             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5599             (int) CharToPiece(ToLower(currentMoveString[0]));
5600           goto drop;
5601         }
5602         *fromX = currentMoveString[0] - AAA;
5603         *fromY = currentMoveString[1] - ONE;
5604         *toX = currentMoveString[2] - AAA;
5605         *toY = currentMoveString[3] - ONE;
5606         *promoChar = currentMoveString[4];
5607         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5608         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5609             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5610     if (appData.debugMode) {
5611         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5612     }
5613             *fromX = *fromY = *toX = *toY = 0;
5614             return FALSE;
5615         }
5616         if (appData.testLegality) {
5617           return (*moveType != IllegalMove);
5618         } else {
5619           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5620                          // [HGM] lion: if this is a double move we are less critical
5621                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5622         }
5623
5624       case WhiteDrop:
5625       case BlackDrop:
5626         *fromX = *moveType == WhiteDrop ?
5627           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5628           (int) CharToPiece(ToLower(currentMoveString[0]));
5629       drop:
5630         *fromY = DROP_RANK;
5631         *toX = currentMoveString[2] - AAA;
5632         *toY = currentMoveString[3] - ONE;
5633         *promoChar = NULLCHAR;
5634         return TRUE;
5635
5636       case AmbiguousMove:
5637       case ImpossibleMove:
5638       case EndOfFile:
5639       case ElapsedTime:
5640       case Comment:
5641       case PGNTag:
5642       case NAG:
5643       case WhiteWins:
5644       case BlackWins:
5645       case GameIsDrawn:
5646       default:
5647     if (appData.debugMode) {
5648         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5649     }
5650         /* bug? */
5651         *fromX = *fromY = *toX = *toY = 0;
5652         *promoChar = NULLCHAR;
5653         return FALSE;
5654     }
5655 }
5656
5657 Boolean pushed = FALSE;
5658 char *lastParseAttempt;
5659
5660 void
5661 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5662 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5663   int fromX, fromY, toX, toY; char promoChar;
5664   ChessMove moveType;
5665   Boolean valid;
5666   int nr = 0;
5667
5668   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5669   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5670     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5671     pushed = TRUE;
5672   }
5673   endPV = forwardMostMove;
5674   do {
5675     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5676     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5677     lastParseAttempt = pv;
5678     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5679     if(!valid && nr == 0 &&
5680        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5681         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5682         // Hande case where played move is different from leading PV move
5683         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5684         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5685         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5686         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5687           endPV += 2; // if position different, keep this
5688           moveList[endPV-1][0] = fromX + AAA;
5689           moveList[endPV-1][1] = fromY + ONE;
5690           moveList[endPV-1][2] = toX + AAA;
5691           moveList[endPV-1][3] = toY + ONE;
5692           parseList[endPV-1][0] = NULLCHAR;
5693           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5694         }
5695       }
5696     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5697     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5698     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5699     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5700         valid++; // allow comments in PV
5701         continue;
5702     }
5703     nr++;
5704     if(endPV+1 > framePtr) break; // no space, truncate
5705     if(!valid) break;
5706     endPV++;
5707     CopyBoard(boards[endPV], boards[endPV-1]);
5708     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5709     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5710     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5711     CoordsToAlgebraic(boards[endPV - 1],
5712                              PosFlags(endPV - 1),
5713                              fromY, fromX, toY, toX, promoChar,
5714                              parseList[endPV - 1]);
5715   } while(valid);
5716   if(atEnd == 2) return; // used hidden, for PV conversion
5717   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5718   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5719   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5720                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5721   DrawPosition(TRUE, boards[currentMove]);
5722 }
5723
5724 int
5725 MultiPV (ChessProgramState *cps, int kind)
5726 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5727         int i;
5728         for(i=0; i<cps->nrOptions; i++) {
5729             char *s = cps->option[i].name;
5730             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5731             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5732                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5733         }
5734         return -1;
5735 }
5736
5737 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5738 static int multi, pv_margin;
5739 static ChessProgramState *activeCps;
5740
5741 Boolean
5742 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5743 {
5744         int startPV, lineStart, origIndex = index;
5745         char *p, buf2[MSG_SIZ];
5746         ChessProgramState *cps = (pane ? &second : &first);
5747
5748         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5749         lastX = x; lastY = y;
5750         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5751         lineStart = startPV = index;
5752         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5753         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5754         index = startPV;
5755         do{ while(buf[index] && buf[index] != '\n') index++;
5756         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5757         buf[index] = 0;
5758         if(lineStart == 0 && gameMode == AnalyzeMode) {
5759             int n = 0;
5760             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5761             if(n == 0) { // click not on "fewer" or "more"
5762                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5763                     pv_margin = cps->option[multi].value;
5764                     activeCps = cps; // non-null signals margin adjustment
5765                 }
5766             } else if((multi = MultiPV(cps, 1)) >= 0) {
5767                 n += cps->option[multi].value; if(n < 1) n = 1;
5768                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5769                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5770                 cps->option[multi].value = n;
5771                 *start = *end = 0;
5772                 return FALSE;
5773             }
5774         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5775                 ExcludeClick(origIndex - lineStart);
5776                 return FALSE;
5777         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5778                 Collapse(origIndex - lineStart);
5779                 return FALSE;
5780         }
5781         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5782         *start = startPV; *end = index-1;
5783         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5784         return TRUE;
5785 }
5786
5787 char *
5788 PvToSAN (char *pv)
5789 {
5790         static char buf[10*MSG_SIZ];
5791         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5792         *buf = NULLCHAR;
5793         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5794         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5795         for(i = forwardMostMove; i<endPV; i++){
5796             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5797             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5798             k += strlen(buf+k);
5799         }
5800         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5801         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5802         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5803         endPV = savedEnd;
5804         return buf;
5805 }
5806
5807 Boolean
5808 LoadPV (int x, int y)
5809 { // called on right mouse click to load PV
5810   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5811   lastX = x; lastY = y;
5812   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5813   extendGame = FALSE;
5814   return TRUE;
5815 }
5816
5817 void
5818 UnLoadPV ()
5819 {
5820   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5821   if(activeCps) {
5822     if(pv_margin != activeCps->option[multi].value) {
5823       char buf[MSG_SIZ];
5824       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5825       SendToProgram(buf, activeCps);
5826       activeCps->option[multi].value = pv_margin;
5827     }
5828     activeCps = NULL;
5829     return;
5830   }
5831   if(endPV < 0) return;
5832   if(appData.autoCopyPV) CopyFENToClipboard();
5833   endPV = -1;
5834   if(extendGame && currentMove > forwardMostMove) {
5835         Boolean saveAnimate = appData.animate;
5836         if(pushed) {
5837             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5838                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5839             } else storedGames--; // abandon shelved tail of original game
5840         }
5841         pushed = FALSE;
5842         forwardMostMove = currentMove;
5843         currentMove = oldFMM;
5844         appData.animate = FALSE;
5845         ToNrEvent(forwardMostMove);
5846         appData.animate = saveAnimate;
5847   }
5848   currentMove = forwardMostMove;
5849   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5850   ClearPremoveHighlights();
5851   DrawPosition(TRUE, boards[currentMove]);
5852 }
5853
5854 void
5855 MovePV (int x, int y, int h)
5856 { // step through PV based on mouse coordinates (called on mouse move)
5857   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5858
5859   if(activeCps) { // adjusting engine's multi-pv margin
5860     if(x > lastX) pv_margin++; else
5861     if(x < lastX) pv_margin -= (pv_margin > 0);
5862     if(x != lastX) {
5863       char buf[MSG_SIZ];
5864       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5865       DisplayMessage(buf, "");
5866     }
5867     lastX = x;
5868     return;
5869   }
5870   // we must somehow check if right button is still down (might be released off board!)
5871   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5872   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5873   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5874   if(!step) return;
5875   lastX = x; lastY = y;
5876
5877   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5878   if(endPV < 0) return;
5879   if(y < margin) step = 1; else
5880   if(y > h - margin) step = -1;
5881   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5882   currentMove += step;
5883   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5884   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5885                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5886   DrawPosition(FALSE, boards[currentMove]);
5887 }
5888
5889
5890 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5891 // All positions will have equal probability, but the current method will not provide a unique
5892 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5893 #define DARK 1
5894 #define LITE 2
5895 #define ANY 3
5896
5897 int squaresLeft[4];
5898 int piecesLeft[(int)BlackPawn];
5899 int seed, nrOfShuffles;
5900
5901 void
5902 GetPositionNumber ()
5903 {       // sets global variable seed
5904         int i;
5905
5906         seed = appData.defaultFrcPosition;
5907         if(seed < 0) { // randomize based on time for negative FRC position numbers
5908                 for(i=0; i<50; i++) seed += random();
5909                 seed = random() ^ random() >> 8 ^ random() << 8;
5910                 if(seed<0) seed = -seed;
5911         }
5912 }
5913
5914 int
5915 put (Board board, int pieceType, int rank, int n, int shade)
5916 // put the piece on the (n-1)-th empty squares of the given shade
5917 {
5918         int i;
5919
5920         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5921                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5922                         board[rank][i] = (ChessSquare) pieceType;
5923                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5924                         squaresLeft[ANY]--;
5925                         piecesLeft[pieceType]--;
5926                         return i;
5927                 }
5928         }
5929         return -1;
5930 }
5931
5932
5933 void
5934 AddOnePiece (Board board, int pieceType, int rank, int shade)
5935 // calculate where the next piece goes, (any empty square), and put it there
5936 {
5937         int i;
5938
5939         i = seed % squaresLeft[shade];
5940         nrOfShuffles *= squaresLeft[shade];
5941         seed /= squaresLeft[shade];
5942         put(board, pieceType, rank, i, shade);
5943 }
5944
5945 void
5946 AddTwoPieces (Board board, int pieceType, int rank)
5947 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5948 {
5949         int i, n=squaresLeft[ANY], j=n-1, k;
5950
5951         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5952         i = seed % k;  // pick one
5953         nrOfShuffles *= k;
5954         seed /= k;
5955         while(i >= j) i -= j--;
5956         j = n - 1 - j; i += j;
5957         put(board, pieceType, rank, j, ANY);
5958         put(board, pieceType, rank, i, ANY);
5959 }
5960
5961 void
5962 SetUpShuffle (Board board, int number)
5963 {
5964         int i, p, first=1;
5965
5966         GetPositionNumber(); nrOfShuffles = 1;
5967
5968         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5969         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5970         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5971
5972         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5973
5974         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5975             p = (int) board[0][i];
5976             if(p < (int) BlackPawn) piecesLeft[p] ++;
5977             board[0][i] = EmptySquare;
5978         }
5979
5980         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5981             // shuffles restricted to allow normal castling put KRR first
5982             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5983                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5984             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5985                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5986             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5987                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5988             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5989                 put(board, WhiteRook, 0, 0, ANY);
5990             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5991         }
5992
5993         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5994             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5995             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5996                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5997                 while(piecesLeft[p] >= 2) {
5998                     AddOnePiece(board, p, 0, LITE);
5999                     AddOnePiece(board, p, 0, DARK);
6000                 }
6001                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
6002             }
6003
6004         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
6005             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
6006             // but we leave King and Rooks for last, to possibly obey FRC restriction
6007             if(p == (int)WhiteRook) continue;
6008             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
6009             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
6010         }
6011
6012         // now everything is placed, except perhaps King (Unicorn) and Rooks
6013
6014         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6015             // Last King gets castling rights
6016             while(piecesLeft[(int)WhiteUnicorn]) {
6017                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6018                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6019             }
6020
6021             while(piecesLeft[(int)WhiteKing]) {
6022                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6023                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6024             }
6025
6026
6027         } else {
6028             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6029             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6030         }
6031
6032         // Only Rooks can be left; simply place them all
6033         while(piecesLeft[(int)WhiteRook]) {
6034                 i = put(board, WhiteRook, 0, 0, ANY);
6035                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6036                         if(first) {
6037                                 first=0;
6038                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6039                         }
6040                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6041                 }
6042         }
6043         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6044             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6045         }
6046
6047         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6048 }
6049
6050 int
6051 ptclen (const char *s, char *escapes)
6052 {
6053     int n = 0;
6054     if(!*escapes) return strlen(s);
6055     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6056     return n;
6057 }
6058
6059 int
6060 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6061 /* [HGM] moved here from winboard.c because of its general usefulness */
6062 /*       Basically a safe strcpy that uses the last character as King */
6063 {
6064     int result = FALSE; int NrPieces;
6065     unsigned char partner[EmptySquare];
6066
6067     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6068                     && NrPieces >= 12 && !(NrPieces&1)) {
6069         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6070
6071         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6072         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6073             char *p, c=0;
6074             if(map[j] == '/') offs = WhitePBishop - i, j++;
6075             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6076             table[i+offs] = map[j++];
6077             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6078             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6079             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6080         }
6081         table[(int) WhiteKing]  = map[j++];
6082         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6083             char *p, c=0;
6084             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6085             i = WHITE_TO_BLACK ii;
6086             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6087             table[i+offs] = map[j++];
6088             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6089             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6090             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6091         }
6092         table[(int) BlackKing]  = map[j++];
6093
6094
6095         if(*escapes) { // set up promotion pairing
6096             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6097             // pieceToChar entirely filled, so we can look up specified partners
6098             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6099                 int c = table[i];
6100                 if(c == '^' || c == '-') { // has specified partner
6101                     int p;
6102                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6103                     if(c == '^') table[i] = '+';
6104                     if(p < EmptySquare) {
6105                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6106                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6107                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6108                     }
6109                 } else if(c == '*') {
6110                     table[i] = partner[i];
6111                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6112                 }
6113             }
6114         }
6115
6116         result = TRUE;
6117     }
6118
6119     return result;
6120 }
6121
6122 int
6123 SetCharTable (unsigned char *table, const char * map)
6124 {
6125     return SetCharTableEsc(table, map, "");
6126 }
6127
6128 void
6129 Prelude (Board board)
6130 {       // [HGM] superchess: random selection of exo-pieces
6131         int i, j, k; ChessSquare p;
6132         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6133
6134         GetPositionNumber(); // use FRC position number
6135
6136         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6137             SetCharTable(pieceToChar, appData.pieceToCharTable);
6138             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6139                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6140         }
6141
6142         j = seed%4;                 seed /= 4;
6143         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6144         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6145         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6146         j = seed%3 + (seed%3 >= j); seed /= 3;
6147         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6148         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6149         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6150         j = seed%3;                 seed /= 3;
6151         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6152         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6153         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6154         j = seed%2 + (seed%2 >= j); seed /= 2;
6155         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6156         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6157         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6158         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6159         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6160         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6161         put(board, exoPieces[0],    0, 0, ANY);
6162         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6163 }
6164
6165 void
6166 InitPosition (int redraw)
6167 {
6168     ChessSquare (* pieces)[BOARD_FILES];
6169     int i, j, pawnRow=1, pieceRows=1, overrule,
6170     oldx = gameInfo.boardWidth,
6171     oldy = gameInfo.boardHeight,
6172     oldh = gameInfo.holdingsWidth;
6173     static int oldv;
6174
6175     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6176
6177     /* [AS] Initialize pv info list [HGM] and game status */
6178     {
6179         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6180             pvInfoList[i].depth = 0;
6181             boards[i][EP_STATUS] = EP_NONE;
6182             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6183         }
6184
6185         initialRulePlies = 0; /* 50-move counter start */
6186
6187         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6188         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6189     }
6190
6191
6192     /* [HGM] logic here is completely changed. In stead of full positions */
6193     /* the initialized data only consist of the two backranks. The switch */
6194     /* selects which one we will use, which is than copied to the Board   */
6195     /* initialPosition, which for the rest is initialized by Pawns and    */
6196     /* empty squares. This initial position is then copied to boards[0],  */
6197     /* possibly after shuffling, so that it remains available.            */
6198
6199     gameInfo.holdingsWidth = 0; /* default board sizes */
6200     gameInfo.boardWidth    = 8;
6201     gameInfo.boardHeight   = 8;
6202     gameInfo.holdingsSize  = 0;
6203     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6204     for(i=0; i<BOARD_FILES-6; i++)
6205       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6206     initialPosition[EP_STATUS] = EP_NONE;
6207     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6208     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6209     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6210          SetCharTable(pieceNickName, appData.pieceNickNames);
6211     else SetCharTable(pieceNickName, "............");
6212     pieces = FIDEArray;
6213
6214     switch (gameInfo.variant) {
6215     case VariantFischeRandom:
6216       shuffleOpenings = TRUE;
6217       appData.fischerCastling = TRUE;
6218     default:
6219       break;
6220     case VariantShatranj:
6221       pieces = ShatranjArray;
6222       nrCastlingRights = 0;
6223       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6224       break;
6225     case VariantMakruk:
6226       pieces = makrukArray;
6227       nrCastlingRights = 0;
6228       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6229       break;
6230     case VariantASEAN:
6231       pieces = aseanArray;
6232       nrCastlingRights = 0;
6233       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6234       break;
6235     case VariantTwoKings:
6236       pieces = twoKingsArray;
6237       break;
6238     case VariantGrand:
6239       pieces = GrandArray;
6240       nrCastlingRights = 0;
6241       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6242       gameInfo.boardWidth = 10;
6243       gameInfo.boardHeight = 10;
6244       gameInfo.holdingsSize = 7;
6245       break;
6246     case VariantCapaRandom:
6247       shuffleOpenings = TRUE;
6248       appData.fischerCastling = TRUE;
6249     case VariantCapablanca:
6250       pieces = CapablancaArray;
6251       gameInfo.boardWidth = 10;
6252       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6253       break;
6254     case VariantGothic:
6255       pieces = GothicArray;
6256       gameInfo.boardWidth = 10;
6257       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6258       break;
6259     case VariantSChess:
6260       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6261       gameInfo.holdingsSize = 7;
6262       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6263       break;
6264     case VariantJanus:
6265       pieces = JanusArray;
6266       gameInfo.boardWidth = 10;
6267       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6268       nrCastlingRights = 6;
6269         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6270         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6271         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6272         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6273         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6274         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6275       break;
6276     case VariantFalcon:
6277       pieces = FalconArray;
6278       gameInfo.boardWidth = 10;
6279       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6280       break;
6281     case VariantXiangqi:
6282       pieces = XiangqiArray;
6283       gameInfo.boardWidth  = 9;
6284       gameInfo.boardHeight = 10;
6285       nrCastlingRights = 0;
6286       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6287       break;
6288     case VariantShogi:
6289       pieces = ShogiArray;
6290       gameInfo.boardWidth  = 9;
6291       gameInfo.boardHeight = 9;
6292       gameInfo.holdingsSize = 7;
6293       nrCastlingRights = 0;
6294       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6295       break;
6296     case VariantChu:
6297       pieces = ChuArray; pieceRows = 3;
6298       gameInfo.boardWidth  = 12;
6299       gameInfo.boardHeight = 12;
6300       nrCastlingRights = 0;
6301 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6302   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6303       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"
6304                                    "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);
6305       break;
6306     case VariantCourier:
6307       pieces = CourierArray;
6308       gameInfo.boardWidth  = 12;
6309       nrCastlingRights = 0;
6310       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6311       break;
6312     case VariantKnightmate:
6313       pieces = KnightmateArray;
6314       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6315       break;
6316     case VariantSpartan:
6317       pieces = SpartanArray;
6318       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6319       break;
6320     case VariantLion:
6321       pieces = lionArray;
6322       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6323       break;
6324     case VariantChuChess:
6325       pieces = ChuChessArray;
6326       gameInfo.boardWidth = 10;
6327       gameInfo.boardHeight = 10;
6328       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6329       break;
6330     case VariantFairy:
6331       pieces = fairyArray;
6332       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6333       break;
6334     case VariantGreat:
6335       pieces = GreatArray;
6336       gameInfo.boardWidth = 10;
6337       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6338       gameInfo.holdingsSize = 8;
6339       break;
6340     case VariantSuper:
6341       pieces = FIDEArray;
6342       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6343       gameInfo.holdingsSize = 8;
6344       startedFromSetupPosition = TRUE;
6345       break;
6346     case VariantCrazyhouse:
6347     case VariantBughouse:
6348       pieces = FIDEArray;
6349       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6350       gameInfo.holdingsSize = 5;
6351       break;
6352     case VariantWildCastle:
6353       pieces = FIDEArray;
6354       /* !!?shuffle with kings guaranteed to be on d or e file */
6355       shuffleOpenings = 1;
6356       break;
6357     case VariantNoCastle:
6358       /* !!?unconstrained back-rank shuffle */
6359       shuffleOpenings = 1;
6360     case VariantSuicide:
6361       pieces = FIDEArray;
6362       nrCastlingRights = 0;
6363       break;
6364     }
6365
6366     overrule = 0;
6367     if(appData.NrFiles >= 0) {
6368         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6369         gameInfo.boardWidth = appData.NrFiles;
6370     }
6371     if(appData.NrRanks >= 0) {
6372         gameInfo.boardHeight = appData.NrRanks;
6373     }
6374     if(appData.holdingsSize >= 0) {
6375         i = appData.holdingsSize;
6376 //        if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6377         gameInfo.holdingsSize = i;
6378     }
6379     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6380     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6381         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6382
6383     if(!handSize) handSize = BOARD_HEIGHT;
6384     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6385     if(pawnRow < 1) pawnRow = 1;
6386     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6387        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6388     if(gameInfo.variant == VariantChu) pawnRow = 3;
6389
6390     /* User pieceToChar list overrules defaults */
6391     if(appData.pieceToCharTable != NULL)
6392         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6393
6394     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6395
6396         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6397             s = (ChessSquare) 0; /* account holding counts in guard band */
6398         for( i=0; i<BOARD_HEIGHT; i++ )
6399             initialPosition[i][j] = s;
6400
6401         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6402         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6403         initialPosition[pawnRow][j] = WhitePawn;
6404         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6405         if(gameInfo.variant == VariantXiangqi) {
6406             if(j&1) {
6407                 initialPosition[pawnRow][j] =
6408                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6409                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6410                    initialPosition[2][j] = WhiteCannon;
6411                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6412                 }
6413             }
6414         }
6415         if(gameInfo.variant == VariantChu) {
6416              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6417                initialPosition[pawnRow+1][j] = WhiteCobra,
6418                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6419              for(i=1; i<pieceRows; i++) {
6420                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6421                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6422              }
6423         }
6424         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6425             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6426                initialPosition[0][j] = WhiteRook;
6427                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6428             }
6429         }
6430         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6431     }
6432     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6433     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6434
6435             j=BOARD_LEFT+1;
6436             initialPosition[1][j] = WhiteBishop;
6437             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6438             j=BOARD_RGHT-2;
6439             initialPosition[1][j] = WhiteRook;
6440             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6441     }
6442
6443     if( nrCastlingRights == -1) {
6444         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6445         /*       This sets default castling rights from none to normal corners   */
6446         /* Variants with other castling rights must set them themselves above    */
6447         nrCastlingRights = 6;
6448
6449         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6450         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6451         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6452         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6453         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6454         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6455      }
6456
6457      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6458      if(gameInfo.variant == VariantGreat) { // promotion commoners
6459         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6460         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6461         initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6462         initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6463      }
6464      if( gameInfo.variant == VariantSChess ) {
6465       initialPosition[1][0] = BlackMarshall;
6466       initialPosition[2][0] = BlackAngel;
6467       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6468       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6469       initialPosition[1][1] = initialPosition[2][1] =
6470       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6471      }
6472      initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6473   if (appData.debugMode) {
6474     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6475   }
6476     if(shuffleOpenings) {
6477         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6478         startedFromSetupPosition = TRUE;
6479     }
6480     if(startedFromPositionFile) {
6481       /* [HGM] loadPos: use PositionFile for every new game */
6482       CopyBoard(initialPosition, filePosition);
6483       for(i=0; i<nrCastlingRights; i++)
6484           initialRights[i] = filePosition[CASTLING][i];
6485       startedFromSetupPosition = TRUE;
6486     }
6487     if(*appData.men) LoadPieceDesc(appData.men);
6488
6489     CopyBoard(boards[0], initialPosition);
6490
6491     if(oldx != gameInfo.boardWidth ||
6492        oldy != gameInfo.boardHeight ||
6493        oldv != gameInfo.variant ||
6494        oldh != gameInfo.holdingsWidth
6495                                          )
6496             InitDrawingSizes(-2 ,0);
6497
6498     oldv = gameInfo.variant;
6499     if (redraw)
6500       DrawPosition(TRUE, boards[currentMove]);
6501 }
6502
6503 void
6504 SendBoard (ChessProgramState *cps, int moveNum)
6505 {
6506     char message[MSG_SIZ];
6507
6508     if (cps->useSetboard) {
6509       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6510       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6511       SendToProgram(message, cps);
6512       free(fen);
6513
6514     } else {
6515       ChessSquare *bp;
6516       int i, j, left=0, right=BOARD_WIDTH;
6517       /* Kludge to set black to move, avoiding the troublesome and now
6518        * deprecated "black" command.
6519        */
6520       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6521         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6522
6523       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6524
6525       SendToProgram("edit\n", cps);
6526       SendToProgram("#\n", cps);
6527       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6528         bp = &boards[moveNum][i][left];
6529         for (j = left; j < right; j++, bp++) {
6530           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6531           if ((int) *bp < (int) BlackPawn) {
6532             if(j == BOARD_RGHT+1)
6533                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6534             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6535             if(message[0] == '+' || message[0] == '~') {
6536               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6537                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6538                         AAA + j, ONE + i - '0');
6539             }
6540             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6541                 message[1] = BOARD_RGHT   - 1 - j + '1';
6542                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6543             }
6544             SendToProgram(message, cps);
6545           }
6546         }
6547       }
6548
6549       SendToProgram("c\n", cps);
6550       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6551         bp = &boards[moveNum][i][left];
6552         for (j = left; j < right; j++, bp++) {
6553           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6554           if (((int) *bp != (int) EmptySquare)
6555               && ((int) *bp >= (int) BlackPawn)) {
6556             if(j == BOARD_LEFT-2)
6557                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6558             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6559                     AAA + j, ONE + i - '0');
6560             if(message[0] == '+' || message[0] == '~') {
6561               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6562                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6563                         AAA + j, ONE + i - '0');
6564             }
6565             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6566                 message[1] = BOARD_RGHT   - 1 - j + '1';
6567                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6568             }
6569             SendToProgram(message, cps);
6570           }
6571         }
6572       }
6573
6574       SendToProgram(".\n", cps);
6575     }
6576     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6577 }
6578
6579 char exclusionHeader[MSG_SIZ];
6580 int exCnt, excludePtr;
6581 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6582 static Exclusion excluTab[200];
6583 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6584
6585 static void
6586 WriteMap (int s)
6587 {
6588     int j;
6589     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6590     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6591 }
6592
6593 static void
6594 ClearMap ()
6595 {
6596     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6597     excludePtr = 24; exCnt = 0;
6598     WriteMap(0);
6599 }
6600
6601 static void
6602 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6603 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6604     char buf[2*MOVE_LEN], *p;
6605     Exclusion *e = excluTab;
6606     int i;
6607     for(i=0; i<exCnt; i++)
6608         if(e[i].ff == fromX && e[i].fr == fromY &&
6609            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6610     if(i == exCnt) { // was not in exclude list; add it
6611         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6612         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6613             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6614             return; // abort
6615         }
6616         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6617         excludePtr++; e[i].mark = excludePtr++;
6618         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6619         exCnt++;
6620     }
6621     exclusionHeader[e[i].mark] = state;
6622 }
6623
6624 static int
6625 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6626 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6627     char buf[MSG_SIZ];
6628     int j, k;
6629     ChessMove moveType;
6630     if((signed char)promoChar == -1) { // kludge to indicate best move
6631         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6632             return 1; // if unparsable, abort
6633     }
6634     // update exclusion map (resolving toggle by consulting existing state)
6635     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6636     j = k%8; k >>= 3;
6637     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6638     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6639          excludeMap[k] |=   1<<j;
6640     else excludeMap[k] &= ~(1<<j);
6641     // update header
6642     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6643     // inform engine
6644     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6645     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6646     SendToBoth(buf);
6647     return (state == '+');
6648 }
6649
6650 static void
6651 ExcludeClick (int index)
6652 {
6653     int i, j;
6654     Exclusion *e = excluTab;
6655     if(index < 25) { // none, best or tail clicked
6656         if(index < 13) { // none: include all
6657             WriteMap(0); // clear map
6658             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6659             SendToBoth("include all\n"); // and inform engine
6660         } else if(index > 18) { // tail
6661             if(exclusionHeader[19] == '-') { // tail was excluded
6662                 SendToBoth("include all\n");
6663                 WriteMap(0); // clear map completely
6664                 // now re-exclude selected moves
6665                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6666                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6667             } else { // tail was included or in mixed state
6668                 SendToBoth("exclude all\n");
6669                 WriteMap(0xFF); // fill map completely
6670                 // now re-include selected moves
6671                 j = 0; // count them
6672                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6673                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6674                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6675             }
6676         } else { // best
6677             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6678         }
6679     } else {
6680         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6681             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6682             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6683             break;
6684         }
6685     }
6686 }
6687
6688 ChessSquare
6689 DefaultPromoChoice (int white)
6690 {
6691     ChessSquare result;
6692     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6693        gameInfo.variant == VariantMakruk)
6694         result = WhiteFerz; // no choice
6695     else if(gameInfo.variant == VariantASEAN)
6696         result = WhiteRook; // no choice
6697     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6698         result= WhiteKing; // in Suicide Q is the last thing we want
6699     else if(gameInfo.variant == VariantSpartan)
6700         result = white ? WhiteQueen : WhiteAngel;
6701     else result = WhiteQueen;
6702     if(!white) result = WHITE_TO_BLACK result;
6703     return result;
6704 }
6705
6706 static int autoQueen; // [HGM] oneclick
6707
6708 int
6709 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6710 {
6711     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6712     /* [HGM] add Shogi promotions */
6713     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6714     ChessSquare piece, partner;
6715     ChessMove moveType;
6716     Boolean premove;
6717
6718     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6719     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6720
6721     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6722       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6723         return FALSE;
6724
6725     if(legal[toY][toX] == 4) return FALSE;
6726
6727     piece = boards[currentMove][fromY][fromX];
6728     if(gameInfo.variant == VariantChu) {
6729         promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3;
6730         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6731         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6732     } else if(gameInfo.variant == VariantShogi) {
6733         promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8);
6734         highestPromotingPiece = (int)WhiteAlfil;
6735         if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
6736     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6737         promotionZoneSize = 3;
6738     }
6739
6740     // Treat Lance as Pawn when it is not representing Amazon or Lance
6741     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6742         if(piece == WhiteLance) piece = WhitePawn; else
6743         if(piece == BlackLance) piece = BlackPawn;
6744     }
6745
6746     // next weed out all moves that do not touch the promotion zone at all
6747     if((int)piece >= BlackPawn) {
6748         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6749              return FALSE;
6750         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6751         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6752     } else {
6753         if(  toY < BOARD_HEIGHT - deadRanks - promotionZoneSize &&
6754            fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE;
6755         if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess)
6756              return FALSE;
6757     }
6758
6759     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6760
6761     // weed out mandatory Shogi promotions
6762     if(gameInfo.variant == VariantShogi) {
6763         if(piece >= BlackPawn) {
6764             if(toY == 0 && piece == BlackPawn ||
6765                toY == 0 && piece == BlackQueen ||
6766                toY <= 1 && piece == BlackKnight) {
6767                 *promoChoice = '+';
6768                 return FALSE;
6769             }
6770         } else {
6771             if(toY == BOARD_HEIGHT-deadRanks-1 && piece == WhitePawn ||
6772                toY == BOARD_HEIGHT-deadRanks-1 && piece == WhiteQueen ||
6773                toY >= BOARD_HEIGHT-deadRanks-2 && piece == WhiteKnight) {
6774                 *promoChoice = '+';
6775                 return FALSE;
6776             }
6777         }
6778     }
6779
6780     // weed out obviously illegal Pawn moves
6781     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6782         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6783         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6784         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6785         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6786         // note we are not allowed to test for valid (non-)capture, due to premove
6787     }
6788
6789     // we either have a choice what to promote to, or (in Shogi) whether to promote
6790     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6791        gameInfo.variant == VariantMakruk) {
6792         ChessSquare p=BlackFerz;  // no choice
6793         while(p < EmptySquare) {  //but make sure we use piece that exists
6794             *promoChoice = PieceToChar(p++);
6795             if(*promoChoice != '.') break;
6796         }
6797         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6798     }
6799     // no sense asking what we must promote to if it is going to explode...
6800     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6801         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6802         return FALSE;
6803     }
6804     // give caller the default choice even if we will not make it
6805     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6806     partner = piece; // pieces can promote if the pieceToCharTable says so
6807     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6808     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6809     if(        sweepSelect && gameInfo.variant != VariantGreat
6810                            && gameInfo.variant != VariantGrand
6811                            && gameInfo.variant != VariantSuper) return FALSE;
6812     if(autoQueen) return FALSE; // predetermined
6813
6814     // suppress promotion popup on illegal moves that are not premoves
6815     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6816               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6817     if(appData.testLegality && !premove) {
6818         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6819                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6820         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6821         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6822             return FALSE;
6823     }
6824
6825     return TRUE;
6826 }
6827
6828 int
6829 InPalace (int row, int column)
6830 {   /* [HGM] for Xiangqi */
6831     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6832          column < (BOARD_WIDTH + 4)/2 &&
6833          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6834     return FALSE;
6835 }
6836
6837 int
6838 PieceForSquare (int x, int y)
6839 {
6840   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1;
6841   if(x == BOARD_RGHT+1 && handOffsets & 1) y += handSize - BOARD_HEIGHT;
6842   if(x == BOARD_LEFT-2 && !(handOffsets & 2)) y += handSize - BOARD_HEIGHT;
6843      return boards[currentMove][y][x];
6844 }
6845
6846 ChessSquare
6847 More (Board board, int col, int start, int end)
6848 {
6849     int k;
6850     for(k=start; k<end; k++) if(board[k][col]) return (col == 1 ? WhiteMonarch : BlackMonarch); // arrow image
6851     return EmptySquare;
6852 }
6853
6854 void
6855 DrawPosition (int repaint, Board board)
6856 {
6857     Board compactedBoard;
6858     if(handSize > BOARD_HEIGHT && board) {
6859         int k;
6860         CopyBoard(compactedBoard, board);
6861         if(handOffsets & 1) {
6862             for(k=0; k<BOARD_HEIGHT; k++) {
6863                 compactedBoard[k][BOARD_WIDTH-1] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-1];
6864                 compactedBoard[k][BOARD_WIDTH-2] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-2];
6865             }
6866             compactedBoard[0][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, 0, handSize-BOARD_HEIGHT+1);
6867         } else compactedBoard[BOARD_HEIGHT-1][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, BOARD_HEIGHT-1, handSize);
6868         if(!(handOffsets & 2)) {
6869             for(k=0; k<BOARD_HEIGHT; k++) {
6870                 compactedBoard[k][0] = board[k+handSize-BOARD_HEIGHT][0];
6871                 compactedBoard[k][1] = board[k+handSize-BOARD_HEIGHT][1];
6872             }
6873             compactedBoard[0][0] = More(board, 1, 0, handSize-BOARD_HEIGHT+1);
6874         } else compactedBoard[BOARD_HEIGHT-1][0] = More(board, 1, BOARD_HEIGHT-1, handSize);
6875         DrawPositionX(TRUE, compactedBoard);
6876     } else DrawPositionX(repaint, board);
6877 }
6878
6879 int
6880 OKToStartUserMove (int x, int y)
6881 {
6882     ChessSquare from_piece;
6883     int white_piece;
6884
6885     if (matchMode) return FALSE;
6886     if (gameMode == EditPosition) return TRUE;
6887
6888     if (x >= 0 && y >= 0)
6889       from_piece = boards[currentMove][y][x];
6890     else
6891       from_piece = EmptySquare;
6892
6893     if (from_piece == EmptySquare) return FALSE;
6894
6895     white_piece = (int)from_piece >= (int)WhitePawn &&
6896       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6897
6898     switch (gameMode) {
6899       case AnalyzeFile:
6900       case TwoMachinesPlay:
6901       case EndOfGame:
6902         return FALSE;
6903
6904       case IcsObserving:
6905       case IcsIdle:
6906         return FALSE;
6907
6908       case MachinePlaysWhite:
6909       case IcsPlayingBlack:
6910         if (appData.zippyPlay) return FALSE;
6911         if (white_piece) {
6912             DisplayMoveError(_("You are playing Black"));
6913             return FALSE;
6914         }
6915         break;
6916
6917       case MachinePlaysBlack:
6918       case IcsPlayingWhite:
6919         if (appData.zippyPlay) return FALSE;
6920         if (!white_piece) {
6921             DisplayMoveError(_("You are playing White"));
6922             return FALSE;
6923         }
6924         break;
6925
6926       case PlayFromGameFile:
6927             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6928       case EditGame:
6929       case AnalyzeMode:
6930         if (!white_piece && WhiteOnMove(currentMove)) {
6931             DisplayMoveError(_("It is White's turn"));
6932             return FALSE;
6933         }
6934         if (white_piece && !WhiteOnMove(currentMove)) {
6935             DisplayMoveError(_("It is Black's turn"));
6936             return FALSE;
6937         }
6938         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6939             /* Editing correspondence game history */
6940             /* Could disallow this or prompt for confirmation */
6941             cmailOldMove = -1;
6942         }
6943         break;
6944
6945       case BeginningOfGame:
6946         if (appData.icsActive) return FALSE;
6947         if (!appData.noChessProgram) {
6948             if (!white_piece) {
6949                 DisplayMoveError(_("You are playing White"));
6950                 return FALSE;
6951             }
6952         }
6953         break;
6954
6955       case Training:
6956         if (!white_piece && WhiteOnMove(currentMove)) {
6957             DisplayMoveError(_("It is White's turn"));
6958             return FALSE;
6959         }
6960         if (white_piece && !WhiteOnMove(currentMove)) {
6961             DisplayMoveError(_("It is Black's turn"));
6962             return FALSE;
6963         }
6964         break;
6965
6966       default:
6967       case IcsExamining:
6968         break;
6969     }
6970     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6971         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6972         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6973         && gameMode != AnalyzeFile && gameMode != Training) {
6974         DisplayMoveError(_("Displayed position is not current"));
6975         return FALSE;
6976     }
6977     return TRUE;
6978 }
6979
6980 Boolean
6981 OnlyMove (int *x, int *y, Boolean captures)
6982 {
6983     DisambiguateClosure cl;
6984     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6985     switch(gameMode) {
6986       case MachinePlaysBlack:
6987       case IcsPlayingWhite:
6988       case BeginningOfGame:
6989         if(!WhiteOnMove(currentMove)) return FALSE;
6990         break;
6991       case MachinePlaysWhite:
6992       case IcsPlayingBlack:
6993         if(WhiteOnMove(currentMove)) return FALSE;
6994         break;
6995       case EditGame:
6996         break;
6997       default:
6998         return FALSE;
6999     }
7000     cl.pieceIn = EmptySquare;
7001     cl.rfIn = *y;
7002     cl.ffIn = *x;
7003     cl.rtIn = -1;
7004     cl.ftIn = -1;
7005     cl.promoCharIn = NULLCHAR;
7006     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7007     if( cl.kind == NormalMove ||
7008         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7009         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7010         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7011       fromX = cl.ff;
7012       fromY = cl.rf;
7013       *x = cl.ft;
7014       *y = cl.rt;
7015       return TRUE;
7016     }
7017     if(cl.kind != ImpossibleMove) return FALSE;
7018     cl.pieceIn = EmptySquare;
7019     cl.rfIn = -1;
7020     cl.ffIn = -1;
7021     cl.rtIn = *y;
7022     cl.ftIn = *x;
7023     cl.promoCharIn = NULLCHAR;
7024     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7025     if( cl.kind == NormalMove ||
7026         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7027         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7028         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7029       fromX = cl.ff;
7030       fromY = cl.rf;
7031       *x = cl.ft;
7032       *y = cl.rt;
7033       autoQueen = TRUE; // act as if autoQueen on when we click to-square
7034       return TRUE;
7035     }
7036     return FALSE;
7037 }
7038
7039 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
7040 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
7041 int lastLoadGameUseList = FALSE;
7042 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
7043 ChessMove lastLoadGameStart = EndOfFile;
7044 int doubleClick;
7045 Boolean addToBookFlag;
7046 static Board rightsBoard, nullBoard;
7047
7048 void
7049 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7050 {
7051     ChessMove moveType;
7052     ChessSquare pup;
7053     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7054
7055     /* Check if the user is playing in turn.  This is complicated because we
7056        let the user "pick up" a piece before it is his turn.  So the piece he
7057        tried to pick up may have been captured by the time he puts it down!
7058        Therefore we use the color the user is supposed to be playing in this
7059        test, not the color of the piece that is currently on the starting
7060        square---except in EditGame mode, where the user is playing both
7061        sides; fortunately there the capture race can't happen.  (It can
7062        now happen in IcsExamining mode, but that's just too bad.  The user
7063        will get a somewhat confusing message in that case.)
7064        */
7065
7066     switch (gameMode) {
7067       case AnalyzeFile:
7068       case TwoMachinesPlay:
7069       case EndOfGame:
7070       case IcsObserving:
7071       case IcsIdle:
7072         /* We switched into a game mode where moves are not accepted,
7073            perhaps while the mouse button was down. */
7074         return;
7075
7076       case MachinePlaysWhite:
7077         /* User is moving for Black */
7078         if (WhiteOnMove(currentMove)) {
7079             DisplayMoveError(_("It is White's turn"));
7080             return;
7081         }
7082         break;
7083
7084       case MachinePlaysBlack:
7085         /* User is moving for White */
7086         if (!WhiteOnMove(currentMove)) {
7087             DisplayMoveError(_("It is Black's turn"));
7088             return;
7089         }
7090         break;
7091
7092       case PlayFromGameFile:
7093             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7094       case EditGame:
7095       case IcsExamining:
7096       case BeginningOfGame:
7097       case AnalyzeMode:
7098       case Training:
7099         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7100         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7101             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7102             /* User is moving for Black */
7103             if (WhiteOnMove(currentMove)) {
7104                 DisplayMoveError(_("It is White's turn"));
7105                 return;
7106             }
7107         } else {
7108             /* User is moving for White */
7109             if (!WhiteOnMove(currentMove)) {
7110                 DisplayMoveError(_("It is Black's turn"));
7111                 return;
7112             }
7113         }
7114         break;
7115
7116       case IcsPlayingBlack:
7117         /* User is moving for Black */
7118         if (WhiteOnMove(currentMove)) {
7119             if (!appData.premove) {
7120                 DisplayMoveError(_("It is White's turn"));
7121             } else if (toX >= 0 && toY >= 0) {
7122                 premoveToX = toX;
7123                 premoveToY = toY;
7124                 premoveFromX = fromX;
7125                 premoveFromY = fromY;
7126                 premovePromoChar = promoChar;
7127                 gotPremove = 1;
7128                 if (appData.debugMode)
7129                     fprintf(debugFP, "Got premove: fromX %d,"
7130                             "fromY %d, toX %d, toY %d\n",
7131                             fromX, fromY, toX, toY);
7132             }
7133             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7134             return;
7135         }
7136         break;
7137
7138       case IcsPlayingWhite:
7139         /* User is moving for White */
7140         if (!WhiteOnMove(currentMove)) {
7141             if (!appData.premove) {
7142                 DisplayMoveError(_("It is Black's turn"));
7143             } else if (toX >= 0 && toY >= 0) {
7144                 premoveToX = toX;
7145                 premoveToY = toY;
7146                 premoveFromX = fromX;
7147                 premoveFromY = fromY;
7148                 premovePromoChar = promoChar;
7149                 gotPremove = 1;
7150                 if (appData.debugMode)
7151                     fprintf(debugFP, "Got premove: fromX %d,"
7152                             "fromY %d, toX %d, toY %d\n",
7153                             fromX, fromY, toX, toY);
7154             }
7155             DrawPosition(TRUE, boards[currentMove]);
7156             return;
7157         }
7158         break;
7159
7160       default:
7161         break;
7162
7163       case EditPosition:
7164         /* EditPosition, empty square, or different color piece;
7165            click-click move is possible */
7166         if (toX == -2 || toY == -2) {
7167             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7168             DrawPosition(FALSE, boards[currentMove]);
7169             return;
7170         } else if (toX >= 0 && toY >= 0) {
7171             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7172                 ChessSquare p = boards[0][rf][ff];
7173                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7174                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7175                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7176                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7177                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7178                     gatingPiece = p;
7179                 }
7180             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7181             boards[0][toY][toX] = boards[0][fromY][fromX];
7182             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7183                 if(boards[0][fromY][0] != EmptySquare) {
7184                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7185                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7186                 }
7187             } else
7188             if(fromX == BOARD_RGHT+1) {
7189                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7190                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7191                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7192                 }
7193             } else
7194             boards[0][fromY][fromX] = gatingPiece;
7195             ClearHighlights();
7196             DrawPosition(FALSE, boards[currentMove]);
7197             return;
7198         }
7199         return;
7200     }
7201
7202     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7203     pup = boards[currentMove][toY][toX];
7204
7205     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7206     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7207          if( pup != EmptySquare ) return;
7208          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7209            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7210                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7211            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7212            if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7213            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7214            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7215          fromY = DROP_RANK;
7216     }
7217
7218     /* [HGM] always test for legality, to get promotion info */
7219     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7220                                          fromY, fromX, toY, toX, promoChar);
7221
7222     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7223
7224     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7225
7226     /* [HGM] but possibly ignore an IllegalMove result */
7227     if (appData.testLegality) {
7228         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7229             DisplayMoveError(_("Illegal move"));
7230             return;
7231         }
7232     }
7233
7234     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7235         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7236              ClearPremoveHighlights(); // was included
7237         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7238         DrawPosition(FALSE, NULL);
7239         return;
7240     }
7241
7242     if(addToBookFlag) { // adding moves to book
7243         char buf[MSG_SIZ], move[MSG_SIZ];
7244         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7245         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7246                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7247         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7248         AddBookMove(buf);
7249         addToBookFlag = FALSE;
7250         ClearHighlights();
7251         return;
7252     }
7253
7254     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7255 }
7256
7257 /* Common tail of UserMoveEvent and DropMenuEvent */
7258 int
7259 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7260 {
7261     char *bookHit = 0;
7262
7263     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7264         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7265         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7266         if(WhiteOnMove(currentMove)) {
7267             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7268         } else {
7269             if(!boards[currentMove][handSize-1-k][1]) return 0;
7270         }
7271     }
7272
7273     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7274        move type in caller when we know the move is a legal promotion */
7275     if(moveType == NormalMove && promoChar)
7276         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7277
7278     /* [HGM] <popupFix> The following if has been moved here from
7279        UserMoveEvent(). Because it seemed to belong here (why not allow
7280        piece drops in training games?), and because it can only be
7281        performed after it is known to what we promote. */
7282     if (gameMode == Training) {
7283       /* compare the move played on the board to the next move in the
7284        * game. If they match, display the move and the opponent's response.
7285        * If they don't match, display an error message.
7286        */
7287       int saveAnimate;
7288       Board testBoard;
7289       CopyBoard(testBoard, boards[currentMove]);
7290       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7291
7292       if (CompareBoards(testBoard, boards[currentMove+1])) {
7293         ForwardInner(currentMove+1);
7294
7295         /* Autoplay the opponent's response.
7296          * if appData.animate was TRUE when Training mode was entered,
7297          * the response will be animated.
7298          */
7299         saveAnimate = appData.animate;
7300         appData.animate = animateTraining;
7301         ForwardInner(currentMove+1);
7302         appData.animate = saveAnimate;
7303
7304         /* check for the end of the game */
7305         if (currentMove >= forwardMostMove) {
7306           gameMode = PlayFromGameFile;
7307           ModeHighlight();
7308           SetTrainingModeOff();
7309           DisplayInformation(_("End of game"));
7310         }
7311       } else {
7312         DisplayError(_("Incorrect move"), 0);
7313       }
7314       return 1;
7315     }
7316
7317   /* Ok, now we know that the move is good, so we can kill
7318      the previous line in Analysis Mode */
7319   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7320                                 && currentMove < forwardMostMove) {
7321     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7322     else forwardMostMove = currentMove;
7323   }
7324
7325   ClearMap();
7326
7327   /* If we need the chess program but it's dead, restart it */
7328   ResurrectChessProgram();
7329
7330   /* A user move restarts a paused game*/
7331   if (pausing)
7332     PauseEvent();
7333
7334   thinkOutput[0] = NULLCHAR;
7335
7336   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7337
7338   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7339     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7340     return 1;
7341   }
7342
7343   if (gameMode == BeginningOfGame) {
7344     if (appData.noChessProgram) {
7345       gameMode = EditGame;
7346       SetGameInfo();
7347     } else {
7348       char buf[MSG_SIZ];
7349       gameMode = MachinePlaysBlack;
7350       StartClocks();
7351       SetGameInfo();
7352       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7353       DisplayTitle(buf);
7354       if (first.sendName) {
7355         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7356         SendToProgram(buf, &first);
7357       }
7358       StartClocks();
7359     }
7360     ModeHighlight();
7361   }
7362
7363   /* Relay move to ICS or chess engine */
7364   if (appData.icsActive) {
7365     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7366         gameMode == IcsExamining) {
7367       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7368         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7369         SendToICS("draw ");
7370         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7371       }
7372       // also send plain move, in case ICS does not understand atomic claims
7373       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7374       ics_user_moved = 1;
7375     }
7376   } else {
7377     if (first.sendTime && (gameMode == BeginningOfGame ||
7378                            gameMode == MachinePlaysWhite ||
7379                            gameMode == MachinePlaysBlack)) {
7380       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7381     }
7382     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7383          // [HGM] book: if program might be playing, let it use book
7384         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7385         first.maybeThinking = TRUE;
7386     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7387         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7388         SendBoard(&first, currentMove+1);
7389         if(second.analyzing) {
7390             if(!second.useSetboard) SendToProgram("undo\n", &second);
7391             SendBoard(&second, currentMove+1);
7392         }
7393     } else {
7394         SendMoveToProgram(forwardMostMove-1, &first);
7395         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7396     }
7397     if (currentMove == cmailOldMove + 1) {
7398       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7399     }
7400   }
7401
7402   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7403
7404   switch (gameMode) {
7405   case EditGame:
7406     if(appData.testLegality)
7407     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7408     case MT_NONE:
7409     case MT_CHECK:
7410       break;
7411     case MT_CHECKMATE:
7412     case MT_STAINMATE:
7413       if (WhiteOnMove(currentMove)) {
7414         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7415       } else {
7416         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7417       }
7418       break;
7419     case MT_STALEMATE:
7420       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7421       break;
7422     }
7423     break;
7424
7425   case MachinePlaysBlack:
7426   case MachinePlaysWhite:
7427     /* disable certain menu options while machine is thinking */
7428     SetMachineThinkingEnables();
7429     break;
7430
7431   default:
7432     break;
7433   }
7434
7435   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7436   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7437
7438   if(bookHit) { // [HGM] book: simulate book reply
7439         static char bookMove[MSG_SIZ]; // a bit generous?
7440
7441         programStats.nodes = programStats.depth = programStats.time =
7442         programStats.score = programStats.got_only_move = 0;
7443         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7444
7445         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7446         strcat(bookMove, bookHit);
7447         HandleMachineMove(bookMove, &first);
7448   }
7449   return 1;
7450 }
7451
7452 void
7453 MarkByFEN(char *fen)
7454 {
7455         int r, f;
7456         if(!appData.markers || !appData.highlightDragging) return;
7457         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7458         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7459         while(*fen) {
7460             int s = 0;
7461             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7462             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7463             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7464             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7465             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7466             if(*fen == 'T') marker[r][f++] = 0; else
7467             if(*fen == 'Y') marker[r][f++] = 1; else
7468             if(*fen == 'G') marker[r][f++] = 3; else
7469             if(*fen == 'B') marker[r][f++] = 4; else
7470             if(*fen == 'C') marker[r][f++] = 5; else
7471             if(*fen == 'M') marker[r][f++] = 6; else
7472             if(*fen == 'W') marker[r][f++] = 7; else
7473             if(*fen == 'D') marker[r][f++] = 8; else
7474             if(*fen == 'R') marker[r][f++] = 2; else {
7475                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7476               f += s; fen -= s>0;
7477             }
7478             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7479             if(r < 0) break;
7480             fen++;
7481         }
7482         DrawPosition(TRUE, NULL);
7483 }
7484
7485 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7486
7487 void
7488 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7489 {
7490     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7491     Markers *m = (Markers *) closure;
7492     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7493                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7494         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7495                          || kind == WhiteCapturesEnPassant
7496                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7497     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7498 }
7499
7500 static int hoverSavedValid;
7501
7502 void
7503 MarkTargetSquares (int clear)
7504 {
7505   int x, y, sum=0;
7506   if(clear) { // no reason to ever suppress clearing
7507     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7508     hoverSavedValid = 0;
7509     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7510   } else {
7511     int capt = 0;
7512     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7513        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7514     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7515     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7516       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7517       if(capt)
7518       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;
7519     }
7520   }
7521   DrawPosition(FALSE, NULL);
7522 }
7523
7524 int
7525 Explode (Board board, int fromX, int fromY, int toX, int toY)
7526 {
7527     if(gameInfo.variant == VariantAtomic &&
7528        (board[toY][toX] != EmptySquare ||                     // capture?
7529         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7530                          board[fromY][fromX] == BlackPawn   )
7531       )) {
7532         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7533         return TRUE;
7534     }
7535     return FALSE;
7536 }
7537
7538 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7539
7540 int
7541 CanPromote (ChessSquare piece, int y)
7542 {
7543         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7544         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7545         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7546         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7547            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7548           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7549            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7550         return (piece == BlackPawn && y <= zone ||
7551                 piece == WhitePawn && y >= BOARD_HEIGHT-1-deadRanks-zone ||
7552                 piece == BlackLance && y <= zone ||
7553                 piece == WhiteLance && y >= BOARD_HEIGHT-1-deadRanks-zone );
7554 }
7555
7556 void
7557 HoverEvent (int xPix, int yPix, int x, int y)
7558 {
7559         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7560         int r, f;
7561         if(!first.highlight) return;
7562         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7563         if(x == oldX && y == oldY) return; // only do something if we enter new square
7564         oldFromX = fromX; oldFromY = fromY;
7565         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7566           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7567             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7568           hoverSavedValid = 1;
7569         } else if(oldX != x || oldY != y) {
7570           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7571           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7572           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7573             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7574           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7575             char buf[MSG_SIZ];
7576             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7577             SendToProgram(buf, &first);
7578           }
7579           oldX = x; oldY = y;
7580 //        SetHighlights(fromX, fromY, x, y);
7581         }
7582 }
7583
7584 void ReportClick(char *action, int x, int y)
7585 {
7586         char buf[MSG_SIZ]; // Inform engine of what user does
7587         int r, f;
7588         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7589           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7590             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7591         if(!first.highlight || gameMode == EditPosition) return;
7592         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7593         SendToProgram(buf, &first);
7594 }
7595
7596 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7597 Boolean deferChoice;
7598 int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode
7599
7600 void
7601 LeftClick (ClickType clickType, int xPix, int yPix)
7602 {
7603     int x, y;
7604     static Boolean saveAnimate;
7605     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7606     char promoChoice = NULLCHAR;
7607     ChessSquare piece;
7608     static TimeMark lastClickTime, prevClickTime;
7609
7610     if(flashing) return;
7611
7612   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7613     x = EventToSquare(xPix, BOARD_WIDTH);
7614     y = EventToSquare(yPix, BOARD_HEIGHT);
7615     if (!flipView && y >= 0) {
7616         y = BOARD_HEIGHT - 1 - y;
7617     }
7618     if (flipView && x >= 0) {
7619         x = BOARD_WIDTH - 1 - x;
7620     }
7621
7622     // map clicks in offsetted holdings back to true coords (or switch the offset)
7623     if(x == BOARD_RGHT+1) {
7624         if(handOffsets & 1) {
7625             if(y == 0) { handOffsets &= ~1; DrawPosition(TRUE, boards[currentMove]); return; }
7626             y += handSize - BOARD_HEIGHT;
7627         } else if(y == BOARD_HEIGHT-1) { handOffsets |= 1; DrawPosition(TRUE, boards[currentMove]); return; }
7628     }
7629     if(x == BOARD_LEFT-2) {
7630         if(!(handOffsets & 2)) {
7631             if(y == 0) { handOffsets |= 2; DrawPosition(TRUE, boards[currentMove]); return; }
7632             y += handSize - BOARD_HEIGHT;
7633         } else if(y == BOARD_HEIGHT-1) { handOffsets &= ~2; DrawPosition(TRUE, boards[currentMove]); return; }
7634     }
7635
7636     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && 
7637         (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY) ) {
7638         static int dummy;
7639         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7640         right = TRUE;
7641         return;
7642     }
7643
7644     createX = createY = -1;
7645
7646     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7647
7648     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7649
7650     if (clickType == Press) ErrorPopDown();
7651     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7652
7653     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7654         defaultPromoChoice = promoSweep;
7655         promoSweep = EmptySquare;   // terminate sweep
7656         promoDefaultAltered = TRUE;
7657         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7658     }
7659
7660     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7661         if(clickType == Release) return; // ignore upclick of click-click destination
7662         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7663         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7664         if(gameInfo.holdingsWidth &&
7665                 (WhiteOnMove(currentMove)
7666                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7667                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7668             // click in right holdings, for determining promotion piece
7669             ChessSquare p = boards[currentMove][y][x];
7670             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7671             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7672             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7673                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7674                 fromX = fromY = -1;
7675                 return;
7676             }
7677         }
7678         DrawPosition(FALSE, boards[currentMove]);
7679         return;
7680     }
7681
7682     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7683     if(clickType == Press
7684             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7685               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7686               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7687         return;
7688
7689     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7690         // could be static click on premove from-square: abort premove
7691         gotPremove = 0;
7692         ClearPremoveHighlights();
7693     }
7694
7695     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7696         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7697
7698     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7699         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7700                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7701         defaultPromoChoice = DefaultPromoChoice(side);
7702     }
7703
7704     autoQueen = appData.alwaysPromoteToQueen;
7705
7706     if (fromX == -1) {
7707       int originalY = y;
7708       gatingPiece = EmptySquare;
7709       if (clickType != Press) {
7710         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7711             DragPieceEnd(xPix, yPix); dragging = 0;
7712             DrawPosition(FALSE, NULL);
7713         }
7714         return;
7715       }
7716       doubleClick = FALSE;
7717       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7718         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7719       }
7720       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7721       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7722          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7723          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7724             /* First square */
7725             if (OKToStartUserMove(fromX, fromY)) {
7726                 second = 0;
7727                 ReportClick("lift", x, y);
7728                 MarkTargetSquares(0);
7729                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7730                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7731                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7732                     promoSweep = defaultPromoChoice;
7733                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7734                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7735                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7736                 }
7737                 if (appData.highlightDragging) {
7738                     SetHighlights(fromX, fromY, -1, -1);
7739                 } else {
7740                     ClearHighlights();
7741                 }
7742             } else fromX = fromY = -1;
7743             return;
7744         }
7745     }
7746
7747     /* fromX != -1 */
7748     if (clickType == Press && gameMode != EditPosition) {
7749         ChessSquare fromP;
7750         ChessSquare toP;
7751         int frc;
7752
7753         // ignore off-board to clicks
7754         if(y < 0 || x < 0) return;
7755
7756         /* Check if clicking again on the same color piece */
7757         fromP = boards[currentMove][fromY][fromX];
7758         toP = boards[currentMove][y][x];
7759         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7760         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7761             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7762            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7763              WhitePawn <= toP && toP <= WhiteKing &&
7764              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7765              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7766             (BlackPawn <= fromP && fromP <= BlackKing &&
7767              BlackPawn <= toP && toP <= BlackKing &&
7768              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7769              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7770             /* Clicked again on same color piece -- changed his mind */
7771             second = (x == fromX && y == fromY);
7772             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7773             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7774                 second = FALSE; // first double-click rather than scond click
7775                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7776             }
7777             promoDefaultAltered = FALSE;
7778            if(!second) MarkTargetSquares(1);
7779            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7780             if (appData.highlightDragging) {
7781                 SetHighlights(x, y, -1, -1);
7782             } else {
7783                 ClearHighlights();
7784             }
7785             if (OKToStartUserMove(x, y)) {
7786                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7787                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7788                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7789                  gatingPiece = boards[currentMove][fromY][fromX];
7790                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7791                 fromX = x;
7792                 fromY = y; dragging = 1;
7793                 if(!second) ReportClick("lift", x, y);
7794                 MarkTargetSquares(0);
7795                 DragPieceBegin(xPix, yPix, FALSE);
7796                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7797                     promoSweep = defaultPromoChoice;
7798                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7799                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7800                 }
7801             }
7802            }
7803            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7804            second = FALSE;
7805         }
7806         // ignore clicks on holdings
7807         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7808     }
7809
7810     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7811         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7812         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7813         return;
7814     }
7815
7816     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7817         DragPieceEnd(xPix, yPix); dragging = 0;
7818         if(clearFlag) {
7819             // a deferred attempt to click-click move an empty square on top of a piece
7820             boards[currentMove][y][x] = EmptySquare;
7821             ClearHighlights();
7822             DrawPosition(FALSE, boards[currentMove]);
7823             fromX = fromY = -1; clearFlag = 0;
7824             return;
7825         }
7826         if (appData.animateDragging) {
7827             /* Undo animation damage if any */
7828             DrawPosition(FALSE, NULL);
7829         }
7830         if (second) {
7831             /* Second up/down in same square; just abort move */
7832             second = 0;
7833             fromX = fromY = -1;
7834             gatingPiece = EmptySquare;
7835             ClearHighlights();
7836             gotPremove = 0;
7837             ClearPremoveHighlights();
7838             MarkTargetSquares(-1);
7839             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7840         } else {
7841             /* First upclick in same square; start click-click mode */
7842             SetHighlights(x, y, -1, -1);
7843         }
7844         return;
7845     }
7846
7847     clearFlag = 0;
7848
7849     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7850        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7851         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7852         DisplayMessage(_("only marked squares are legal"),"");
7853         DrawPosition(TRUE, NULL);
7854         return; // ignore to-click
7855     }
7856
7857     /* we now have a different from- and (possibly off-board) to-square */
7858     /* Completed move */
7859     if(!sweepSelecting) {
7860         toX = x;
7861         toY = y;
7862     }
7863
7864     piece = boards[currentMove][fromY][fromX];
7865
7866     saveAnimate = appData.animate;
7867     if (clickType == Press) {
7868         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7869         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7870             // must be Edit Position mode with empty-square selected
7871             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7872             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7873             return;
7874         }
7875         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7876             return;
7877         }
7878         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7879             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7880         } else
7881         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7882         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7883           if(appData.sweepSelect) {
7884             promoSweep = defaultPromoChoice;
7885             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7886             selectFlag = 0; lastX = xPix; lastY = yPix;
7887             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7888             saveFlash = appData.flashCount; appData.flashCount = 0;
7889             Sweep(0); // Pawn that is going to promote: preview promotion piece
7890             sweepSelecting = 1;
7891             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7892             MarkTargetSquares(1);
7893           }
7894           return; // promo popup appears on up-click
7895         }
7896         /* Finish clickclick move */
7897         if (appData.animate || appData.highlightLastMove) {
7898             SetHighlights(fromX, fromY, toX, toY);
7899         } else {
7900             ClearHighlights();
7901         }
7902         MarkTargetSquares(1);
7903     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7904         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7905         *promoRestrict = 0; appData.flashCount = saveFlash;
7906         if (appData.animate || appData.highlightLastMove) {
7907             SetHighlights(fromX, fromY, toX, toY);
7908         } else {
7909             ClearHighlights();
7910         }
7911         MarkTargetSquares(1);
7912     } else {
7913 #if 0
7914 // [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
7915         /* Finish drag move */
7916         if (appData.highlightLastMove) {
7917             SetHighlights(fromX, fromY, toX, toY);
7918         } else {
7919             ClearHighlights();
7920         }
7921 #endif
7922         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7923           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7924         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7925         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7926           dragging *= 2;            // flag button-less dragging if we are dragging
7927           MarkTargetSquares(1);
7928           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7929           else {
7930             kill2X = killX; kill2Y = killY;
7931             killX = x; killY = y;     // remember this square as intermediate
7932             ReportClick("put", x, y); // and inform engine
7933             ReportClick("lift", x, y);
7934             MarkTargetSquares(0);
7935             return;
7936           }
7937         }
7938         DragPieceEnd(xPix, yPix); dragging = 0;
7939         /* Don't animate move and drag both */
7940         appData.animate = FALSE;
7941         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7942     }
7943
7944     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7945     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7946         ChessSquare piece = boards[currentMove][fromY][fromX];
7947         if(gameMode == EditPosition && piece != EmptySquare &&
7948            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7949             int n;
7950
7951             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7952                 n = PieceToNumber(piece - (int)BlackPawn);
7953                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7954                 boards[currentMove][handSize-1 - n][0] = piece;
7955                 boards[currentMove][handSize-1 - n][1]++;
7956             } else
7957             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7958                 n = PieceToNumber(piece);
7959                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7960                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7961                 boards[currentMove][n][BOARD_WIDTH-2]++;
7962             }
7963             boards[currentMove][fromY][fromX] = EmptySquare;
7964         }
7965         ClearHighlights();
7966         fromX = fromY = -1;
7967         MarkTargetSquares(1);
7968         DrawPosition(TRUE, boards[currentMove]);
7969         return;
7970     }
7971
7972     // off-board moves should not be highlighted
7973     if(x < 0 || y < 0) {
7974         ClearHighlights();
7975         DrawPosition(FALSE, NULL);
7976     } else ReportClick("put", x, y);
7977
7978     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7979  }
7980
7981     if(legal[toY][toX] == 2) { // highlight-induced promotion
7982         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7983         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7984     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7985       if(!*promoRestrict) {           // but has not done that yet
7986         deferChoice = TRUE;           // set up retry for when it does
7987         return;                       // and wait for that
7988       }
7989       promoChoice = ToLower(*promoRestrict); // force engine's choice
7990       deferChoice = FALSE;
7991     }
7992
7993     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7994         SetHighlights(fromX, fromY, toX, toY);
7995         MarkTargetSquares(1);
7996         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7997             // [HGM] super: promotion to captured piece selected from holdings
7998             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7999             promotionChoice = TRUE;
8000             // kludge follows to temporarily execute move on display, without promoting yet
8001             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
8002             boards[currentMove][toY][toX] = p;
8003             DrawPosition(FALSE, boards[currentMove]);
8004             boards[currentMove][fromY][fromX] = p; // take back, but display stays
8005             boards[currentMove][toY][toX] = q;
8006             DisplayMessage("Click in holdings to choose piece", "");
8007             return;
8008         }
8009         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
8010         PromotionPopUp(promoChoice);
8011     } else {
8012         int oldMove = currentMove;
8013         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
8014         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
8015         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
8016         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
8017         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
8018            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
8019             DrawPosition(TRUE, boards[currentMove]);
8020         else DrawPosition(FALSE, NULL);
8021         fromX = fromY = -1;
8022         flashing = 0;
8023     }
8024     appData.animate = saveAnimate;
8025     if (appData.animate || appData.animateDragging) {
8026         /* Undo animation damage if needed */
8027 //      DrawPosition(FALSE, NULL);
8028     }
8029 }
8030
8031 int
8032 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
8033 {   // front-end-free part taken out of PieceMenuPopup
8034     int whichMenu; int xSqr, ySqr;
8035
8036     if(seekGraphUp) { // [HGM] seekgraph
8037         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
8038         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
8039         return -2;
8040     }
8041
8042     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
8043          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
8044         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
8045         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
8046         if(action == Press)   {
8047             originalFlip = flipView;
8048             flipView = !flipView; // temporarily flip board to see game from partners perspective
8049             DrawPosition(TRUE, partnerBoard);
8050             DisplayMessage(partnerStatus, "");
8051             partnerUp = TRUE;
8052         } else if(action == Release) {
8053             flipView = originalFlip;
8054             DrawPosition(TRUE, boards[currentMove]);
8055             partnerUp = FALSE;
8056         }
8057         return -2;
8058     }
8059
8060     xSqr = EventToSquare(x, BOARD_WIDTH);
8061     ySqr = EventToSquare(y, BOARD_HEIGHT);
8062     if (action == Release) {
8063         if(pieceSweep != EmptySquare) {
8064             EditPositionMenuEvent(pieceSweep, toX, toY);
8065             pieceSweep = EmptySquare;
8066         } else UnLoadPV(); // [HGM] pv
8067     }
8068     if (action != Press) return -2; // return code to be ignored
8069     switch (gameMode) {
8070       case IcsExamining:
8071         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8072       case EditPosition:
8073         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8074         if (xSqr < 0 || ySqr < 0) return -1;
8075         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8076         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8077         if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) {
8078             ChessSquare p = boards[currentMove][ySqr][xSqr];
8079             do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.');
8080             boards[currentMove][ySqr][xSqr] = p; DrawPosition(FALSE, boards[currentMove]);
8081             return -2;
8082         }
8083         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8084         createX = toX = xSqr; createY = toY = ySqr; lastX = x, lastY = y;
8085         NextPiece(0);
8086         return 2; // grab
8087       case IcsObserving:
8088         if(!appData.icsEngineAnalyze) return -1;
8089       case IcsPlayingWhite:
8090       case IcsPlayingBlack:
8091         if(!appData.zippyPlay) goto noZip;
8092       case AnalyzeMode:
8093       case AnalyzeFile:
8094       case MachinePlaysWhite:
8095       case MachinePlaysBlack:
8096       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8097         if (!appData.dropMenu) {
8098           LoadPV(x, y);
8099           return 2; // flag front-end to grab mouse events
8100         }
8101         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8102            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8103       case EditGame:
8104       noZip:
8105         if (xSqr < 0 || ySqr < 0) return -1;
8106         if (!appData.dropMenu || appData.testLegality &&
8107             gameInfo.variant != VariantBughouse &&
8108             gameInfo.variant != VariantCrazyhouse) return -1;
8109         whichMenu = 1; // drop menu
8110         break;
8111       default:
8112         return -1;
8113     }
8114
8115     if (((*fromX = xSqr) < 0) ||
8116         ((*fromY = ySqr) < 0)) {
8117         *fromX = *fromY = -1;
8118         return -1;
8119     }
8120     if (flipView)
8121       *fromX = BOARD_WIDTH - 1 - *fromX;
8122     else
8123       *fromY = BOARD_HEIGHT - 1 - *fromY;
8124
8125     return whichMenu;
8126 }
8127
8128 void
8129 Wheel (int dir, int x, int y)
8130 {
8131     if(gameMode == EditPosition) {
8132         int xSqr = EventToSquare(x, BOARD_WIDTH);
8133         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8134         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8135         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8136         do {
8137             boards[currentMove][ySqr][xSqr] += dir;
8138             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8139             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8140         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8141         DrawPosition(FALSE, boards[currentMove]);
8142     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8143 }
8144
8145 void
8146 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8147 {
8148 //    char * hint = lastHint;
8149     FrontEndProgramStats stats;
8150
8151     stats.which = cps == &first ? 0 : 1;
8152     stats.depth = cpstats->depth;
8153     stats.nodes = cpstats->nodes;
8154     stats.score = cpstats->score;
8155     stats.time = cpstats->time;
8156     stats.pv = cpstats->movelist;
8157     stats.hint = lastHint;
8158     stats.an_move_index = 0;
8159     stats.an_move_count = 0;
8160
8161     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8162         stats.hint = cpstats->move_name;
8163         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8164         stats.an_move_count = cpstats->nr_moves;
8165     }
8166
8167     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
8168
8169     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8170         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8171
8172     SetProgramStats( &stats );
8173 }
8174
8175 void
8176 ClearEngineOutputPane (int which)
8177 {
8178     static FrontEndProgramStats dummyStats;
8179     dummyStats.which = which;
8180     dummyStats.pv = "#";
8181     SetProgramStats( &dummyStats );
8182 }
8183
8184 #define MAXPLAYERS 500
8185
8186 char *
8187 TourneyStandings (int display)
8188 {
8189     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8190     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8191     char result, *p, *names[MAXPLAYERS];
8192
8193     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8194         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8195     names[0] = p = strdup(appData.participants);
8196     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8197
8198     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8199
8200     while(result = appData.results[nr]) {
8201         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8202         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8203         wScore = bScore = 0;
8204         switch(result) {
8205           case '+': wScore = 2; break;
8206           case '-': bScore = 2; break;
8207           case '=': wScore = bScore = 1; break;
8208           case ' ':
8209           case '*': return strdup("busy"); // tourney not finished
8210         }
8211         score[w] += wScore;
8212         score[b] += bScore;
8213         games[w]++;
8214         games[b]++;
8215         nr++;
8216     }
8217     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8218     for(w=0; w<nPlayers; w++) {
8219         bScore = -1;
8220         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8221         ranking[w] = b; points[w] = bScore; score[b] = -2;
8222     }
8223     p = malloc(nPlayers*34+1);
8224     for(w=0; w<nPlayers && w<display; w++)
8225         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8226     free(names[0]);
8227     return p;
8228 }
8229
8230 void
8231 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8232 {       // count all piece types
8233         int p, f, r;
8234         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8235         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8236         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8237                 p = board[r][f];
8238                 pCnt[p]++;
8239                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8240                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8241                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8242                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8243                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8244                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8245         }
8246 }
8247
8248 int
8249 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8250 {
8251         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8252         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8253
8254         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8255         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8256         if(myPawns == 2 && nMine == 3) // KPP
8257             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8258         if(myPawns == 1 && nMine == 2) // KP
8259             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8260         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8261             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8262         if(myPawns) return FALSE;
8263         if(pCnt[WhiteRook+side])
8264             return pCnt[BlackRook-side] ||
8265                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8266                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8267                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8268         if(pCnt[WhiteCannon+side]) {
8269             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8270             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8271         }
8272         if(pCnt[WhiteKnight+side])
8273             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8274         return FALSE;
8275 }
8276
8277 int
8278 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8279 {
8280         VariantClass v = gameInfo.variant;
8281
8282         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8283         if(v == VariantShatranj) return TRUE; // always winnable through baring
8284         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8285         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8286
8287         if(v == VariantXiangqi) {
8288                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8289
8290                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8291                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8292                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8293                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8294                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8295                 if(stale) // we have at least one last-rank P plus perhaps C
8296                     return majors // KPKX
8297                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8298                 else // KCA*E*
8299                     return pCnt[WhiteFerz+side] // KCAK
8300                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8301                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8302                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8303
8304         } else if(v == VariantKnightmate) {
8305                 if(nMine == 1) return FALSE;
8306                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8307         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8308                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8309
8310                 if(nMine == 1) return FALSE; // bare King
8311                 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
8312                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8313                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8314                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8315                 if(pCnt[WhiteKnight+side])
8316                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8317                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8318                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8319                 if(nBishops)
8320                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8321                 if(pCnt[WhiteAlfil+side])
8322                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8323                 if(pCnt[WhiteWazir+side])
8324                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8325         }
8326
8327         return TRUE;
8328 }
8329
8330 int
8331 CompareWithRights (Board b1, Board b2)
8332 {
8333     int rights = 0;
8334     if(!CompareBoards(b1, b2)) return FALSE;
8335     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8336     /* compare castling rights */
8337     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8338            rights++; /* King lost rights, while rook still had them */
8339     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8340         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8341            rights++; /* but at least one rook lost them */
8342     }
8343     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8344            rights++;
8345     if( b1[CASTLING][5] != NoRights ) {
8346         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8347            rights++;
8348     }
8349     return rights == 0;
8350 }
8351
8352 int
8353 Adjudicate (ChessProgramState *cps)
8354 {       // [HGM] some adjudications useful with buggy engines
8355         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8356         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8357         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8358         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8359         int k, drop, count = 0; static int bare = 1;
8360         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8361         Boolean canAdjudicate = !appData.icsActive;
8362
8363         // most tests only when we understand the game, i.e. legality-checking on
8364             if( appData.testLegality )
8365             {   /* [HGM] Some more adjudications for obstinate engines */
8366                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8367                 static int moveCount = 6;
8368                 ChessMove result;
8369                 char *reason = NULL;
8370
8371                 /* Count what is on board. */
8372                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8373
8374                 /* Some material-based adjudications that have to be made before stalemate test */
8375                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8376                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8377                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8378                      if(canAdjudicate && appData.checkMates) {
8379                          if(engineOpponent)
8380                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8381                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8382                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8383                          return 1;
8384                      }
8385                 }
8386
8387                 /* Bare King in Shatranj (loses) or Losers (wins) */
8388                 if( nrW == 1 || nrB == 1) {
8389                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8390                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8391                      if(canAdjudicate && appData.checkMates) {
8392                          if(engineOpponent)
8393                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8394                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8395                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8396                          return 1;
8397                      }
8398                   } else
8399                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8400                   {    /* bare King */
8401                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8402                         if(canAdjudicate && appData.checkMates) {
8403                             /* but only adjudicate if adjudication enabled */
8404                             if(engineOpponent)
8405                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8406                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8407                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8408                             return 1;
8409                         }
8410                   }
8411                 } else bare = 1;
8412
8413
8414             // don't wait for engine to announce game end if we can judge ourselves
8415             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8416               case MT_CHECK:
8417                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8418                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8419                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8420                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8421                             checkCnt++;
8422                         if(checkCnt >= 2) {
8423                             reason = "Xboard adjudication: 3rd check";
8424                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8425                             break;
8426                         }
8427                     }
8428                 }
8429               case MT_NONE:
8430               default:
8431                 break;
8432               case MT_STEALMATE:
8433               case MT_STALEMATE:
8434               case MT_STAINMATE:
8435                 reason = "Xboard adjudication: Stalemate";
8436                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8437                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8438                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8439                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8440                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8441                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8442                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8443                                                                         EP_CHECKMATE : EP_WINS);
8444                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8445                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8446                 }
8447                 break;
8448               case MT_CHECKMATE:
8449                 reason = "Xboard adjudication: Checkmate";
8450                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8451                 if(gameInfo.variant == VariantShogi) {
8452                     if(forwardMostMove > backwardMostMove
8453                        && moveList[forwardMostMove-1][1] == '@'
8454                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8455                         reason = "XBoard adjudication: pawn-drop mate";
8456                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8457                     }
8458                 }
8459                 break;
8460             }
8461
8462                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8463                     case EP_STALEMATE:
8464                         result = GameIsDrawn; break;
8465                     case EP_CHECKMATE:
8466                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8467                     case EP_WINS:
8468                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8469                     default:
8470                         result = EndOfFile;
8471                 }
8472                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8473                     if(engineOpponent)
8474                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8475                     GameEnds( result, reason, GE_XBOARD );
8476                     return 1;
8477                 }
8478
8479                 /* Next absolutely insufficient mating material. */
8480                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8481                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8482                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8483
8484                      /* always flag draws, for judging claims */
8485                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8486
8487                      if(canAdjudicate && appData.materialDraws) {
8488                          /* but only adjudicate them if adjudication enabled */
8489                          if(engineOpponent) {
8490                            SendToProgram("force\n", engineOpponent); // suppress reply
8491                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8492                          }
8493                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8494                          return 1;
8495                      }
8496                 }
8497
8498                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8499                 if(gameInfo.variant == VariantXiangqi ?
8500                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8501                  : nrW + nrB == 4 &&
8502                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8503                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8504                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8505                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8506                    ) ) {
8507                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8508                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8509                           if(engineOpponent) {
8510                             SendToProgram("force\n", engineOpponent); // suppress reply
8511                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8512                           }
8513                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8514                           return 1;
8515                      }
8516                 } else moveCount = 6;
8517
8518                 if(gameInfo.variant == VariantMakruk && // Makruk counting rules
8519                   (nrW == 1 || nrB == 1 || nr[WhitePawn] + nr[BlackPawn] == 0)) { // which only kick in when pawnless or bare King
8520                     int maxcnt, his, mine, c, wom = WhiteOnMove(forwardMostMove);
8521                     count = forwardMostMove;
8522                     while(count >= backwardMostMove) {
8523                         int np = nr[WhitePawn] + nr[BlackPawn];
8524                         if(wom) mine = nrW, his = nrB, c = BlackPawn;
8525                         else    mine = nrB, his = nrW, c = WhitePawn;
8526                         if(mine > 1 && np) { count++; break; }
8527                         if(mine > 1) maxcnt = 64; else
8528                         maxcnt = (nr[WhiteRook+c] > 1 ? 8 : nr[WhiteRook+c] ? 16 : nr[WhiteMan+c] > 1 ? 22 :
8529                                                             nr[WhiteKnight+c] > 1 ? 32 : nr[WhiteMan+c] ? 44 : 64) - his - 1;
8530                         while(boards[count][EP_STATUS] != EP_CAPTURE && count > backwardMostMove) count--; // seek previous character
8531                         if(count == backwardMostMove) break;
8532                         if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) break;
8533                         Count(boards[--count], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8534                     }
8535                     if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) {
8536                         boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8537                         if(canAdjudicate && appData.ruleMoves >= 0) {
8538                             GameEnds( GameIsDrawn, "Xboard adjudication: counting rule", GE_XBOARD );
8539                             return 1;
8540                         }
8541                     }
8542                 }
8543             }
8544
8545         // Repetition draws and 50-move rule can be applied independently of legality testing
8546
8547                 /* Check for rep-draws */
8548                 count = 0;
8549                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8550                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8551                 for(k = forwardMostMove-2;
8552                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8553                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8554                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8555                     k-=2)
8556                 {   int rights=0;
8557                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8558                         /* compare castling rights */
8559                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8560                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8561                                 rights++; /* King lost rights, while rook still had them */
8562                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8563                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8564                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8565                                    rights++; /* but at least one rook lost them */
8566                         }
8567                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8568                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8569                                 rights++;
8570                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8571                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8572                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8573                                    rights++;
8574                         }
8575                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8576                             && appData.drawRepeats > 1) {
8577                              /* adjudicate after user-specified nr of repeats */
8578                              int result = GameIsDrawn;
8579                              char *details = "XBoard adjudication: repetition draw";
8580                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8581                                 // [HGM] xiangqi: check for forbidden perpetuals
8582                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8583                                 for(m=forwardMostMove; m>k; m-=2) {
8584                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8585                                         ourPerpetual = 0; // the current mover did not always check
8586                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8587                                         hisPerpetual = 0; // the opponent did not always check
8588                                 }
8589                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8590                                                                         ourPerpetual, hisPerpetual);
8591                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8592                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8593                                     details = "Xboard adjudication: perpetual checking";
8594                                 } else
8595                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8596                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8597                                 } else
8598                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8599                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8600                                         result = BlackWins;
8601                                         details = "Xboard adjudication: repetition";
8602                                     }
8603                                 } else // it must be XQ
8604                                 // Now check for perpetual chases
8605                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8606                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8607                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8608                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8609                                         static char resdet[MSG_SIZ];
8610                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8611                                         details = resdet;
8612                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8613                                     } else
8614                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8615                                         break; // Abort repetition-checking loop.
8616                                 }
8617                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8618                              }
8619                              if(engineOpponent) {
8620                                SendToProgram("force\n", engineOpponent); // suppress reply
8621                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8622                              }
8623                              GameEnds( result, details, GE_XBOARD );
8624                              return 1;
8625                         }
8626                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8627                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8628                     }
8629                 }
8630
8631                 /* Now we test for 50-move draws. Determine ply count */
8632                 count = forwardMostMove;
8633                 /* look for last irreversble move */
8634                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8635                     count--;
8636                 /* if we hit starting position, add initial plies */
8637                 if( count == backwardMostMove )
8638                     count -= initialRulePlies;
8639                 count = forwardMostMove - count;
8640                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8641                         // adjust reversible move counter for checks in Xiangqi
8642                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8643                         if(i < backwardMostMove) i = backwardMostMove;
8644                         while(i <= forwardMostMove) {
8645                                 lastCheck = inCheck; // check evasion does not count
8646                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8647                                 if(inCheck || lastCheck) count--; // check does not count
8648                                 i++;
8649                         }
8650                 }
8651                 if( count >= 100 && gameInfo.variant != VariantMakruk) // do not accept 50-move claims in Makruk
8652                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8653                          /* this is used to judge if draw claims are legal */
8654                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8655                          if(engineOpponent) {
8656                            SendToProgram("force\n", engineOpponent); // suppress reply
8657                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8658                          }
8659                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8660                          return 1;
8661                 }
8662
8663                 /* if draw offer is pending, treat it as a draw claim
8664                  * when draw condition present, to allow engines a way to
8665                  * claim draws before making their move to avoid a race
8666                  * condition occurring after their move
8667                  */
8668                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8669                          char *p = NULL;
8670                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8671                              p = "Draw claim: 50-move rule";
8672                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8673                              p = "Draw claim: 3-fold repetition";
8674                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8675                              p = "Draw claim: insufficient mating material";
8676                          if( p != NULL && canAdjudicate) {
8677                              if(engineOpponent) {
8678                                SendToProgram("force\n", engineOpponent); // suppress reply
8679                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8680                              }
8681                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8682                              return 1;
8683                          }
8684                 }
8685
8686                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8687                     if(engineOpponent) {
8688                       SendToProgram("force\n", engineOpponent); // suppress reply
8689                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8690                     }
8691                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8692                     return 1;
8693                 }
8694         return 0;
8695 }
8696
8697 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8698 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8699 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8700
8701 static int
8702 BitbaseProbe ()
8703 {
8704     int pieces[10], squares[10], cnt=0, r, f, res;
8705     static int loaded;
8706     static PPROBE_EGBB probeBB;
8707     if(!appData.testLegality) return 10;
8708     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8709     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8710     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8711     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8712         ChessSquare piece = boards[forwardMostMove][r][f];
8713         int black = (piece >= BlackPawn);
8714         int type = piece - black*BlackPawn;
8715         if(piece == EmptySquare) continue;
8716         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8717         if(type == WhiteKing) type = WhiteQueen + 1;
8718         type = egbbCode[type];
8719         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8720         pieces[cnt] = type + black*6;
8721         if(++cnt > 5) return 11;
8722     }
8723     pieces[cnt] = squares[cnt] = 0;
8724     // probe EGBB
8725     if(loaded == 2) return 13; // loading failed before
8726     if(loaded == 0) {
8727         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8728         HMODULE lib;
8729         PLOAD_EGBB loadBB;
8730         loaded = 2; // prepare for failure
8731         if(!path) return 13; // no egbb installed
8732         strncpy(buf, path + 8, MSG_SIZ);
8733         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8734         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8735         lib = LoadLibrary(buf);
8736         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8737         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8738         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8739         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8740         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8741         loaded = 1; // success!
8742     }
8743     res = probeBB(forwardMostMove & 1, pieces, squares);
8744     return res > 0 ? 1 : res < 0 ? -1 : 0;
8745 }
8746
8747 char *
8748 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8749 {   // [HGM] book: this routine intercepts moves to simulate book replies
8750     char *bookHit = NULL;
8751
8752     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8753         char buf[MSG_SIZ];
8754         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8755         SendToProgram(buf, cps);
8756     }
8757     //first determine if the incoming move brings opponent into his book
8758     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8759         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8760     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8761     if(bookHit != NULL && !cps->bookSuspend) {
8762         // make sure opponent is not going to reply after receiving move to book position
8763         SendToProgram("force\n", cps);
8764         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8765     }
8766     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8767     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8768     // now arrange restart after book miss
8769     if(bookHit) {
8770         // after a book hit we never send 'go', and the code after the call to this routine
8771         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8772         char buf[MSG_SIZ], *move = bookHit;
8773         if(cps->useSAN) {
8774             int fromX, fromY, toX, toY;
8775             char promoChar;
8776             ChessMove moveType;
8777             move = buf + 30;
8778             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8779                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8780                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8781                                     PosFlags(forwardMostMove),
8782                                     fromY, fromX, toY, toX, promoChar, move);
8783             } else {
8784                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8785                 bookHit = NULL;
8786             }
8787         }
8788         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8789         SendToProgram(buf, cps);
8790         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8791     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8792         SendToProgram("go\n", cps);
8793         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8794     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8795         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8796             SendToProgram("go\n", cps);
8797         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8798     }
8799     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8800 }
8801
8802 int
8803 LoadError (char *errmess, ChessProgramState *cps)
8804 {   // unloads engine and switches back to -ncp mode if it was first
8805     if(cps->initDone) return FALSE;
8806     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8807     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8808     cps->pr = NoProc;
8809     if(cps == &first) {
8810         appData.noChessProgram = TRUE;
8811         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8812         gameMode = BeginningOfGame; ModeHighlight();
8813         SetNCPMode();
8814     }
8815     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8816     DisplayMessage("", ""); // erase waiting message
8817     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8818     return TRUE;
8819 }
8820
8821 char *savedMessage;
8822 ChessProgramState *savedState;
8823 void
8824 DeferredBookMove (void)
8825 {
8826         if(savedState->lastPing != savedState->lastPong)
8827                     ScheduleDelayedEvent(DeferredBookMove, 10);
8828         else
8829         HandleMachineMove(savedMessage, savedState);
8830 }
8831
8832 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8833 static ChessProgramState *stalledEngine;
8834 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8835
8836 void
8837 HandleMachineMove (char *message, ChessProgramState *cps)
8838 {
8839     static char firstLeg[20], legs;
8840     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8841     char realname[MSG_SIZ];
8842     int fromX, fromY, toX, toY;
8843     ChessMove moveType;
8844     char promoChar, roar;
8845     char *p, *pv=buf1;
8846     int oldError;
8847     char *bookHit;
8848
8849     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8850         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8851         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8852             DisplayError(_("Invalid pairing from pairing engine"), 0);
8853             return;
8854         }
8855         pairingReceived = 1;
8856         NextMatchGame();
8857         return; // Skim the pairing messages here.
8858     }
8859
8860     oldError = cps->userError; cps->userError = 0;
8861
8862 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8863     /*
8864      * Kludge to ignore BEL characters
8865      */
8866     while (*message == '\007') message++;
8867
8868     /*
8869      * [HGM] engine debug message: ignore lines starting with '#' character
8870      */
8871     if(cps->debug && *message == '#') return;
8872
8873     /*
8874      * Look for book output
8875      */
8876     if (cps == &first && bookRequested) {
8877         if (message[0] == '\t' || message[0] == ' ') {
8878             /* Part of the book output is here; append it */
8879             strcat(bookOutput, message);
8880             strcat(bookOutput, "  \n");
8881             return;
8882         } else if (bookOutput[0] != NULLCHAR) {
8883             /* All of book output has arrived; display it */
8884             char *p = bookOutput;
8885             while (*p != NULLCHAR) {
8886                 if (*p == '\t') *p = ' ';
8887                 p++;
8888             }
8889             DisplayInformation(bookOutput);
8890             bookRequested = FALSE;
8891             /* Fall through to parse the current output */
8892         }
8893     }
8894
8895     /*
8896      * Look for machine move.
8897      */
8898     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8899         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8900     {
8901         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8902             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8903             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8904             stalledEngine = cps;
8905             if(appData.ponderNextMove) { // bring opponent out of ponder
8906                 if(gameMode == TwoMachinesPlay) {
8907                     if(cps->other->pause)
8908                         PauseEngine(cps->other);
8909                     else
8910                         SendToProgram("easy\n", cps->other);
8911                 }
8912             }
8913             StopClocks();
8914             return;
8915         }
8916
8917       if(cps->usePing) {
8918
8919         /* This method is only useful on engines that support ping */
8920         if(abortEngineThink) {
8921             if (appData.debugMode) {
8922                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8923             }
8924             SendToProgram("undo\n", cps);
8925             return;
8926         }
8927
8928         if (cps->lastPing != cps->lastPong) {
8929             /* Extra move from before last new; ignore */
8930             if (appData.debugMode) {
8931                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8932             }
8933           return;
8934         }
8935
8936       } else {
8937
8938         int machineWhite = FALSE;
8939
8940         switch (gameMode) {
8941           case BeginningOfGame:
8942             /* Extra move from before last reset; ignore */
8943             if (appData.debugMode) {
8944                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8945             }
8946             return;
8947
8948           case EndOfGame:
8949           case IcsIdle:
8950           default:
8951             /* Extra move after we tried to stop.  The mode test is
8952                not a reliable way of detecting this problem, but it's
8953                the best we can do on engines that don't support ping.
8954             */
8955             if (appData.debugMode) {
8956                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8957                         cps->which, gameMode);
8958             }
8959             SendToProgram("undo\n", cps);
8960             return;
8961
8962           case MachinePlaysWhite:
8963           case IcsPlayingWhite:
8964             machineWhite = TRUE;
8965             break;
8966
8967           case MachinePlaysBlack:
8968           case IcsPlayingBlack:
8969             machineWhite = FALSE;
8970             break;
8971
8972           case TwoMachinesPlay:
8973             machineWhite = (cps->twoMachinesColor[0] == 'w');
8974             break;
8975         }
8976         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8977             if (appData.debugMode) {
8978                 fprintf(debugFP,
8979                         "Ignoring move out of turn by %s, gameMode %d"
8980                         ", forwardMost %d\n",
8981                         cps->which, gameMode, forwardMostMove);
8982             }
8983             return;
8984         }
8985       }
8986
8987         if(cps->alphaRank) AlphaRank(machineMove, 4);
8988
8989         // [HGM] lion: (some very limited) support for Alien protocol
8990         killX = killY = kill2X = kill2Y = -1;
8991         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8992             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8993             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8994             return;
8995         }
8996         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8997             char *q = strchr(p+1, ',');            // second comma?
8998             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8999             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
9000             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
9001         }
9002         if(firstLeg[0]) { // there was a previous leg;
9003             // only support case where same piece makes two step
9004             char buf[20], *p = machineMove+1, *q = buf+1, f;
9005             safeStrCpy(buf, machineMove, 20);
9006             while(isdigit(*q)) q++; // find start of to-square
9007             safeStrCpy(machineMove, firstLeg, 20);
9008             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
9009             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
9010             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)
9011             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
9012             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
9013             firstLeg[0] = NULLCHAR; legs = 0;
9014         }
9015
9016         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
9017                               &fromX, &fromY, &toX, &toY, &promoChar)) {
9018             /* Machine move could not be parsed; ignore it. */
9019           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
9020                     machineMove, _(cps->which));
9021             DisplayMoveError(buf1);
9022             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
9023                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
9024             if (gameMode == TwoMachinesPlay) {
9025               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9026                        buf1, GE_XBOARD);
9027             }
9028             return;
9029         }
9030
9031         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
9032         /* So we have to redo legality test with true e.p. status here,  */
9033         /* to make sure an illegal e.p. capture does not slip through,   */
9034         /* to cause a forfeit on a justified illegal-move complaint      */
9035         /* of the opponent.                                              */
9036         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
9037            ChessMove moveType;
9038            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
9039                              fromY, fromX, toY, toX, promoChar);
9040             if(moveType == IllegalMove) {
9041               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
9042                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
9043                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9044                            buf1, GE_XBOARD);
9045                 return;
9046            } else if(!appData.fischerCastling)
9047            /* [HGM] Kludge to handle engines that send FRC-style castling
9048               when they shouldn't (like TSCP-Gothic) */
9049            switch(moveType) {
9050              case WhiteASideCastleFR:
9051              case BlackASideCastleFR:
9052                toX+=2;
9053                currentMoveString[2]++;
9054                break;
9055              case WhiteHSideCastleFR:
9056              case BlackHSideCastleFR:
9057                toX--;
9058                currentMoveString[2]--;
9059                break;
9060              default: ; // nothing to do, but suppresses warning of pedantic compilers
9061            }
9062         }
9063         hintRequested = FALSE;
9064         lastHint[0] = NULLCHAR;
9065         bookRequested = FALSE;
9066         /* Program may be pondering now */
9067         cps->maybeThinking = TRUE;
9068         if (cps->sendTime == 2) cps->sendTime = 1;
9069         if (cps->offeredDraw) cps->offeredDraw--;
9070
9071         /* [AS] Save move info*/
9072         pvInfoList[ forwardMostMove ].score = programStats.score;
9073         pvInfoList[ forwardMostMove ].depth = programStats.depth;
9074         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
9075
9076         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
9077
9078         /* Test suites abort the 'game' after one move */
9079         if(*appData.finger) {
9080            static FILE *f;
9081            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
9082            if(!f) f = fopen(appData.finger, "w");
9083            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
9084            else { DisplayFatalError("Bad output file", errno, 0); return; }
9085            free(fen);
9086            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9087         }
9088         if(appData.epd) {
9089            if(solvingTime >= 0) {
9090               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
9091               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
9092            } else {
9093               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
9094               if(solvingTime == -2) second.matchWins++;
9095            }
9096            OutputKibitz(2, buf1);
9097            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9098         }
9099
9100         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9101         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9102             int count = 0;
9103
9104             while( count < adjudicateLossPlies ) {
9105                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9106
9107                 if( count & 1 ) {
9108                     score = -score; /* Flip score for winning side */
9109                 }
9110
9111                 if( score > appData.adjudicateLossThreshold ) {
9112                     break;
9113                 }
9114
9115                 count++;
9116             }
9117
9118             if( count >= adjudicateLossPlies ) {
9119                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9120
9121                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9122                     "Xboard adjudication",
9123                     GE_XBOARD );
9124
9125                 return;
9126             }
9127         }
9128
9129         if(Adjudicate(cps)) {
9130             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9131             return; // [HGM] adjudicate: for all automatic game ends
9132         }
9133
9134 #if ZIPPY
9135         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9136             first.initDone) {
9137           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9138                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9139                 SendToICS("draw ");
9140                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9141           }
9142           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9143           ics_user_moved = 1;
9144           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9145                 char buf[3*MSG_SIZ];
9146
9147                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9148                         programStats.score / 100.,
9149                         programStats.depth,
9150                         programStats.time / 100.,
9151                         (unsigned int)programStats.nodes,
9152                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9153                         programStats.movelist);
9154                 SendToICS(buf);
9155           }
9156         }
9157 #endif
9158
9159         /* [AS] Clear stats for next move */
9160         ClearProgramStats();
9161         thinkOutput[0] = NULLCHAR;
9162         hiddenThinkOutputState = 0;
9163
9164         bookHit = NULL;
9165         if (gameMode == TwoMachinesPlay) {
9166             /* [HGM] relaying draw offers moved to after reception of move */
9167             /* and interpreting offer as claim if it brings draw condition */
9168             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9169                 SendToProgram("draw\n", cps->other);
9170             }
9171             if (cps->other->sendTime) {
9172                 SendTimeRemaining(cps->other,
9173                                   cps->other->twoMachinesColor[0] == 'w');
9174             }
9175             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9176             if (firstMove && !bookHit) {
9177                 firstMove = FALSE;
9178                 if (cps->other->useColors) {
9179                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9180                 }
9181                 SendToProgram("go\n", cps->other);
9182             }
9183             cps->other->maybeThinking = TRUE;
9184         }
9185
9186         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9187
9188         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9189
9190         if (!pausing && appData.ringBellAfterMoves) {
9191             if(!roar) RingBell();
9192         }
9193
9194         /*
9195          * Reenable menu items that were disabled while
9196          * machine was thinking
9197          */
9198         if (gameMode != TwoMachinesPlay)
9199             SetUserThinkingEnables();
9200
9201         // [HGM] book: after book hit opponent has received move and is now in force mode
9202         // force the book reply into it, and then fake that it outputted this move by jumping
9203         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9204         if(bookHit) {
9205                 static char bookMove[MSG_SIZ]; // a bit generous?
9206
9207                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9208                 strcat(bookMove, bookHit);
9209                 message = bookMove;
9210                 cps = cps->other;
9211                 programStats.nodes = programStats.depth = programStats.time =
9212                 programStats.score = programStats.got_only_move = 0;
9213                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9214
9215                 if(cps->lastPing != cps->lastPong) {
9216                     savedMessage = message; // args for deferred call
9217                     savedState = cps;
9218                     ScheduleDelayedEvent(DeferredBookMove, 10);
9219                     return;
9220                 }
9221                 goto FakeBookMove;
9222         }
9223
9224         return;
9225     }
9226
9227     /* Set special modes for chess engines.  Later something general
9228      *  could be added here; for now there is just one kludge feature,
9229      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9230      *  when "xboard" is given as an interactive command.
9231      */
9232     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9233         cps->useSigint = FALSE;
9234         cps->useSigterm = FALSE;
9235     }
9236     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9237       ParseFeatures(message+8, cps); if(tryNr < 3) tryNr = 3;
9238       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9239     }
9240
9241     if (!strncmp(message, "setup ", 6) && 
9242         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9243           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9244                                         ) { // [HGM] allow first engine to define opening position
9245       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9246       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9247       *buf = NULLCHAR;
9248       if(sscanf(message, "setup (%s", buf) == 1) {
9249         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9250         ASSIGN(appData.pieceToCharTable, buf);
9251       }
9252       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9253       if(dummy >= 3) {
9254         while(message[s] && message[s++] != ' ');
9255         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9256            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9257 //          if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9258             if(hand > h) handSize = hand; else handSize = h;
9259             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9260             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9261           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9262           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9263           startedFromSetupPosition = FALSE;
9264         }
9265       }
9266       if(startedFromSetupPosition) return;
9267       ParseFEN(boards[0], &dummy, message+s, FALSE);
9268       DrawPosition(TRUE, boards[0]);
9269       CopyBoard(initialPosition, boards[0]);
9270       startedFromSetupPosition = TRUE;
9271       return;
9272     }
9273     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9274       ChessSquare piece = WhitePawn;
9275       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9276       if(*p == '+') promoted++, ID = *++p;
9277       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9278       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9279       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9280       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9281       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9282       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9283       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9284       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9285                                                && gameInfo.variant != VariantGreat
9286                                                && gameInfo.variant != VariantFairy    ) return;
9287       if(piece < EmptySquare) {
9288         pieceDefs = TRUE;
9289         ASSIGN(pieceDesc[piece], buf1);
9290         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9291       }
9292       return;
9293     }
9294     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9295       if(deferChoice) {
9296         LeftClick(Press, 0, 0); // finish the click that was interrupted
9297       } else if(promoSweep != EmptySquare) {
9298         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9299         if(strlen(promoRestrict) > 1) Sweep(0);
9300       }
9301       return;
9302     }
9303     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9304      * want this, I was asked to put it in, and obliged.
9305      */
9306     if (!strncmp(message, "setboard ", 9)) {
9307         Board initial_position;
9308
9309         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9310
9311         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9312             DisplayError(_("Bad FEN received from engine"), 0);
9313             return ;
9314         } else {
9315            Reset(TRUE, FALSE);
9316            CopyBoard(boards[0], initial_position);
9317            initialRulePlies = FENrulePlies;
9318            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9319            else gameMode = MachinePlaysBlack;
9320            DrawPosition(FALSE, boards[currentMove]);
9321         }
9322         return;
9323     }
9324
9325     /*
9326      * Look for communication commands
9327      */
9328     if (!strncmp(message, "telluser ", 9)) {
9329         if(message[9] == '\\' && message[10] == '\\')
9330             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9331         PlayTellSound();
9332         DisplayNote(message + 9);
9333         return;
9334     }
9335     if (!strncmp(message, "tellusererror ", 14)) {
9336         cps->userError = 1;
9337         if(message[14] == '\\' && message[15] == '\\')
9338             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9339         PlayTellSound();
9340         DisplayError(message + 14, 0);
9341         return;
9342     }
9343     if (!strncmp(message, "tellopponent ", 13)) {
9344       if (appData.icsActive) {
9345         if (loggedOn) {
9346           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9347           SendToICS(buf1);
9348         }
9349       } else {
9350         DisplayNote(message + 13);
9351       }
9352       return;
9353     }
9354     if (!strncmp(message, "tellothers ", 11)) {
9355       if (appData.icsActive) {
9356         if (loggedOn) {
9357           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9358           SendToICS(buf1);
9359         }
9360       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9361       return;
9362     }
9363     if (!strncmp(message, "tellall ", 8)) {
9364       if (appData.icsActive) {
9365         if (loggedOn) {
9366           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9367           SendToICS(buf1);
9368         }
9369       } else {
9370         DisplayNote(message + 8);
9371       }
9372       return;
9373     }
9374     if (strncmp(message, "warning", 7) == 0) {
9375         /* Undocumented feature, use tellusererror in new code */
9376         DisplayError(message, 0);
9377         return;
9378     }
9379     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9380         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9381         strcat(realname, " query");
9382         AskQuestion(realname, buf2, buf1, cps->pr);
9383         return;
9384     }
9385     /* Commands from the engine directly to ICS.  We don't allow these to be
9386      *  sent until we are logged on. Crafty kibitzes have been known to
9387      *  interfere with the login process.
9388      */
9389     if (loggedOn) {
9390         if (!strncmp(message, "tellics ", 8)) {
9391             SendToICS(message + 8);
9392             SendToICS("\n");
9393             return;
9394         }
9395         if (!strncmp(message, "tellicsnoalias ", 15)) {
9396             SendToICS(ics_prefix);
9397             SendToICS(message + 15);
9398             SendToICS("\n");
9399             return;
9400         }
9401         /* The following are for backward compatibility only */
9402         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9403             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9404             SendToICS(ics_prefix);
9405             SendToICS(message);
9406             SendToICS("\n");
9407             return;
9408         }
9409     }
9410     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9411         if(initPing == cps->lastPong) {
9412             if(gameInfo.variant == VariantUnknown) {
9413                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9414                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9415                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9416             }
9417             initPing = -1;
9418         }
9419         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9420             abortEngineThink = FALSE;
9421             DisplayMessage("", "");
9422             ThawUI();
9423         }
9424         return;
9425     }
9426     if(!strncmp(message, "highlight ", 10)) {
9427         if(appData.testLegality && !*engineVariant && appData.markers) return;
9428         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9429         return;
9430     }
9431     if(!strncmp(message, "click ", 6)) {
9432         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9433         if(appData.testLegality || !appData.oneClick) return;
9434         sscanf(message+6, "%c%d%c", &f, &y, &c);
9435         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9436         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9437         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9438         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9439         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9440         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9441             LeftClick(Release, lastLeftX, lastLeftY);
9442         controlKey  = (c == ',');
9443         LeftClick(Press, x, y);
9444         LeftClick(Release, x, y);
9445         first.highlight = f;
9446         return;
9447     }
9448     if(strncmp(message, "uciok", 5) == 0) { // response to "uci" probe
9449         appData.isUCI[0] = isUCI = 1;
9450         ReplaceEngine(&first, 0); // retry install as UCI
9451         return;
9452     }
9453     /*
9454      * If the move is illegal, cancel it and redraw the board.
9455      * Also deal with other error cases.  Matching is rather loose
9456      * here to accommodate engines written before the spec.
9457      */
9458     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9459         strncmp(message, "Error", 5) == 0) {
9460         if (StrStr(message, "name") ||
9461             StrStr(message, "rating") || StrStr(message, "?") ||
9462             StrStr(message, "result") || StrStr(message, "board") ||
9463             StrStr(message, "bk") || StrStr(message, "computer") ||
9464             StrStr(message, "variant") || StrStr(message, "hint") ||
9465             StrStr(message, "random") || StrStr(message, "depth") ||
9466             StrStr(message, "accepted")) {
9467             return;
9468         }
9469         if (StrStr(message, "protover")) {
9470           /* Program is responding to input, so it's apparently done
9471              initializing, and this error message indicates it is
9472              protocol version 1.  So we don't need to wait any longer
9473              for it to initialize and send feature commands. */
9474           FeatureDone(cps, 1);
9475           cps->protocolVersion = 1;
9476           return;
9477         }
9478         cps->maybeThinking = FALSE;
9479
9480         if (StrStr(message, "draw")) {
9481             /* Program doesn't have "draw" command */
9482             cps->sendDrawOffers = 0;
9483             return;
9484         }
9485         if (cps->sendTime != 1 &&
9486             (StrStr(message, "time") || StrStr(message, "otim"))) {
9487           /* Program apparently doesn't have "time" or "otim" command */
9488           cps->sendTime = 0;
9489           return;
9490         }
9491         if (StrStr(message, "analyze")) {
9492             cps->analysisSupport = FALSE;
9493             cps->analyzing = FALSE;
9494 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9495             EditGameEvent(); // [HGM] try to preserve loaded game
9496             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9497             DisplayError(buf2, 0);
9498             return;
9499         }
9500         if (StrStr(message, "(no matching move)st")) {
9501           /* Special kludge for GNU Chess 4 only */
9502           cps->stKludge = TRUE;
9503           SendTimeControl(cps, movesPerSession, timeControl,
9504                           timeIncrement, appData.searchDepth,
9505                           searchTime);
9506           return;
9507         }
9508         if (StrStr(message, "(no matching move)sd")) {
9509           /* Special kludge for GNU Chess 4 only */
9510           cps->sdKludge = TRUE;
9511           SendTimeControl(cps, movesPerSession, timeControl,
9512                           timeIncrement, appData.searchDepth,
9513                           searchTime);
9514           return;
9515         }
9516         if (!StrStr(message, "llegal")) {
9517             return;
9518         }
9519         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9520             gameMode == IcsIdle) return;
9521         if (forwardMostMove <= backwardMostMove) return;
9522         if (pausing) PauseEvent();
9523       if(appData.forceIllegal) {
9524             // [HGM] illegal: machine refused move; force position after move into it
9525           SendToProgram("force\n", cps);
9526           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9527                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9528                 // when black is to move, while there might be nothing on a2 or black
9529                 // might already have the move. So send the board as if white has the move.
9530                 // But first we must change the stm of the engine, as it refused the last move
9531                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9532                 if(WhiteOnMove(forwardMostMove)) {
9533                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9534                     SendBoard(cps, forwardMostMove); // kludgeless board
9535                 } else {
9536                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9537                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9538                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9539                 }
9540           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9541             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9542                  gameMode == TwoMachinesPlay)
9543               SendToProgram("go\n", cps);
9544             return;
9545       } else
9546         if (gameMode == PlayFromGameFile) {
9547             /* Stop reading this game file */
9548             gameMode = EditGame;
9549             ModeHighlight();
9550         }
9551         /* [HGM] illegal-move claim should forfeit game when Xboard */
9552         /* only passes fully legal moves                            */
9553         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9554             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9555                                 "False illegal-move claim", GE_XBOARD );
9556             return; // do not take back move we tested as valid
9557         }
9558         currentMove = forwardMostMove-1;
9559         DisplayMove(currentMove-1); /* before DisplayMoveError */
9560         SwitchClocks(forwardMostMove-1); // [HGM] race
9561         DisplayBothClocks();
9562         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9563                 parseList[currentMove], _(cps->which));
9564         DisplayMoveError(buf1);
9565         DrawPosition(FALSE, boards[currentMove]);
9566
9567         SetUserThinkingEnables();
9568         return;
9569     }
9570     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9571         /* Program has a broken "time" command that
9572            outputs a string not ending in newline.
9573            Don't use it. */
9574         cps->sendTime = 0;
9575     }
9576     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9577         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9578             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9579     }
9580
9581     /*
9582      * If chess program startup fails, exit with an error message.
9583      * Attempts to recover here are futile. [HGM] Well, we try anyway
9584      */
9585     if ((StrStr(message, "unknown host") != NULL)
9586         || (StrStr(message, "No remote directory") != NULL)
9587         || (StrStr(message, "not found") != NULL)
9588         || (StrStr(message, "No such file") != NULL)
9589         || (StrStr(message, "can't alloc") != NULL)
9590         || (StrStr(message, "Permission denied") != NULL)) {
9591
9592         cps->maybeThinking = FALSE;
9593         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9594                 _(cps->which), cps->program, cps->host, message);
9595         RemoveInputSource(cps->isr);
9596         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9597             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9598             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9599         }
9600         return;
9601     }
9602
9603     /*
9604      * Look for hint output
9605      */
9606     if (sscanf(message, "Hint: %s", buf1) == 1) {
9607         if (cps == &first && hintRequested) {
9608             hintRequested = FALSE;
9609             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9610                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9611                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9612                                     PosFlags(forwardMostMove),
9613                                     fromY, fromX, toY, toX, promoChar, buf1);
9614                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9615                 DisplayInformation(buf2);
9616             } else {
9617                 /* Hint move could not be parsed!? */
9618               snprintf(buf2, sizeof(buf2),
9619                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9620                         buf1, _(cps->which));
9621                 DisplayError(buf2, 0);
9622             }
9623         } else {
9624           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9625         }
9626         return;
9627     }
9628
9629     /*
9630      * Ignore other messages if game is not in progress
9631      */
9632     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9633         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9634
9635     /*
9636      * look for win, lose, draw, or draw offer
9637      */
9638     if (strncmp(message, "1-0", 3) == 0) {
9639         char *p, *q, *r = "";
9640         p = strchr(message, '{');
9641         if (p) {
9642             q = strchr(p, '}');
9643             if (q) {
9644                 *q = NULLCHAR;
9645                 r = p + 1;
9646             }
9647         }
9648         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9649         return;
9650     } else if (strncmp(message, "0-1", 3) == 0) {
9651         char *p, *q, *r = "";
9652         p = strchr(message, '{');
9653         if (p) {
9654             q = strchr(p, '}');
9655             if (q) {
9656                 *q = NULLCHAR;
9657                 r = p + 1;
9658             }
9659         }
9660         /* Kludge for Arasan 4.1 bug */
9661         if (strcmp(r, "Black resigns") == 0) {
9662             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9663             return;
9664         }
9665         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9666         return;
9667     } else if (strncmp(message, "1/2", 3) == 0) {
9668         char *p, *q, *r = "";
9669         p = strchr(message, '{');
9670         if (p) {
9671             q = strchr(p, '}');
9672             if (q) {
9673                 *q = NULLCHAR;
9674                 r = p + 1;
9675             }
9676         }
9677
9678         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9679         return;
9680
9681     } else if (strncmp(message, "White resign", 12) == 0) {
9682         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9683         return;
9684     } else if (strncmp(message, "Black resign", 12) == 0) {
9685         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9686         return;
9687     } else if (strncmp(message, "White matches", 13) == 0 ||
9688                strncmp(message, "Black matches", 13) == 0   ) {
9689         /* [HGM] ignore GNUShogi noises */
9690         return;
9691     } else if (strncmp(message, "White", 5) == 0 &&
9692                message[5] != '(' &&
9693                StrStr(message, "Black") == NULL) {
9694         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9695         return;
9696     } else if (strncmp(message, "Black", 5) == 0 &&
9697                message[5] != '(') {
9698         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9699         return;
9700     } else if (strcmp(message, "resign") == 0 ||
9701                strcmp(message, "computer resigns") == 0) {
9702         switch (gameMode) {
9703           case MachinePlaysBlack:
9704           case IcsPlayingBlack:
9705             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9706             break;
9707           case MachinePlaysWhite:
9708           case IcsPlayingWhite:
9709             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9710             break;
9711           case TwoMachinesPlay:
9712             if (cps->twoMachinesColor[0] == 'w')
9713               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9714             else
9715               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9716             break;
9717           default:
9718             /* can't happen */
9719             break;
9720         }
9721         return;
9722     } else if (strncmp(message, "opponent mates", 14) == 0) {
9723         switch (gameMode) {
9724           case MachinePlaysBlack:
9725           case IcsPlayingBlack:
9726             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9727             break;
9728           case MachinePlaysWhite:
9729           case IcsPlayingWhite:
9730             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9731             break;
9732           case TwoMachinesPlay:
9733             if (cps->twoMachinesColor[0] == 'w')
9734               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9735             else
9736               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9737             break;
9738           default:
9739             /* can't happen */
9740             break;
9741         }
9742         return;
9743     } else if (strncmp(message, "computer mates", 14) == 0) {
9744         switch (gameMode) {
9745           case MachinePlaysBlack:
9746           case IcsPlayingBlack:
9747             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9748             break;
9749           case MachinePlaysWhite:
9750           case IcsPlayingWhite:
9751             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9752             break;
9753           case TwoMachinesPlay:
9754             if (cps->twoMachinesColor[0] == 'w')
9755               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9756             else
9757               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9758             break;
9759           default:
9760             /* can't happen */
9761             break;
9762         }
9763         return;
9764     } else if (strncmp(message, "checkmate", 9) == 0) {
9765         if (WhiteOnMove(forwardMostMove)) {
9766             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9767         } else {
9768             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9769         }
9770         return;
9771     } else if (strstr(message, "Draw") != NULL ||
9772                strstr(message, "game is a draw") != NULL) {
9773         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9774         return;
9775     } else if (strstr(message, "offer") != NULL &&
9776                strstr(message, "draw") != NULL) {
9777 #if ZIPPY
9778         if (appData.zippyPlay && first.initDone) {
9779             /* Relay offer to ICS */
9780             SendToICS(ics_prefix);
9781             SendToICS("draw\n");
9782         }
9783 #endif
9784         cps->offeredDraw = 2; /* valid until this engine moves twice */
9785         if (gameMode == TwoMachinesPlay) {
9786             if (cps->other->offeredDraw) {
9787                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9788             /* [HGM] in two-machine mode we delay relaying draw offer      */
9789             /* until after we also have move, to see if it is really claim */
9790             }
9791         } else if (gameMode == MachinePlaysWhite ||
9792                    gameMode == MachinePlaysBlack) {
9793           if (userOfferedDraw) {
9794             DisplayInformation(_("Machine accepts your draw offer"));
9795             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9796           } else {
9797             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9798           }
9799         }
9800     }
9801
9802
9803     /*
9804      * Look for thinking output
9805      */
9806     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9807           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9808                                 ) {
9809         int plylev, mvleft, mvtot, curscore, time;
9810         char mvname[MOVE_LEN];
9811         u64 nodes; // [DM]
9812         char plyext;
9813         int ignore = FALSE;
9814         int prefixHint = FALSE;
9815         mvname[0] = NULLCHAR;
9816
9817         switch (gameMode) {
9818           case MachinePlaysBlack:
9819           case IcsPlayingBlack:
9820             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9821             break;
9822           case MachinePlaysWhite:
9823           case IcsPlayingWhite:
9824             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9825             break;
9826           case AnalyzeMode:
9827           case AnalyzeFile:
9828             break;
9829           case IcsObserving: /* [DM] icsEngineAnalyze */
9830             if (!appData.icsEngineAnalyze) ignore = TRUE;
9831             break;
9832           case TwoMachinesPlay:
9833             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9834                 ignore = TRUE;
9835             }
9836             break;
9837           default:
9838             ignore = TRUE;
9839             break;
9840         }
9841
9842         if (!ignore) {
9843             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9844             int solved = 0;
9845             buf1[0] = NULLCHAR;
9846             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9847                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9848                 char score_buf[MSG_SIZ];
9849
9850                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9851                     nodes += u64Const(0x100000000);
9852
9853                 if (plyext != ' ' && plyext != '\t') {
9854                     time *= 100;
9855                 }
9856
9857                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9858                 if( cps->scoreIsAbsolute &&
9859                     ( gameMode == MachinePlaysBlack ||
9860                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9861                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9862                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9863                      !WhiteOnMove(currentMove)
9864                     ) )
9865                 {
9866                     curscore = -curscore;
9867                 }
9868
9869                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9870
9871                 if(*bestMove) { // rememer time best EPD move was first found
9872                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9873                     ChessMove mt; char *p = bestMove;
9874                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9875                     solved = 0;
9876                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9877                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9878                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9879                             solved = 1;
9880                             break;
9881                         }
9882                         while(*p && *p != ' ') p++;
9883                         while(*p == ' ') p++;
9884                     }
9885                     if(!solved) solvingTime = -1;
9886                 }
9887                 if(*avoidMove && !solved) {
9888                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9889                     ChessMove mt; char *p = avoidMove, solved = 1;
9890                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9891                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9892                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9893                             solved = 0; solvingTime = -2;
9894                             break;
9895                         }
9896                         while(*p && *p != ' ') p++;
9897                         while(*p == ' ') p++;
9898                     }
9899                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9900                 }
9901
9902                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9903                         char buf[MSG_SIZ];
9904                         FILE *f;
9905                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9906                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9907                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9908                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9909                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9910                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9911                                 fclose(f);
9912                         }
9913                         else
9914                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9915                           DisplayError(_("failed writing PV"), 0);
9916                 }
9917
9918                 tempStats.depth = plylev;
9919                 tempStats.nodes = nodes;
9920                 tempStats.time = time;
9921                 tempStats.score = curscore;
9922                 tempStats.got_only_move = 0;
9923
9924                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9925                         int ticklen;
9926
9927                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9928                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9929                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9930                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9931                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9932                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9933                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9934                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9935                 }
9936
9937                 /* Buffer overflow protection */
9938                 if (pv[0] != NULLCHAR) {
9939                     if (strlen(pv) >= sizeof(tempStats.movelist)
9940                         && appData.debugMode) {
9941                         fprintf(debugFP,
9942                                 "PV is too long; using the first %u bytes.\n",
9943                                 (unsigned) sizeof(tempStats.movelist) - 1);
9944                     }
9945
9946                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9947                 } else {
9948                     sprintf(tempStats.movelist, " no PV\n");
9949                 }
9950
9951                 if (tempStats.seen_stat) {
9952                     tempStats.ok_to_send = 1;
9953                 }
9954
9955                 if (strchr(tempStats.movelist, '(') != NULL) {
9956                     tempStats.line_is_book = 1;
9957                     tempStats.nr_moves = 0;
9958                     tempStats.moves_left = 0;
9959                 } else {
9960                     tempStats.line_is_book = 0;
9961                 }
9962
9963                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9964                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9965
9966                 SendProgramStatsToFrontend( cps, &tempStats );
9967
9968                 /*
9969                     [AS] Protect the thinkOutput buffer from overflow... this
9970                     is only useful if buf1 hasn't overflowed first!
9971                 */
9972                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9973                 if(curscore >= MATE_SCORE) 
9974                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9975                 else if(curscore <= -MATE_SCORE) 
9976                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9977                 else
9978                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9979                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9980                          plylev,
9981                          (gameMode == TwoMachinesPlay ?
9982                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9983                          score_buf,
9984                          prefixHint ? lastHint : "",
9985                          prefixHint ? " " : "" );
9986
9987                 if( buf1[0] != NULLCHAR ) {
9988                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9989
9990                     if( strlen(pv) > max_len ) {
9991                         if( appData.debugMode) {
9992                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9993                         }
9994                         pv[max_len+1] = '\0';
9995                     }
9996
9997                     strcat( thinkOutput, pv);
9998                 }
9999
10000                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
10001                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10002                     DisplayMove(currentMove - 1);
10003                 }
10004                 return;
10005
10006             } else if ((p=StrStr(message, "(only move)")) != NULL) {
10007                 /* crafty (9.25+) says "(only move) <move>"
10008                  * if there is only 1 legal move
10009                  */
10010                 sscanf(p, "(only move) %s", buf1);
10011                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
10012                 sprintf(programStats.movelist, "%s (only move)", buf1);
10013                 programStats.depth = 1;
10014                 programStats.nr_moves = 1;
10015                 programStats.moves_left = 1;
10016                 programStats.nodes = 1;
10017                 programStats.time = 1;
10018                 programStats.got_only_move = 1;
10019
10020                 /* Not really, but we also use this member to
10021                    mean "line isn't going to change" (Crafty
10022                    isn't searching, so stats won't change) */
10023                 programStats.line_is_book = 1;
10024
10025                 SendProgramStatsToFrontend( cps, &programStats );
10026
10027                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10028                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10029                     DisplayMove(currentMove - 1);
10030                 }
10031                 return;
10032             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
10033                               &time, &nodes, &plylev, &mvleft,
10034                               &mvtot, mvname) >= 5) {
10035                 /* The stat01: line is from Crafty (9.29+) in response
10036                    to the "." command */
10037                 programStats.seen_stat = 1;
10038                 cps->maybeThinking = TRUE;
10039
10040                 if (programStats.got_only_move || !appData.periodicUpdates)
10041                   return;
10042
10043                 programStats.depth = plylev;
10044                 programStats.time = time;
10045                 programStats.nodes = nodes;
10046                 programStats.moves_left = mvleft;
10047                 programStats.nr_moves = mvtot;
10048                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
10049                 programStats.ok_to_send = 1;
10050                 programStats.movelist[0] = '\0';
10051
10052                 SendProgramStatsToFrontend( cps, &programStats );
10053
10054                 return;
10055
10056             } else if (strncmp(message,"++",2) == 0) {
10057                 /* Crafty 9.29+ outputs this */
10058                 programStats.got_fail = 2;
10059                 return;
10060
10061             } else if (strncmp(message,"--",2) == 0) {
10062                 /* Crafty 9.29+ outputs this */
10063                 programStats.got_fail = 1;
10064                 return;
10065
10066             } else if (thinkOutput[0] != NULLCHAR &&
10067                        strncmp(message, "    ", 4) == 0) {
10068                 unsigned message_len;
10069
10070                 p = message;
10071                 while (*p && *p == ' ') p++;
10072
10073                 message_len = strlen( p );
10074
10075                 /* [AS] Avoid buffer overflow */
10076                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
10077                     strcat(thinkOutput, " ");
10078                     strcat(thinkOutput, p);
10079                 }
10080
10081                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
10082                     strcat(programStats.movelist, " ");
10083                     strcat(programStats.movelist, p);
10084                 }
10085
10086                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10087                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10088                     DisplayMove(currentMove - 1);
10089                 }
10090                 return;
10091             }
10092         }
10093         else {
10094             buf1[0] = NULLCHAR;
10095
10096             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
10097                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
10098             {
10099                 ChessProgramStats cpstats;
10100
10101                 if (plyext != ' ' && plyext != '\t') {
10102                     time *= 100;
10103                 }
10104
10105                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10106                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10107                     curscore = -curscore;
10108                 }
10109
10110                 cpstats.depth = plylev;
10111                 cpstats.nodes = nodes;
10112                 cpstats.time = time;
10113                 cpstats.score = curscore;
10114                 cpstats.got_only_move = 0;
10115                 cpstats.movelist[0] = '\0';
10116
10117                 if (buf1[0] != NULLCHAR) {
10118                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10119                 }
10120
10121                 cpstats.ok_to_send = 0;
10122                 cpstats.line_is_book = 0;
10123                 cpstats.nr_moves = 0;
10124                 cpstats.moves_left = 0;
10125
10126                 SendProgramStatsToFrontend( cps, &cpstats );
10127             }
10128         }
10129     }
10130 }
10131
10132
10133 /* Parse a game score from the character string "game", and
10134    record it as the history of the current game.  The game
10135    score is NOT assumed to start from the standard position.
10136    The display is not updated in any way.
10137    */
10138 void
10139 ParseGameHistory (char *game)
10140 {
10141     ChessMove moveType;
10142     int fromX, fromY, toX, toY, boardIndex, mask;
10143     char promoChar;
10144     char *p, *q;
10145     char buf[MSG_SIZ];
10146
10147     if (appData.debugMode)
10148       fprintf(debugFP, "Parsing game history: %s\n", game);
10149
10150     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10151     gameInfo.site = StrSave(appData.icsHost);
10152     gameInfo.date = PGNDate();
10153     gameInfo.round = StrSave("-");
10154
10155     /* Parse out names of players */
10156     while (*game == ' ') game++;
10157     p = buf;
10158     while (*game != ' ') *p++ = *game++;
10159     *p = NULLCHAR;
10160     gameInfo.white = StrSave(buf);
10161     while (*game == ' ') game++;
10162     p = buf;
10163     while (*game != ' ' && *game != '\n') *p++ = *game++;
10164     *p = NULLCHAR;
10165     gameInfo.black = StrSave(buf);
10166
10167     /* Parse moves */
10168     boardIndex = blackPlaysFirst ? 1 : 0;
10169     yynewstr(game);
10170     for (;;) {
10171         yyboardindex = boardIndex;
10172         moveType = (ChessMove) Myylex();
10173         switch (moveType) {
10174           case IllegalMove:             /* maybe suicide chess, etc. */
10175   if (appData.debugMode) {
10176     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10177     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10178     setbuf(debugFP, NULL);
10179   }
10180           case WhitePromotion:
10181           case BlackPromotion:
10182           case WhiteNonPromotion:
10183           case BlackNonPromotion:
10184           case NormalMove:
10185           case FirstLeg:
10186           case WhiteCapturesEnPassant:
10187           case BlackCapturesEnPassant:
10188           case WhiteKingSideCastle:
10189           case WhiteQueenSideCastle:
10190           case BlackKingSideCastle:
10191           case BlackQueenSideCastle:
10192           case WhiteKingSideCastleWild:
10193           case WhiteQueenSideCastleWild:
10194           case BlackKingSideCastleWild:
10195           case BlackQueenSideCastleWild:
10196           /* PUSH Fabien */
10197           case WhiteHSideCastleFR:
10198           case WhiteASideCastleFR:
10199           case BlackHSideCastleFR:
10200           case BlackASideCastleFR:
10201           /* POP Fabien */
10202             fromX = currentMoveString[0] - AAA;
10203             fromY = currentMoveString[1] - ONE;
10204             toX = currentMoveString[2] - AAA;
10205             toY = currentMoveString[3] - ONE;
10206             promoChar = currentMoveString[4];
10207             break;
10208           case WhiteDrop:
10209           case BlackDrop:
10210             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10211             fromX = moveType == WhiteDrop ?
10212               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10213             (int) CharToPiece(ToLower(currentMoveString[0]));
10214             fromY = DROP_RANK;
10215             toX = currentMoveString[2] - AAA;
10216             toY = currentMoveString[3] - ONE;
10217             promoChar = NULLCHAR;
10218             break;
10219           case AmbiguousMove:
10220             /* bug? */
10221             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10222   if (appData.debugMode) {
10223     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10224     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10225     setbuf(debugFP, NULL);
10226   }
10227             DisplayError(buf, 0);
10228             return;
10229           case ImpossibleMove:
10230             /* bug? */
10231             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10232   if (appData.debugMode) {
10233     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10234     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10235     setbuf(debugFP, NULL);
10236   }
10237             DisplayError(buf, 0);
10238             return;
10239           case EndOfFile:
10240             if (boardIndex < backwardMostMove) {
10241                 /* Oops, gap.  How did that happen? */
10242                 DisplayError(_("Gap in move list"), 0);
10243                 return;
10244             }
10245             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10246             if (boardIndex > forwardMostMove) {
10247                 forwardMostMove = boardIndex;
10248             }
10249             return;
10250           case ElapsedTime:
10251             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10252                 strcat(parseList[boardIndex-1], " ");
10253                 strcat(parseList[boardIndex-1], yy_text);
10254             }
10255             continue;
10256           case Comment:
10257           case PGNTag:
10258           case NAG:
10259           default:
10260             /* ignore */
10261             continue;
10262           case WhiteWins:
10263           case BlackWins:
10264           case GameIsDrawn:
10265           case GameUnfinished:
10266             if (gameMode == IcsExamining) {
10267                 if (boardIndex < backwardMostMove) {
10268                     /* Oops, gap.  How did that happen? */
10269                     return;
10270                 }
10271                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10272                 return;
10273             }
10274             gameInfo.result = moveType;
10275             p = strchr(yy_text, '{');
10276             if (p == NULL) p = strchr(yy_text, '(');
10277             if (p == NULL) {
10278                 p = yy_text;
10279                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10280             } else {
10281                 q = strchr(p, *p == '{' ? '}' : ')');
10282                 if (q != NULL) *q = NULLCHAR;
10283                 p++;
10284             }
10285             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10286             gameInfo.resultDetails = StrSave(p);
10287             continue;
10288         }
10289         if (boardIndex >= forwardMostMove &&
10290             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10291             backwardMostMove = blackPlaysFirst ? 1 : 0;
10292             return;
10293         }
10294         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10295                                  fromY, fromX, toY, toX, promoChar,
10296                                  parseList[boardIndex]);
10297         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10298         /* currentMoveString is set as a side-effect of yylex */
10299         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10300         strcat(moveList[boardIndex], "\n");
10301         boardIndex++;
10302         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10303         mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10304         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10305           case MT_NONE:
10306           case MT_STALEMATE:
10307           default:
10308             break;
10309           case MT_CHECK:
10310             if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10311             if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10312                 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10313                 break;
10314             }
10315           case MT_CHECKMATE:
10316           case MT_STAINMATE:
10317             strcat(parseList[boardIndex - 1], "#");
10318             break;
10319         }
10320     }
10321 }
10322
10323
10324 /* Apply a move to the given board  */
10325 void
10326 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10327 {
10328   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10329   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10330
10331     /* [HGM] compute & store e.p. status and castling rights for new position */
10332     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10333
10334       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10335       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10336       board[EP_STATUS] = EP_NONE;
10337       board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10338
10339   if (fromY == DROP_RANK) {
10340         /* must be first */
10341         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10342             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10343             return;
10344         }
10345         piece = board[toY][toX] = (ChessSquare) fromX;
10346   } else {
10347 //      ChessSquare victim;
10348       int i;
10349
10350       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10351 //           victim = board[killY][killX],
10352            killed = board[killY][killX],
10353            board[killY][killX] = EmptySquare,
10354            board[EP_STATUS] = EP_CAPTURE;
10355            if( kill2X >= 0 && kill2Y >= 0)
10356              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10357       }
10358
10359       if( board[toY][toX] != EmptySquare ) {
10360            board[EP_STATUS] = EP_CAPTURE;
10361            if( (fromX != toX || fromY != toY) && // not igui!
10362                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10363                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10364                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10365            }
10366       }
10367
10368       pawn = board[fromY][fromX];
10369       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10370         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10371             captured = board[lastRank][lastFile]; // remove victim
10372             board[lastRank][lastFile] = EmptySquare;
10373             pawn = EmptySquare; // kludge to suppress old e.p. code
10374         }
10375       }
10376       if( pawn == WhiteLance || pawn == BlackLance ) {
10377            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10378                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10379                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10380            }
10381       }
10382       if( pawn == WhitePawn ) {
10383            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10384                board[EP_STATUS] = EP_PAWN_MOVE;
10385            if( toY-fromY>=2) {
10386                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10387                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10388                         gameInfo.variant != VariantBerolina || toX < fromX)
10389                       board[EP_STATUS] = toX | berolina;
10390                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10391                         gameInfo.variant != VariantBerolina || toX > fromX)
10392                       board[EP_STATUS] = toX;
10393                board[LAST_TO] = toX + 256*toY;
10394            }
10395       } else
10396       if( pawn == BlackPawn ) {
10397            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10398                board[EP_STATUS] = EP_PAWN_MOVE;
10399            if( toY-fromY<= -2) {
10400                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10401                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10402                         gameInfo.variant != VariantBerolina || toX < fromX)
10403                       board[EP_STATUS] = toX | berolina;
10404                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10405                         gameInfo.variant != VariantBerolina || toX > fromX)
10406                       board[EP_STATUS] = toX;
10407                board[LAST_TO] = toX + 256*toY;
10408            }
10409        }
10410
10411        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10412        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10413        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10414        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10415
10416        for(i=0; i<nrCastlingRights; i++) {
10417            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10418               board[CASTLING][i] == toX   && castlingRank[i] == toY
10419              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10420        }
10421
10422        if(gameInfo.variant == VariantSChess) { // update virginity
10423            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10424            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10425            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10426            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10427        }
10428
10429      if (fromX == toX && fromY == toY && killX < 0) return;
10430
10431      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10432      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10433      if(gameInfo.variant == VariantKnightmate)
10434          king += (int) WhiteUnicorn - (int) WhiteKing;
10435
10436     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10437        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10438         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10439         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10440         board[EP_STATUS] = EP_NONE; // capture was fake!
10441     } else
10442     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10443         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10444         board[toY][toX] = piece;
10445         board[EP_STATUS] = EP_NONE; // capture was fake!
10446     } else
10447     /* Code added by Tord: */
10448     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10449     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10450         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10451       board[EP_STATUS] = EP_NONE; // capture was fake!
10452       board[fromY][fromX] = EmptySquare;
10453       board[toY][toX] = EmptySquare;
10454       if((toX > fromX) != (piece == WhiteRook)) {
10455         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10456       } else {
10457         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10458       }
10459     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10460                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10461       board[EP_STATUS] = EP_NONE;
10462       board[fromY][fromX] = EmptySquare;
10463       board[toY][toX] = EmptySquare;
10464       if((toX > fromX) != (piece == BlackRook)) {
10465         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10466       } else {
10467         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10468       }
10469     /* End of code added by Tord */
10470
10471     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10472         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10473         board[toY][toX] = piece;
10474     } else if (board[fromY][fromX] == king
10475         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10476         && toY == fromY && toX > fromX+1) {
10477         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10478                                                                                              ; // castle with nearest piece
10479         board[fromY][toX-1] = board[fromY][rookX];
10480         board[fromY][rookX] = EmptySquare;
10481         board[fromY][fromX] = EmptySquare;
10482         board[toY][toX] = king;
10483     } else if (board[fromY][fromX] == king
10484         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10485                && toY == fromY && toX < fromX-1) {
10486         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10487                                                                                   ; // castle with nearest piece
10488         board[fromY][toX+1] = board[fromY][rookX];
10489         board[fromY][rookX] = EmptySquare;
10490         board[fromY][fromX] = EmptySquare;
10491         board[toY][toX] = king;
10492     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10493                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10494                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10495                ) {
10496         /* white pawn promotion */
10497         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10498         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10499             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10500         board[fromY][fromX] = EmptySquare;
10501     } else if ((fromY >= BOARD_HEIGHT>>1)
10502                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10503                && (toX != fromX)
10504                && gameInfo.variant != VariantXiangqi
10505                && gameInfo.variant != VariantBerolina
10506                && (pawn == WhitePawn)
10507                && (board[toY][toX] == EmptySquare)) {
10508         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10509         board[fromY][fromX] = EmptySquare;
10510         board[toY][toX] = piece;
10511         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10512     } else if ((fromY == BOARD_HEIGHT-4)
10513                && (toX == fromX)
10514                && gameInfo.variant == VariantBerolina
10515                && (board[fromY][fromX] == WhitePawn)
10516                && (board[toY][toX] == EmptySquare)) {
10517         board[fromY][fromX] = EmptySquare;
10518         board[toY][toX] = WhitePawn;
10519         if(oldEP & EP_BEROLIN_A) {
10520                 captured = board[fromY][fromX-1];
10521                 board[fromY][fromX-1] = EmptySquare;
10522         }else{  captured = board[fromY][fromX+1];
10523                 board[fromY][fromX+1] = EmptySquare;
10524         }
10525     } else if (board[fromY][fromX] == king
10526         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10527                && toY == fromY && toX > fromX+1) {
10528         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10529                                                                                              ;
10530         board[fromY][toX-1] = board[fromY][rookX];
10531         board[fromY][rookX] = EmptySquare;
10532         board[fromY][fromX] = EmptySquare;
10533         board[toY][toX] = king;
10534     } else if (board[fromY][fromX] == king
10535         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10536                && toY == fromY && toX < fromX-1) {
10537         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10538                                                                                 ;
10539         board[fromY][toX+1] = board[fromY][rookX];
10540         board[fromY][rookX] = EmptySquare;
10541         board[fromY][fromX] = EmptySquare;
10542         board[toY][toX] = king;
10543     } else if (fromY == 7 && fromX == 3
10544                && board[fromY][fromX] == BlackKing
10545                && toY == 7 && toX == 5) {
10546         board[fromY][fromX] = EmptySquare;
10547         board[toY][toX] = BlackKing;
10548         board[fromY][7] = EmptySquare;
10549         board[toY][4] = BlackRook;
10550     } else if (fromY == 7 && fromX == 3
10551                && board[fromY][fromX] == BlackKing
10552                && toY == 7 && toX == 1) {
10553         board[fromY][fromX] = EmptySquare;
10554         board[toY][toX] = BlackKing;
10555         board[fromY][0] = EmptySquare;
10556         board[toY][2] = BlackRook;
10557     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10558                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10559                && toY < promoRank && promoChar
10560                ) {
10561         /* black pawn promotion */
10562         board[toY][toX] = CharToPiece(ToLower(promoChar));
10563         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10564             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10565         board[fromY][fromX] = EmptySquare;
10566     } else if ((fromY < BOARD_HEIGHT>>1)
10567                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10568                && (toX != fromX)
10569                && gameInfo.variant != VariantXiangqi
10570                && gameInfo.variant != VariantBerolina
10571                && (pawn == BlackPawn)
10572                && (board[toY][toX] == EmptySquare)) {
10573         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10574         board[fromY][fromX] = EmptySquare;
10575         board[toY][toX] = piece;
10576         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10577     } else if ((fromY == 3)
10578                && (toX == fromX)
10579                && gameInfo.variant == VariantBerolina
10580                && (board[fromY][fromX] == BlackPawn)
10581                && (board[toY][toX] == EmptySquare)) {
10582         board[fromY][fromX] = EmptySquare;
10583         board[toY][toX] = BlackPawn;
10584         if(oldEP & EP_BEROLIN_A) {
10585                 captured = board[fromY][fromX-1];
10586                 board[fromY][fromX-1] = EmptySquare;
10587         }else{  captured = board[fromY][fromX+1];
10588                 board[fromY][fromX+1] = EmptySquare;
10589         }
10590     } else {
10591         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10592         board[fromY][fromX] = EmptySquare;
10593         board[toY][toX] = piece;
10594     }
10595   }
10596
10597     if (gameInfo.holdingsWidth != 0) {
10598
10599       /* !!A lot more code needs to be written to support holdings  */
10600       /* [HGM] OK, so I have written it. Holdings are stored in the */
10601       /* penultimate board files, so they are automaticlly stored   */
10602       /* in the game history.                                       */
10603       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10604                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10605         /* Delete from holdings, by decreasing count */
10606         /* and erasing image if necessary            */
10607         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10608         if(p < (int) BlackPawn) { /* white drop */
10609              p -= (int)WhitePawn;
10610                  p = PieceToNumber((ChessSquare)p);
10611              if(p >= gameInfo.holdingsSize) p = 0;
10612              if(--board[p][BOARD_WIDTH-2] <= 0)
10613                   board[p][BOARD_WIDTH-1] = EmptySquare;
10614              if((int)board[p][BOARD_WIDTH-2] < 0)
10615                         board[p][BOARD_WIDTH-2] = 0;
10616         } else {                  /* black drop */
10617              p -= (int)BlackPawn;
10618                  p = PieceToNumber((ChessSquare)p);
10619              if(p >= gameInfo.holdingsSize) p = 0;
10620              if(--board[handSize-1-p][1] <= 0)
10621                   board[handSize-1-p][0] = EmptySquare;
10622              if((int)board[handSize-1-p][1] < 0)
10623                         board[handSize-1-p][1] = 0;
10624         }
10625       }
10626       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10627           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10628         /* [HGM] holdings: Add to holdings, if holdings exist */
10629         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10630                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10631                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10632         }
10633         p = (int) captured;
10634         if (p >= (int) BlackPawn) {
10635           p -= (int)BlackPawn;
10636           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10637                   /* Restore shogi-promoted piece to its original  first */
10638                   captured = (ChessSquare) (DEMOTED(captured));
10639                   p = DEMOTED(p);
10640           }
10641           p = PieceToNumber((ChessSquare)p);
10642           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10643           board[p][BOARD_WIDTH-2]++;
10644           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10645         } else {
10646           p -= (int)WhitePawn;
10647           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10648                   captured = (ChessSquare) (DEMOTED(captured));
10649                   p = DEMOTED(p);
10650           }
10651           p = PieceToNumber((ChessSquare)p);
10652           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10653           board[handSize-1-p][1]++;
10654           board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10655         }
10656       }
10657     } else if (gameInfo.variant == VariantAtomic) {
10658       if (captured != EmptySquare) {
10659         int y, x;
10660         for (y = toY-1; y <= toY+1; y++) {
10661           for (x = toX-1; x <= toX+1; x++) {
10662             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10663                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10664               board[y][x] = EmptySquare;
10665             }
10666           }
10667         }
10668         board[toY][toX] = EmptySquare;
10669       }
10670     }
10671
10672     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10673         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10674     } else
10675     if(promoChar == '+') {
10676         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10677         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10678         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10679           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10680     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10681         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10682         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10683            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10684         board[toY][toX] = newPiece;
10685     }
10686     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10687                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10688         // [HGM] superchess: take promotion piece out of holdings
10689         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10690         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10691             if(!--board[k][BOARD_WIDTH-2])
10692                 board[k][BOARD_WIDTH-1] = EmptySquare;
10693         } else {
10694             if(!--board[handSize-1-k][1])
10695                 board[handSize-1-k][0] = EmptySquare;
10696         }
10697     }
10698 }
10699
10700 /* Updates forwardMostMove */
10701 void
10702 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10703 {
10704     int x = toX, y = toY, mask;
10705     char *s = parseList[forwardMostMove];
10706     ChessSquare p = boards[forwardMostMove][toY][toX];
10707 //    forwardMostMove++; // [HGM] bare: moved downstream
10708
10709     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10710     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10711     (void) CoordsToAlgebraic(boards[forwardMostMove],
10712                              PosFlags(forwardMostMove),
10713                              fromY, fromX, y, x, (killX < 0)*promoChar,
10714                              s);
10715     if(kill2X >= 0 && kill2Y >= 0)
10716         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10717     if(killX >= 0 && killY >= 0)
10718         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10719                                            toX + AAA, toY + ONE - '0', promoChar);
10720
10721     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10722         int timeLeft; static int lastLoadFlag=0; int king, piece;
10723         piece = boards[forwardMostMove][fromY][fromX];
10724         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10725         if(gameInfo.variant == VariantKnightmate)
10726             king += (int) WhiteUnicorn - (int) WhiteKing;
10727         if(forwardMostMove == 0) {
10728             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10729                 fprintf(serverMoves, "%s;", UserName());
10730             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10731                 fprintf(serverMoves, "%s;", second.tidy);
10732             fprintf(serverMoves, "%s;", first.tidy);
10733             if(gameMode == MachinePlaysWhite)
10734                 fprintf(serverMoves, "%s;", UserName());
10735             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10736                 fprintf(serverMoves, "%s;", second.tidy);
10737         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10738         lastLoadFlag = loadFlag;
10739         // print base move
10740         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10741         // print castling suffix
10742         if( toY == fromY && piece == king ) {
10743             if(toX-fromX > 1)
10744                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10745             if(fromX-toX >1)
10746                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10747         }
10748         // e.p. suffix
10749         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10750              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10751              boards[forwardMostMove][toY][toX] == EmptySquare
10752              && fromX != toX && fromY != toY)
10753                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10754         // promotion suffix
10755         if(promoChar != NULLCHAR) {
10756             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10757                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10758                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10759             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10760         }
10761         if(!loadFlag) {
10762                 char buf[MOVE_LEN*2], *p; int len;
10763             fprintf(serverMoves, "/%d/%d",
10764                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10765             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10766             else                      timeLeft = blackTimeRemaining/1000;
10767             fprintf(serverMoves, "/%d", timeLeft);
10768                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10769                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10770                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10771                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10772             fprintf(serverMoves, "/%s", buf);
10773         }
10774         fflush(serverMoves);
10775     }
10776
10777     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10778         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10779       return;
10780     }
10781     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10782     if (commentList[forwardMostMove+1] != NULL) {
10783         free(commentList[forwardMostMove+1]);
10784         commentList[forwardMostMove+1] = NULL;
10785     }
10786     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10787     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10788     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10789     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10790     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10791     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10792     adjustedClock = FALSE;
10793     gameInfo.result = GameUnfinished;
10794     if (gameInfo.resultDetails != NULL) {
10795         free(gameInfo.resultDetails);
10796         gameInfo.resultDetails = NULL;
10797     }
10798     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10799                               moveList[forwardMostMove - 1]);
10800     mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10801     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10802       case MT_NONE:
10803       case MT_STALEMATE:
10804       default:
10805         break;
10806       case MT_CHECK:
10807         if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10808         if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10809             if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10810             break;
10811         }
10812       case MT_CHECKMATE:
10813       case MT_STAINMATE:
10814         strcat(parseList[forwardMostMove - 1], "#");
10815         break;
10816     }
10817 }
10818
10819 /* Updates currentMove if not pausing */
10820 void
10821 ShowMove (int fromX, int fromY, int toX, int toY)
10822 {
10823     int instant = (gameMode == PlayFromGameFile) ?
10824         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10825     if(appData.noGUI) return;
10826     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10827         if (!instant) {
10828             if (forwardMostMove == currentMove + 1) {
10829                 AnimateMove(boards[forwardMostMove - 1],
10830                             fromX, fromY, toX, toY);
10831             }
10832         }
10833         currentMove = forwardMostMove;
10834     }
10835
10836     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10837
10838     if (instant) return;
10839
10840     DisplayMove(currentMove - 1);
10841     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10842             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10843                 SetHighlights(fromX, fromY, toX, toY);
10844             }
10845     }
10846     DrawPosition(FALSE, boards[currentMove]);
10847     DisplayBothClocks();
10848     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10849 }
10850
10851 void
10852 SendEgtPath (ChessProgramState *cps)
10853 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10854         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10855
10856         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10857
10858         while(*p) {
10859             char c, *q = name+1, *r, *s;
10860
10861             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10862             while(*p && *p != ',') *q++ = *p++;
10863             *q++ = ':'; *q = 0;
10864             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10865                 strcmp(name, ",nalimov:") == 0 ) {
10866                 // take nalimov path from the menu-changeable option first, if it is defined
10867               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10868                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10869             } else
10870             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10871                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10872                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10873                 s = r = StrStr(s, ":") + 1; // beginning of path info
10874                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10875                 c = *r; *r = 0;             // temporarily null-terminate path info
10876                     *--q = 0;               // strip of trailig ':' from name
10877                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10878                 *r = c;
10879                 SendToProgram(buf,cps);     // send egtbpath command for this format
10880             }
10881             if(*p == ',') p++; // read away comma to position for next format name
10882         }
10883 }
10884
10885 static int
10886 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10887 {
10888       int width = 8, height = 8, holdings = 0;             // most common sizes
10889       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10890       // correct the deviations default for each variant
10891       if( v == VariantXiangqi ) width = 9,  height = 10;
10892       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10893       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10894       if( v == VariantCapablanca || v == VariantCapaRandom ||
10895           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10896                                 width = 10;
10897       if( v == VariantCourier ) width = 12;
10898       if( v == VariantSuper )                            holdings = 8;
10899       if( v == VariantGreat )   width = 10,              holdings = 8;
10900       if( v == VariantSChess )                           holdings = 7;
10901       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10902       if( v == VariantChuChess) width = 10, height = 10;
10903       if( v == VariantChu )     width = 12, height = 12;
10904       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10905              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10906              holdingsSize >= 0 && holdingsSize != holdings;
10907 }
10908
10909 char variantError[MSG_SIZ];
10910
10911 char *
10912 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10913 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10914       char *p, *variant = VariantName(v);
10915       static char b[MSG_SIZ];
10916       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10917            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10918                                                holdingsSize, variant); // cook up sized variant name
10919            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10920            if(StrStr(list, b) == NULL) {
10921                // specific sized variant not known, check if general sizing allowed
10922                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10923                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10924                             boardWidth, boardHeight, holdingsSize, engine);
10925                    return NULL;
10926                }
10927                /* [HGM] here we really should compare with the maximum supported board size */
10928            }
10929       } else snprintf(b, MSG_SIZ,"%s", variant);
10930       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10931       p = StrStr(list, b);
10932       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10933       if(p == NULL) {
10934           // occurs not at all in list, or only as sub-string
10935           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10936           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10937               int l = strlen(variantError);
10938               char *q;
10939               while(p != list && p[-1] != ',') p--;
10940               q = strchr(p, ',');
10941               if(q) *q = NULLCHAR;
10942               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10943               if(q) *q= ',';
10944           }
10945           return NULL;
10946       }
10947       return b;
10948 }
10949
10950 void
10951 InitChessProgram (ChessProgramState *cps, int setup)
10952 /* setup needed to setup FRC opening position */
10953 {
10954     char buf[MSG_SIZ], *b;
10955     if (appData.noChessProgram) return;
10956     hintRequested = FALSE;
10957     bookRequested = FALSE;
10958
10959     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10960     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10961     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10962     if(cps->memSize) { /* [HGM] memory */
10963       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10964         SendToProgram(buf, cps);
10965     }
10966     SendEgtPath(cps); /* [HGM] EGT */
10967     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10968       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10969         SendToProgram(buf, cps);
10970     }
10971
10972     setboardSpoiledMachineBlack = FALSE;
10973     SendToProgram(cps->initString, cps);
10974     if (gameInfo.variant != VariantNormal &&
10975         gameInfo.variant != VariantLoadable
10976         /* [HGM] also send variant if board size non-standard */
10977         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10978
10979       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10980                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10981
10982       if (b == NULL) {
10983         VariantClass v;
10984         char c, *q = cps->variants, *p = strchr(q, ',');
10985         if(p) *p = NULLCHAR;
10986         v = StringToVariant(q);
10987         DisplayError(variantError, 0);
10988         if(v != VariantUnknown && cps == &first) {
10989             int w, h, s;
10990             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10991                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10992             ASSIGN(appData.variant, q);
10993             Reset(TRUE, FALSE);
10994         }
10995         if(p) *p = ',';
10996         return;
10997       }
10998
10999       snprintf(buf, MSG_SIZ, "variant %s\n", b);
11000       SendToProgram(buf, cps);
11001     }
11002     currentlyInitializedVariant = gameInfo.variant;
11003
11004     /* [HGM] send opening position in FRC to first engine */
11005     if(setup) {
11006           SendToProgram("force\n", cps);
11007           SendBoard(cps, 0);
11008           /* engine is now in force mode! Set flag to wake it up after first move. */
11009           setboardSpoiledMachineBlack = 1;
11010     }
11011
11012     if (cps->sendICS) {
11013       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
11014       SendToProgram(buf, cps);
11015     }
11016     cps->maybeThinking = FALSE;
11017     cps->offeredDraw = 0;
11018     if (!appData.icsActive) {
11019         SendTimeControl(cps, movesPerSession, timeControl,
11020                         timeIncrement, appData.searchDepth,
11021                         searchTime);
11022     }
11023     if (appData.showThinking
11024         // [HGM] thinking: four options require thinking output to be sent
11025         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
11026                                 ) {
11027         SendToProgram("post\n", cps);
11028     }
11029     SendToProgram("hard\n", cps);
11030     if (!appData.ponderNextMove) {
11031         /* Warning: "easy" is a toggle in GNU Chess, so don't send
11032            it without being sure what state we are in first.  "hard"
11033            is not a toggle, so that one is OK.
11034          */
11035         SendToProgram("easy\n", cps);
11036     }
11037     if (cps->usePing) {
11038       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
11039       SendToProgram(buf, cps);
11040     }
11041     cps->initDone = TRUE;
11042     ClearEngineOutputPane(cps == &second);
11043 }
11044
11045
11046 char *
11047 ResendOptions (ChessProgramState *cps, int toEngine)
11048 { // send the stored value of the options
11049   int i;
11050   static char buf2[MSG_SIZ*10];
11051   char buf[MSG_SIZ], *p = buf2;
11052   Option *opt = cps->option;
11053   *p = NULLCHAR;
11054   for(i=0; i<cps->nrOptions; i++, opt++) {
11055       *buf = NULLCHAR;
11056       switch(opt->type) {
11057         case Spin:
11058         case Slider:
11059         case CheckBox:
11060             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11061             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
11062           break;
11063         case ComboBox:
11064             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11065             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
11066           break;
11067         default:
11068             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
11069             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
11070           break;
11071         case Button:
11072         case SaveButton:
11073           continue;
11074       }
11075       if(*buf) {
11076         if(toEngine) {
11077           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
11078           SendToProgram(buf2, cps);
11079         } else {
11080           if(p != buf2) *p++ = ',';
11081           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
11082           while(*p) p++;
11083         }
11084       }
11085   }
11086   return buf2;
11087 }
11088
11089 void
11090 StartChessProgram (ChessProgramState *cps)
11091 {
11092     char buf[MSG_SIZ];
11093     int err;
11094
11095     if (appData.noChessProgram) return;
11096     cps->initDone = FALSE;
11097
11098     if (strcmp(cps->host, "localhost") == 0) {
11099         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
11100     } else if (*appData.remoteShell == NULLCHAR) {
11101         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
11102     } else {
11103         if (*appData.remoteUser == NULLCHAR) {
11104           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11105                     cps->program);
11106         } else {
11107           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11108                     cps->host, appData.remoteUser, cps->program);
11109         }
11110         err = StartChildProcess(buf, "", &cps->pr);
11111     }
11112
11113     if (err != 0) {
11114       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11115         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11116         if(cps != &first) return;
11117         appData.noChessProgram = TRUE;
11118         ThawUI();
11119         SetNCPMode();
11120 //      DisplayFatalError(buf, err, 1);
11121 //      cps->pr = NoProc;
11122 //      cps->isr = NULL;
11123         return;
11124     }
11125
11126     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11127     if (cps->protocolVersion > 1) {
11128       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11129       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11130         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11131         cps->comboCnt = 0;  //                and values of combo boxes
11132       }
11133       SendToProgram(buf, cps);
11134       if(cps->reload) ResendOptions(cps, TRUE);
11135     } else {
11136       SendToProgram("xboard\n", cps);
11137     }
11138 }
11139
11140 void
11141 TwoMachinesEventIfReady P((void))
11142 {
11143   static int curMess = 0;
11144   if (first.lastPing != first.lastPong) {
11145     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11146     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11147     return;
11148   }
11149   if (second.lastPing != second.lastPong) {
11150     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11151     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11152     return;
11153   }
11154   DisplayMessage("", ""); curMess = 0;
11155   TwoMachinesEvent();
11156 }
11157
11158 char *
11159 MakeName (char *template)
11160 {
11161     time_t clock;
11162     struct tm *tm;
11163     static char buf[MSG_SIZ];
11164     char *p = buf;
11165     int i;
11166
11167     clock = time((time_t *)NULL);
11168     tm = localtime(&clock);
11169
11170     while(*p++ = *template++) if(p[-1] == '%') {
11171         switch(*template++) {
11172           case 0:   *p = 0; return buf;
11173           case 'Y': i = tm->tm_year+1900; break;
11174           case 'y': i = tm->tm_year-100; break;
11175           case 'M': i = tm->tm_mon+1; break;
11176           case 'd': i = tm->tm_mday; break;
11177           case 'h': i = tm->tm_hour; break;
11178           case 'm': i = tm->tm_min; break;
11179           case 's': i = tm->tm_sec; break;
11180           default:  i = 0;
11181         }
11182         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11183     }
11184     return buf;
11185 }
11186
11187 int
11188 CountPlayers (char *p)
11189 {
11190     int n = 0;
11191     while(p = strchr(p, '\n')) p++, n++; // count participants
11192     return n;
11193 }
11194
11195 FILE *
11196 WriteTourneyFile (char *results, FILE *f)
11197 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11198     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11199     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11200         // create a file with tournament description
11201         fprintf(f, "-participants {%s}\n", appData.participants);
11202         fprintf(f, "-seedBase %d\n", appData.seedBase);
11203         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11204         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11205         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11206         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11207         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11208         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11209         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11210         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11211         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11212         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11213         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11214         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11215         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11216         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11217         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11218         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11219         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11220         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11221         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11222         fprintf(f, "-smpCores %d\n", appData.smpCores);
11223         if(searchTime > 0)
11224                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11225         else {
11226                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11227                 fprintf(f, "-tc %s\n", appData.timeControl);
11228                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11229         }
11230         fprintf(f, "-results \"%s\"\n", results);
11231     }
11232     return f;
11233 }
11234
11235 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11236
11237 void
11238 Substitute (char *participants, int expunge)
11239 {
11240     int i, changed, changes=0, nPlayers=0;
11241     char *p, *q, *r, buf[MSG_SIZ];
11242     if(participants == NULL) return;
11243     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11244     r = p = participants; q = appData.participants;
11245     while(*p && *p == *q) {
11246         if(*p == '\n') r = p+1, nPlayers++;
11247         p++; q++;
11248     }
11249     if(*p) { // difference
11250         while(*p && *p++ != '\n')
11251                                  ;
11252         while(*q && *q++ != '\n')
11253                                  ;
11254       changed = nPlayers;
11255         changes = 1 + (strcmp(p, q) != 0);
11256     }
11257     if(changes == 1) { // a single engine mnemonic was changed
11258         q = r; while(*q) nPlayers += (*q++ == '\n');
11259         p = buf; while(*r && (*p = *r++) != '\n') p++;
11260         *p = NULLCHAR;
11261         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11262         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11263         if(mnemonic[i]) { // The substitute is valid
11264             FILE *f;
11265             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11266                 flock(fileno(f), LOCK_EX);
11267                 ParseArgsFromFile(f);
11268                 fseek(f, 0, SEEK_SET);
11269                 FREE(appData.participants); appData.participants = participants;
11270                 if(expunge) { // erase results of replaced engine
11271                     int len = strlen(appData.results), w, b, dummy;
11272                     for(i=0; i<len; i++) {
11273                         Pairing(i, nPlayers, &w, &b, &dummy);
11274                         if((w == changed || b == changed) && appData.results[i] == '*') {
11275                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11276                             fclose(f);
11277                             return;
11278                         }
11279                     }
11280                     for(i=0; i<len; i++) {
11281                         Pairing(i, nPlayers, &w, &b, &dummy);
11282                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11283                     }
11284                 }
11285                 WriteTourneyFile(appData.results, f);
11286                 fclose(f); // release lock
11287                 return;
11288             }
11289         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11290     }
11291     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11292     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11293     free(participants);
11294     return;
11295 }
11296
11297 int
11298 CheckPlayers (char *participants)
11299 {
11300         int i;
11301         char buf[MSG_SIZ], *p;
11302         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11303         while(p = strchr(participants, '\n')) {
11304             *p = NULLCHAR;
11305             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11306             if(!mnemonic[i]) {
11307                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11308                 *p = '\n';
11309                 DisplayError(buf, 0);
11310                 return 1;
11311             }
11312             *p = '\n';
11313             participants = p + 1;
11314         }
11315         return 0;
11316 }
11317
11318 int
11319 CreateTourney (char *name)
11320 {
11321         FILE *f;
11322         if(matchMode && strcmp(name, appData.tourneyFile)) {
11323              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11324         }
11325         if(name[0] == NULLCHAR) {
11326             if(appData.participants[0])
11327                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11328             return 0;
11329         }
11330         f = fopen(name, "r");
11331         if(f) { // file exists
11332             ASSIGN(appData.tourneyFile, name);
11333             ParseArgsFromFile(f); // parse it
11334         } else {
11335             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11336             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11337                 DisplayError(_("Not enough participants"), 0);
11338                 return 0;
11339             }
11340             if(CheckPlayers(appData.participants)) return 0;
11341             ASSIGN(appData.tourneyFile, name);
11342             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11343             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11344         }
11345         fclose(f);
11346         appData.noChessProgram = FALSE;
11347         appData.clockMode = TRUE;
11348         SetGNUMode();
11349         return 1;
11350 }
11351
11352 int
11353 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11354 {
11355     char buf[2*MSG_SIZ], *p, *q;
11356     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11357     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11358     skip = !all && group[0]; // if group requested, we start in skip mode
11359     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11360         p = names; q = buf; header = 0;
11361         while(*p && *p != '\n') *q++ = *p++;
11362         *q = 0;
11363         if(*p == '\n') p++;
11364         if(buf[0] == '#') {
11365             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11366             depth++; // we must be entering a new group
11367             if(all) continue; // suppress printing group headers when complete list requested
11368             header = 1;
11369             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11370         }
11371         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11372         if(engineList[i]) free(engineList[i]);
11373         engineList[i] = strdup(buf);
11374         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11375         if(engineMnemonic[i]) free(engineMnemonic[i]);
11376         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11377             strcat(buf, " (");
11378             sscanf(q + 8, "%s", buf + strlen(buf));
11379             strcat(buf, ")");
11380         }
11381         engineMnemonic[i] = strdup(buf);
11382         i++;
11383     }
11384     engineList[i] = engineMnemonic[i] = NULL;
11385     return i;
11386 }
11387
11388 void
11389 SaveEngineSettings (int n)
11390 {
11391     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11392     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11393     p = strstr(firstChessProgramNames, currentEngine[n]);
11394     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11395     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11396     len = strlen(currentEngine[n]);
11397     q = p + len; *p = 0; // cut list into head and tail piece
11398     s = strstr(currentEngine[n], "firstOptions");
11399     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11400         char *r = s + 14;
11401         while(*r && *r != s[13]) r++;
11402         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11403         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11404     } else if(*optionSettings) {
11405         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11406     }
11407     ASSIGN(currentEngine[n], buf); // updated engine line
11408     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11409     s = malloc(len);
11410     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11411     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11412 }
11413
11414 // following implemented as macro to avoid type limitations
11415 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11416
11417 void
11418 SwapEngines (int n)
11419 {   // swap settings for first engine and other engine (so far only some selected options)
11420     int h;
11421     char *p;
11422     if(n == 0) return;
11423     SWAP(directory, p)
11424     SWAP(chessProgram, p)
11425     SWAP(isUCI, h)
11426     SWAP(hasOwnBookUCI, h)
11427     SWAP(protocolVersion, h)
11428     SWAP(reuse, h)
11429     SWAP(scoreIsAbsolute, h)
11430     SWAP(timeOdds, h)
11431     SWAP(logo, p)
11432     SWAP(pgnName, p)
11433     SWAP(pvSAN, h)
11434     SWAP(engOptions, p)
11435     SWAP(engInitString, p)
11436     SWAP(computerString, p)
11437     SWAP(features, p)
11438     SWAP(fenOverride, p)
11439     SWAP(NPS, h)
11440     SWAP(accumulateTC, h)
11441     SWAP(drawDepth, h)
11442     SWAP(host, p)
11443     SWAP(pseudo, h)
11444 }
11445
11446 int
11447 GetEngineLine (char *s, int n)
11448 {
11449     int i;
11450     char buf[MSG_SIZ];
11451     extern char *icsNames;
11452     if(!s || !*s) return 0;
11453     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11454     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11455     if(!mnemonic[i]) return 0;
11456     if(n == 11) return 1; // just testing if there was a match
11457     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11458     if(n == 1) SwapEngines(n);
11459     ParseArgsFromString(buf);
11460     if(n == 1) SwapEngines(n);
11461     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11462     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11463         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11464         ParseArgsFromString(buf);
11465     }
11466     return 1;
11467 }
11468
11469 int
11470 SetPlayer (int player, char *p)
11471 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11472     int i;
11473     char buf[MSG_SIZ], *engineName;
11474     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11475     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11476     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11477     if(mnemonic[i]) {
11478         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11479         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11480         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11481         ParseArgsFromString(buf);
11482     } else { // no engine with this nickname is installed!
11483         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11484         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11485         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11486         ModeHighlight();
11487         DisplayError(buf, 0);
11488         return 0;
11489     }
11490     free(engineName);
11491     return i;
11492 }
11493
11494 char *recentEngines;
11495
11496 void
11497 RecentEngineEvent (int nr)
11498 {
11499     int n;
11500 //    SwapEngines(1); // bump first to second
11501 //    ReplaceEngine(&second, 1); // and load it there
11502     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11503     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11504     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11505         ReplaceEngine(&first, 0);
11506         FloatToFront(&appData.recentEngineList, command[n]);
11507         ASSIGN(currentEngine[0], command[n]);
11508     }
11509 }
11510
11511 int
11512 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11513 {   // determine players from game number
11514     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11515
11516     if(appData.tourneyType == 0) {
11517         roundsPerCycle = (nPlayers - 1) | 1;
11518         pairingsPerRound = nPlayers / 2;
11519     } else if(appData.tourneyType > 0) {
11520         roundsPerCycle = nPlayers - appData.tourneyType;
11521         pairingsPerRound = appData.tourneyType;
11522     }
11523     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11524     gamesPerCycle = gamesPerRound * roundsPerCycle;
11525     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11526     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11527     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11528     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11529     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11530     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11531
11532     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11533     if(appData.roundSync) *syncInterval = gamesPerRound;
11534
11535     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11536
11537     if(appData.tourneyType == 0) {
11538         if(curPairing == (nPlayers-1)/2 ) {
11539             *whitePlayer = curRound;
11540             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11541         } else {
11542             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11543             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11544             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11545             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11546         }
11547     } else if(appData.tourneyType > 1) {
11548         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11549         *whitePlayer = curRound + appData.tourneyType;
11550     } else if(appData.tourneyType > 0) {
11551         *whitePlayer = curPairing;
11552         *blackPlayer = curRound + appData.tourneyType;
11553     }
11554
11555     // take care of white/black alternation per round.
11556     // For cycles and games this is already taken care of by default, derived from matchGame!
11557     return curRound & 1;
11558 }
11559
11560 int
11561 NextTourneyGame (int nr, int *swapColors)
11562 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11563     char *p, *q;
11564     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11565     FILE *tf;
11566     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11567     tf = fopen(appData.tourneyFile, "r");
11568     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11569     ParseArgsFromFile(tf); fclose(tf);
11570     InitTimeControls(); // TC might be altered from tourney file
11571
11572     nPlayers = CountPlayers(appData.participants); // count participants
11573     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11574     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11575
11576     if(syncInterval) {
11577         p = q = appData.results;
11578         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11579         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11580             DisplayMessage(_("Waiting for other game(s)"),"");
11581             waitingForGame = TRUE;
11582             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11583             return 0;
11584         }
11585         waitingForGame = FALSE;
11586     }
11587
11588     if(appData.tourneyType < 0) {
11589         if(nr>=0 && !pairingReceived) {
11590             char buf[1<<16];
11591             if(pairing.pr == NoProc) {
11592                 if(!appData.pairingEngine[0]) {
11593                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11594                     return 0;
11595                 }
11596                 StartChessProgram(&pairing); // starts the pairing engine
11597             }
11598             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11599             SendToProgram(buf, &pairing);
11600             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11601             SendToProgram(buf, &pairing);
11602             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11603         }
11604         pairingReceived = 0;                              // ... so we continue here
11605         *swapColors = 0;
11606         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11607         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11608         matchGame = 1; roundNr = nr / syncInterval + 1;
11609     }
11610
11611     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11612
11613     // redefine engines, engine dir, etc.
11614     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11615     if(first.pr == NoProc) {
11616       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11617       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11618     }
11619     if(second.pr == NoProc) {
11620       SwapEngines(1);
11621       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11622       SwapEngines(1);         // and make that valid for second engine by swapping
11623       InitEngine(&second, 1);
11624     }
11625     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11626     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11627     return OK;
11628 }
11629
11630 void
11631 NextMatchGame ()
11632 {   // performs game initialization that does not invoke engines, and then tries to start the game
11633     int res, firstWhite, swapColors = 0;
11634     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11635     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
11636         char buf[MSG_SIZ];
11637         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11638         if(strcmp(buf, currentDebugFile)) { // name has changed
11639             FILE *f = fopen(buf, "w");
11640             if(f) { // if opening the new file failed, just keep using the old one
11641                 ASSIGN(currentDebugFile, buf);
11642                 fclose(debugFP);
11643                 debugFP = f;
11644             }
11645             if(appData.serverFileName) {
11646                 if(serverFP) fclose(serverFP);
11647                 serverFP = fopen(appData.serverFileName, "w");
11648                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11649                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11650             }
11651         }
11652     }
11653     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11654     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11655     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11656     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11657     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11658     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11659     Reset(FALSE, first.pr != NoProc);
11660     res = LoadGameOrPosition(matchGame); // setup game
11661     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11662     if(!res) return; // abort when bad game/pos file
11663     if(appData.epd) {// in EPD mode we make sure first engine is to move
11664         firstWhite = !(forwardMostMove & 1);
11665         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11666         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11667     }
11668     TwoMachinesEvent();
11669 }
11670
11671 void
11672 UserAdjudicationEvent (int result)
11673 {
11674     ChessMove gameResult = GameIsDrawn;
11675
11676     if( result > 0 ) {
11677         gameResult = WhiteWins;
11678     }
11679     else if( result < 0 ) {
11680         gameResult = BlackWins;
11681     }
11682
11683     if( gameMode == TwoMachinesPlay ) {
11684         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11685     }
11686 }
11687
11688
11689 // [HGM] save: calculate checksum of game to make games easily identifiable
11690 int
11691 StringCheckSum (char *s)
11692 {
11693         int i = 0;
11694         if(s==NULL) return 0;
11695         while(*s) i = i*259 + *s++;
11696         return i;
11697 }
11698
11699 int
11700 GameCheckSum ()
11701 {
11702         int i, sum=0;
11703         for(i=backwardMostMove; i<forwardMostMove; i++) {
11704                 sum += pvInfoList[i].depth;
11705                 sum += StringCheckSum(parseList[i]);
11706                 sum += StringCheckSum(commentList[i]);
11707                 sum *= 261;
11708         }
11709         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11710         return sum + StringCheckSum(commentList[i]);
11711 } // end of save patch
11712
11713 void
11714 GameEnds (ChessMove result, char *resultDetails, int whosays)
11715 {
11716     GameMode nextGameMode;
11717     int isIcsGame;
11718     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11719
11720     if(endingGame) return; /* [HGM] crash: forbid recursion */
11721     endingGame = 1;
11722     if(twoBoards) { // [HGM] dual: switch back to one board
11723         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11724         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11725     }
11726     if (appData.debugMode) {
11727       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11728               result, resultDetails ? resultDetails : "(null)", whosays);
11729     }
11730
11731     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11732
11733     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11734
11735     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11736         /* If we are playing on ICS, the server decides when the
11737            game is over, but the engine can offer to draw, claim
11738            a draw, or resign.
11739          */
11740 #if ZIPPY
11741         if (appData.zippyPlay && first.initDone) {
11742             if (result == GameIsDrawn) {
11743                 /* In case draw still needs to be claimed */
11744                 SendToICS(ics_prefix);
11745                 SendToICS("draw\n");
11746             } else if (StrCaseStr(resultDetails, "resign")) {
11747                 SendToICS(ics_prefix);
11748                 SendToICS("resign\n");
11749             }
11750         }
11751 #endif
11752         endingGame = 0; /* [HGM] crash */
11753         return;
11754     }
11755
11756     /* If we're loading the game from a file, stop */
11757     if (whosays == GE_FILE) {
11758       (void) StopLoadGameTimer();
11759       gameFileFP = NULL;
11760     }
11761
11762     /* Cancel draw offers */
11763     first.offeredDraw = second.offeredDraw = 0;
11764
11765     /* If this is an ICS game, only ICS can really say it's done;
11766        if not, anyone can. */
11767     isIcsGame = (gameMode == IcsPlayingWhite ||
11768                  gameMode == IcsPlayingBlack ||
11769                  gameMode == IcsObserving    ||
11770                  gameMode == IcsExamining);
11771
11772     if (!isIcsGame || whosays == GE_ICS) {
11773         /* OK -- not an ICS game, or ICS said it was done */
11774         StopClocks();
11775         if (!isIcsGame && !appData.noChessProgram)
11776           SetUserThinkingEnables();
11777
11778         /* [HGM] if a machine claims the game end we verify this claim */
11779         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11780             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11781                 char claimer;
11782                 ChessMove trueResult = (ChessMove) -1;
11783
11784                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11785                                             first.twoMachinesColor[0] :
11786                                             second.twoMachinesColor[0] ;
11787
11788                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11789                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11790                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11791                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11792                 } else
11793                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11794                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11795                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11796                 } else
11797                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11798                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11799                 }
11800
11801                 // now verify win claims, but not in drop games, as we don't understand those yet
11802                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11803                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11804                     (result == WhiteWins && claimer == 'w' ||
11805                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11806                       if (appData.debugMode) {
11807                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11808                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11809                       }
11810                       if(result != trueResult) {
11811                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11812                               result = claimer == 'w' ? BlackWins : WhiteWins;
11813                               resultDetails = buf;
11814                       }
11815                 } else
11816                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11817                     && (forwardMostMove <= backwardMostMove ||
11818                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11819                         (claimer=='b')==(forwardMostMove&1))
11820                                                                                   ) {
11821                       /* [HGM] verify: draws that were not flagged are false claims */
11822                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11823                       result = claimer == 'w' ? BlackWins : WhiteWins;
11824                       resultDetails = buf;
11825                 }
11826                 /* (Claiming a loss is accepted no questions asked!) */
11827             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11828                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11829                 result = GameUnfinished;
11830                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11831             }
11832             /* [HGM] bare: don't allow bare King to win */
11833             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11834                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11835                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11836                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11837                && result != GameIsDrawn)
11838             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11839                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11840                         int p = (int)boards[forwardMostMove][i][j] - color;
11841                         if(p >= 0 && p <= (int)WhiteKing) k++;
11842                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11843                 }
11844                 if (appData.debugMode) {
11845                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11846                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11847                 }
11848                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11849                         result = GameIsDrawn;
11850                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11851                         resultDetails = buf;
11852                 }
11853             }
11854         }
11855
11856
11857         if(serverMoves != NULL && !loadFlag) { char c = '=';
11858             if(result==WhiteWins) c = '+';
11859             if(result==BlackWins) c = '-';
11860             if(resultDetails != NULL)
11861                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11862         }
11863         if (resultDetails != NULL) {
11864             gameInfo.result = result;
11865             gameInfo.resultDetails = StrSave(resultDetails);
11866
11867             /* display last move only if game was not loaded from file */
11868             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11869                 DisplayMove(currentMove - 1);
11870
11871             if (forwardMostMove != 0) {
11872                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11873                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11874                                                                 ) {
11875                     if (*appData.saveGameFile != NULLCHAR) {
11876                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11877                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11878                         else
11879                         SaveGameToFile(appData.saveGameFile, TRUE);
11880                     } else if (appData.autoSaveGames) {
11881                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11882                     }
11883                     if (*appData.savePositionFile != NULLCHAR) {
11884                         SavePositionToFile(appData.savePositionFile);
11885                     }
11886                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11887                 }
11888             }
11889
11890             /* Tell program how game ended in case it is learning */
11891             /* [HGM] Moved this to after saving the PGN, just in case */
11892             /* engine died and we got here through time loss. In that */
11893             /* case we will get a fatal error writing the pipe, which */
11894             /* would otherwise lose us the PGN.                       */
11895             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11896             /* output during GameEnds should never be fatal anymore   */
11897             if (gameMode == MachinePlaysWhite ||
11898                 gameMode == MachinePlaysBlack ||
11899                 gameMode == TwoMachinesPlay ||
11900                 gameMode == IcsPlayingWhite ||
11901                 gameMode == IcsPlayingBlack ||
11902                 gameMode == BeginningOfGame) {
11903                 char buf[MSG_SIZ];
11904                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11905                         resultDetails);
11906                 if (first.pr != NoProc) {
11907                     SendToProgram(buf, &first);
11908                 }
11909                 if (second.pr != NoProc &&
11910                     gameMode == TwoMachinesPlay) {
11911                     SendToProgram(buf, &second);
11912                 }
11913             }
11914         }
11915
11916         if (appData.icsActive) {
11917             if (appData.quietPlay &&
11918                 (gameMode == IcsPlayingWhite ||
11919                  gameMode == IcsPlayingBlack)) {
11920                 SendToICS(ics_prefix);
11921                 SendToICS("set shout 1\n");
11922             }
11923             nextGameMode = IcsIdle;
11924             ics_user_moved = FALSE;
11925             /* clean up premove.  It's ugly when the game has ended and the
11926              * premove highlights are still on the board.
11927              */
11928             if (gotPremove) {
11929               gotPremove = FALSE;
11930               ClearPremoveHighlights();
11931               DrawPosition(FALSE, boards[currentMove]);
11932             }
11933             if (whosays == GE_ICS) {
11934                 switch (result) {
11935                 case WhiteWins:
11936                     if (gameMode == IcsPlayingWhite)
11937                         PlayIcsWinSound();
11938                     else if(gameMode == IcsPlayingBlack)
11939                         PlayIcsLossSound();
11940                     break;
11941                 case BlackWins:
11942                     if (gameMode == IcsPlayingBlack)
11943                         PlayIcsWinSound();
11944                     else if(gameMode == IcsPlayingWhite)
11945                         PlayIcsLossSound();
11946                     break;
11947                 case GameIsDrawn:
11948                     PlayIcsDrawSound();
11949                     break;
11950                 default:
11951                     PlayIcsUnfinishedSound();
11952                 }
11953             }
11954             if(appData.quitNext) { ExitEvent(0); return; }
11955         } else if (gameMode == EditGame ||
11956                    gameMode == PlayFromGameFile ||
11957                    gameMode == AnalyzeMode ||
11958                    gameMode == AnalyzeFile) {
11959             nextGameMode = gameMode;
11960         } else {
11961             nextGameMode = EndOfGame;
11962         }
11963         pausing = FALSE;
11964         ModeHighlight();
11965     } else {
11966         nextGameMode = gameMode;
11967     }
11968
11969     if (appData.noChessProgram) {
11970         gameMode = nextGameMode;
11971         ModeHighlight();
11972         endingGame = 0; /* [HGM] crash */
11973         return;
11974     }
11975
11976     if (first.reuse) {
11977         /* Put first chess program into idle state */
11978         if (first.pr != NoProc &&
11979             (gameMode == MachinePlaysWhite ||
11980              gameMode == MachinePlaysBlack ||
11981              gameMode == TwoMachinesPlay ||
11982              gameMode == IcsPlayingWhite ||
11983              gameMode == IcsPlayingBlack ||
11984              gameMode == BeginningOfGame)) {
11985             SendToProgram("force\n", &first);
11986             if (first.usePing) {
11987               char buf[MSG_SIZ];
11988               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11989               SendToProgram(buf, &first);
11990             }
11991         }
11992     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11993         /* Kill off first chess program */
11994         if (first.isr != NULL)
11995           RemoveInputSource(first.isr);
11996         first.isr = NULL;
11997
11998         if (first.pr != NoProc) {
11999             ExitAnalyzeMode();
12000             DoSleep( appData.delayBeforeQuit );
12001             SendToProgram("quit\n", &first);
12002             DestroyChildProcess(first.pr, 4 + first.useSigterm);
12003             first.reload = TRUE;
12004         }
12005         first.pr = NoProc;
12006     }
12007     if (second.reuse) {
12008         /* Put second chess program into idle state */
12009         if (second.pr != NoProc &&
12010             gameMode == TwoMachinesPlay) {
12011             SendToProgram("force\n", &second);
12012             if (second.usePing) {
12013               char buf[MSG_SIZ];
12014               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
12015               SendToProgram(buf, &second);
12016             }
12017         }
12018     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12019         /* Kill off second chess program */
12020         if (second.isr != NULL)
12021           RemoveInputSource(second.isr);
12022         second.isr = NULL;
12023
12024         if (second.pr != NoProc) {
12025             DoSleep( appData.delayBeforeQuit );
12026             SendToProgram("quit\n", &second);
12027             DestroyChildProcess(second.pr, 4 + second.useSigterm);
12028             second.reload = TRUE;
12029         }
12030         second.pr = NoProc;
12031     }
12032
12033     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
12034         char resChar = '=';
12035         switch (result) {
12036         case WhiteWins:
12037           resChar = '+';
12038           if (first.twoMachinesColor[0] == 'w') {
12039             first.matchWins++;
12040           } else {
12041             second.matchWins++;
12042           }
12043           break;
12044         case BlackWins:
12045           resChar = '-';
12046           if (first.twoMachinesColor[0] == 'b') {
12047             first.matchWins++;
12048           } else {
12049             second.matchWins++;
12050           }
12051           break;
12052         case GameUnfinished:
12053           resChar = ' ';
12054         default:
12055           break;
12056         }
12057
12058         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
12059         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
12060             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
12061             ReserveGame(nextGame, resChar); // sets nextGame
12062             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
12063             else ranking = strdup("busy"); //suppress popup when aborted but not finished
12064         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
12065
12066         if (nextGame <= appData.matchGames && !abortMatch) {
12067             gameMode = nextGameMode;
12068             matchGame = nextGame; // this will be overruled in tourney mode!
12069             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
12070             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
12071             endingGame = 0; /* [HGM] crash */
12072             return;
12073         } else {
12074             gameMode = nextGameMode;
12075             if(appData.epd) {
12076                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
12077                 OutputKibitz(2, buf);
12078                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
12079                 OutputKibitz(2, buf);
12080                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
12081                 if(second.matchWins) OutputKibitz(2, buf);
12082                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
12083                 OutputKibitz(2, buf);
12084             }
12085             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
12086                      first.tidy, second.tidy,
12087                      first.matchWins, second.matchWins,
12088                      appData.matchGames - (first.matchWins + second.matchWins));
12089             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
12090             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
12091             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
12092             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
12093                 first.twoMachinesColor = "black\n";
12094                 second.twoMachinesColor = "white\n";
12095             } else {
12096                 first.twoMachinesColor = "white\n";
12097                 second.twoMachinesColor = "black\n";
12098             }
12099         }
12100     }
12101     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
12102         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
12103       ExitAnalyzeMode();
12104     gameMode = nextGameMode;
12105     ModeHighlight();
12106     endingGame = 0;  /* [HGM] crash */
12107     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12108         if(matchMode == TRUE) { // match through command line: exit with or without popup
12109             if(ranking) {
12110                 ToNrEvent(forwardMostMove);
12111                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12112                 else ExitEvent(0);
12113             } else DisplayFatalError(buf, 0, 0);
12114         } else { // match through menu; just stop, with or without popup
12115             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12116             ModeHighlight();
12117             if(ranking){
12118                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12119             } else DisplayNote(buf);
12120       }
12121       if(ranking) free(ranking);
12122     }
12123 }
12124
12125 /* Assumes program was just initialized (initString sent).
12126    Leaves program in force mode. */
12127 void
12128 FeedMovesToProgram (ChessProgramState *cps, int upto)
12129 {
12130     int i;
12131
12132     if (appData.debugMode)
12133       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12134               startedFromSetupPosition ? "position and " : "",
12135               backwardMostMove, upto, cps->which);
12136     if(currentlyInitializedVariant != gameInfo.variant) {
12137       char buf[MSG_SIZ];
12138         // [HGM] variantswitch: make engine aware of new variant
12139         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12140                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12141                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12142         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12143         SendToProgram(buf, cps);
12144         currentlyInitializedVariant = gameInfo.variant;
12145     }
12146     SendToProgram("force\n", cps);
12147     if (startedFromSetupPosition) {
12148         SendBoard(cps, backwardMostMove);
12149     if (appData.debugMode) {
12150         fprintf(debugFP, "feedMoves\n");
12151     }
12152     }
12153     for (i = backwardMostMove; i < upto; i++) {
12154         SendMoveToProgram(i, cps);
12155     }
12156 }
12157
12158
12159 int
12160 ResurrectChessProgram ()
12161 {
12162      /* The chess program may have exited.
12163         If so, restart it and feed it all the moves made so far. */
12164     static int doInit = 0;
12165
12166     if (appData.noChessProgram) return 1;
12167
12168     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12169         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12170         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12171         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12172     } else {
12173         if (first.pr != NoProc) return 1;
12174         StartChessProgram(&first);
12175     }
12176     InitChessProgram(&first, FALSE);
12177     FeedMovesToProgram(&first, currentMove);
12178
12179     if (!first.sendTime) {
12180         /* can't tell gnuchess what its clock should read,
12181            so we bow to its notion. */
12182         ResetClocks();
12183         timeRemaining[0][currentMove] = whiteTimeRemaining;
12184         timeRemaining[1][currentMove] = blackTimeRemaining;
12185     }
12186
12187     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12188                 appData.icsEngineAnalyze) && first.analysisSupport) {
12189       SendToProgram("analyze\n", &first);
12190       first.analyzing = TRUE;
12191     }
12192     return 1;
12193 }
12194
12195 /*
12196  * Button procedures
12197  */
12198 void
12199 Reset (int redraw, int init)
12200 {
12201     int i;
12202
12203     if (appData.debugMode) {
12204         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12205                 redraw, init, gameMode);
12206     }
12207     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12208     deadRanks = 0; // assume entire board is used
12209     handSize = 0;
12210     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12211     CleanupTail(); // [HGM] vari: delete any stored variations
12212     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12213     pausing = pauseExamInvalid = FALSE;
12214     startedFromSetupPosition = blackPlaysFirst = FALSE;
12215     firstMove = TRUE;
12216     whiteFlag = blackFlag = FALSE;
12217     userOfferedDraw = FALSE;
12218     hintRequested = bookRequested = FALSE;
12219     first.maybeThinking = FALSE;
12220     second.maybeThinking = FALSE;
12221     first.bookSuspend = FALSE; // [HGM] book
12222     second.bookSuspend = FALSE;
12223     thinkOutput[0] = NULLCHAR;
12224     lastHint[0] = NULLCHAR;
12225     ClearGameInfo(&gameInfo);
12226     gameInfo.variant = StringToVariant(appData.variant);
12227     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12228         gameInfo.variant = VariantUnknown;
12229         strncpy(engineVariant, appData.variant, MSG_SIZ);
12230     }
12231     ics_user_moved = ics_clock_paused = FALSE;
12232     ics_getting_history = H_FALSE;
12233     ics_gamenum = -1;
12234     white_holding[0] = black_holding[0] = NULLCHAR;
12235     ClearProgramStats();
12236     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12237
12238     ResetFrontEnd();
12239     ClearHighlights();
12240     flipView = appData.flipView;
12241     ClearPremoveHighlights();
12242     gotPremove = FALSE;
12243     alarmSounded = FALSE;
12244     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12245
12246     GameEnds(EndOfFile, NULL, GE_PLAYER);
12247     if(appData.serverMovesName != NULL) {
12248         /* [HGM] prepare to make moves file for broadcasting */
12249         clock_t t = clock();
12250         if(serverMoves != NULL) fclose(serverMoves);
12251         serverMoves = fopen(appData.serverMovesName, "r");
12252         if(serverMoves != NULL) {
12253             fclose(serverMoves);
12254             /* delay 15 sec before overwriting, so all clients can see end */
12255             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12256         }
12257         serverMoves = fopen(appData.serverMovesName, "w");
12258     }
12259
12260     ExitAnalyzeMode();
12261     gameMode = BeginningOfGame;
12262     ModeHighlight();
12263     if(appData.icsActive) gameInfo.variant = VariantNormal;
12264     currentMove = forwardMostMove = backwardMostMove = 0;
12265     MarkTargetSquares(1);
12266     InitPosition(redraw);
12267     for (i = 0; i < MAX_MOVES; i++) {
12268         if (commentList[i] != NULL) {
12269             free(commentList[i]);
12270             commentList[i] = NULL;
12271         }
12272     }
12273     ResetClocks();
12274     timeRemaining[0][0] = whiteTimeRemaining;
12275     timeRemaining[1][0] = blackTimeRemaining;
12276
12277     if (first.pr == NoProc) {
12278         StartChessProgram(&first);
12279     }
12280     if (init) {
12281             InitChessProgram(&first, startedFromSetupPosition);
12282     }
12283     DisplayTitle("");
12284     DisplayMessage("", "");
12285     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12286     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12287     ClearMap();        // [HGM] exclude: invalidate map
12288 }
12289
12290 void
12291 AutoPlayGameLoop ()
12292 {
12293     for (;;) {
12294         if (!AutoPlayOneMove())
12295           return;
12296         if (matchMode || appData.timeDelay == 0)
12297           continue;
12298         if (appData.timeDelay < 0)
12299           return;
12300         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12301         break;
12302     }
12303 }
12304
12305 void
12306 AnalyzeNextGame()
12307 {
12308     ReloadGame(1); // next game
12309 }
12310
12311 int
12312 AutoPlayOneMove ()
12313 {
12314     int fromX, fromY, toX, toY;
12315
12316     if (appData.debugMode) {
12317       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12318     }
12319
12320     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12321       return FALSE;
12322
12323     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12324       pvInfoList[currentMove].depth = programStats.depth;
12325       pvInfoList[currentMove].score = programStats.score;
12326       pvInfoList[currentMove].time  = 0;
12327       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12328       else { // append analysis of final position as comment
12329         char buf[MSG_SIZ];
12330         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12331         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12332       }
12333       programStats.depth = 0;
12334     }
12335
12336     if (currentMove >= forwardMostMove) {
12337       if(gameMode == AnalyzeFile) {
12338           if(appData.loadGameIndex == -1) {
12339             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12340           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12341           } else {
12342           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12343         }
12344       }
12345 //      gameMode = EndOfGame;
12346 //      ModeHighlight();
12347
12348       /* [AS] Clear current move marker at the end of a game */
12349       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12350
12351       return FALSE;
12352     }
12353
12354     toX = moveList[currentMove][2] - AAA;
12355     toY = moveList[currentMove][3] - ONE;
12356
12357     if (moveList[currentMove][1] == '@') {
12358         if (appData.highlightLastMove) {
12359             SetHighlights(-1, -1, toX, toY);
12360         }
12361     } else {
12362         fromX = moveList[currentMove][0] - AAA;
12363         fromY = moveList[currentMove][1] - ONE;
12364
12365         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12366
12367         if(moveList[currentMove][4] == ';') { // multi-leg
12368             killX = moveList[currentMove][5] - AAA;
12369             killY = moveList[currentMove][6] - ONE;
12370         }
12371         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12372         killX = killY = -1;
12373
12374         if (appData.highlightLastMove) {
12375             SetHighlights(fromX, fromY, toX, toY);
12376         }
12377     }
12378     DisplayMove(currentMove);
12379     SendMoveToProgram(currentMove++, &first);
12380     DisplayBothClocks();
12381     DrawPosition(FALSE, boards[currentMove]);
12382     // [HGM] PV info: always display, routine tests if empty
12383     DisplayComment(currentMove - 1, commentList[currentMove]);
12384     return TRUE;
12385 }
12386
12387
12388 int
12389 LoadGameOneMove (ChessMove readAhead)
12390 {
12391     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12392     char promoChar = NULLCHAR;
12393     ChessMove moveType;
12394     char move[MSG_SIZ];
12395     char *p, *q;
12396
12397     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12398         gameMode != AnalyzeMode && gameMode != Training) {
12399         gameFileFP = NULL;
12400         return FALSE;
12401     }
12402
12403     yyboardindex = forwardMostMove;
12404     if (readAhead != EndOfFile) {
12405       moveType = readAhead;
12406     } else {
12407       if (gameFileFP == NULL)
12408           return FALSE;
12409       moveType = (ChessMove) Myylex();
12410     }
12411
12412     done = FALSE;
12413     switch (moveType) {
12414       case Comment:
12415         if (appData.debugMode)
12416           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12417         p = yy_text;
12418
12419         /* append the comment but don't display it */
12420         AppendComment(currentMove, p, FALSE);
12421         return TRUE;
12422
12423       case WhiteCapturesEnPassant:
12424       case BlackCapturesEnPassant:
12425       case WhitePromotion:
12426       case BlackPromotion:
12427       case WhiteNonPromotion:
12428       case BlackNonPromotion:
12429       case NormalMove:
12430       case FirstLeg:
12431       case WhiteKingSideCastle:
12432       case WhiteQueenSideCastle:
12433       case BlackKingSideCastle:
12434       case BlackQueenSideCastle:
12435       case WhiteKingSideCastleWild:
12436       case WhiteQueenSideCastleWild:
12437       case BlackKingSideCastleWild:
12438       case BlackQueenSideCastleWild:
12439       /* PUSH Fabien */
12440       case WhiteHSideCastleFR:
12441       case WhiteASideCastleFR:
12442       case BlackHSideCastleFR:
12443       case BlackASideCastleFR:
12444       /* POP Fabien */
12445         if (appData.debugMode)
12446           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12447         fromX = currentMoveString[0] - AAA;
12448         fromY = currentMoveString[1] - ONE;
12449         toX = currentMoveString[2] - AAA;
12450         toY = currentMoveString[3] - ONE;
12451         promoChar = currentMoveString[4];
12452         if(promoChar == ';') promoChar = currentMoveString[7];
12453         break;
12454
12455       case WhiteDrop:
12456       case BlackDrop:
12457         if (appData.debugMode)
12458           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12459         fromX = moveType == WhiteDrop ?
12460           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12461         (int) CharToPiece(ToLower(currentMoveString[0]));
12462         fromY = DROP_RANK;
12463         toX = currentMoveString[2] - AAA;
12464         toY = currentMoveString[3] - ONE;
12465         break;
12466
12467       case WhiteWins:
12468       case BlackWins:
12469       case GameIsDrawn:
12470       case GameUnfinished:
12471         if (appData.debugMode)
12472           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12473         p = strchr(yy_text, '{');
12474         if (p == NULL) p = strchr(yy_text, '(');
12475         if (p == NULL) {
12476             p = yy_text;
12477             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12478         } else {
12479             q = strchr(p, *p == '{' ? '}' : ')');
12480             if (q != NULL) *q = NULLCHAR;
12481             p++;
12482         }
12483         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12484         GameEnds(moveType, p, GE_FILE);
12485         done = TRUE;
12486         if (cmailMsgLoaded) {
12487             ClearHighlights();
12488             flipView = WhiteOnMove(currentMove);
12489             if (moveType == GameUnfinished) flipView = !flipView;
12490             if (appData.debugMode)
12491               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12492         }
12493         break;
12494
12495       case EndOfFile:
12496         if (appData.debugMode)
12497           fprintf(debugFP, "Parser hit end of file\n");
12498         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12499           case MT_NONE:
12500           case MT_CHECK:
12501             break;
12502           case MT_CHECKMATE:
12503           case MT_STAINMATE:
12504             if (WhiteOnMove(currentMove)) {
12505                 GameEnds(BlackWins, "Black mates", GE_FILE);
12506             } else {
12507                 GameEnds(WhiteWins, "White mates", GE_FILE);
12508             }
12509             break;
12510           case MT_STALEMATE:
12511             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12512             break;
12513         }
12514         done = TRUE;
12515         break;
12516
12517       case MoveNumberOne:
12518         if (lastLoadGameStart == GNUChessGame) {
12519             /* GNUChessGames have numbers, but they aren't move numbers */
12520             if (appData.debugMode)
12521               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12522                       yy_text, (int) moveType);
12523             return LoadGameOneMove(EndOfFile); /* tail recursion */
12524         }
12525         /* else fall thru */
12526
12527       case XBoardGame:
12528       case GNUChessGame:
12529       case PGNTag:
12530         /* Reached start of next game in file */
12531         if (appData.debugMode)
12532           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12533         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12534           case MT_NONE:
12535           case MT_CHECK:
12536             break;
12537           case MT_CHECKMATE:
12538           case MT_STAINMATE:
12539             if (WhiteOnMove(currentMove)) {
12540                 GameEnds(BlackWins, "Black mates", GE_FILE);
12541             } else {
12542                 GameEnds(WhiteWins, "White mates", GE_FILE);
12543             }
12544             break;
12545           case MT_STALEMATE:
12546             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12547             break;
12548         }
12549         done = TRUE;
12550         break;
12551
12552       case PositionDiagram:     /* should not happen; ignore */
12553       case ElapsedTime:         /* ignore */
12554       case NAG:                 /* ignore */
12555         if (appData.debugMode)
12556           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12557                   yy_text, (int) moveType);
12558         return LoadGameOneMove(EndOfFile); /* tail recursion */
12559
12560       case IllegalMove:
12561         if (appData.testLegality) {
12562             if (appData.debugMode)
12563               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12564             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12565                     (forwardMostMove / 2) + 1,
12566                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12567             DisplayError(move, 0);
12568             done = TRUE;
12569         } else {
12570             if (appData.debugMode)
12571               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12572                       yy_text, currentMoveString);
12573             if(currentMoveString[1] == '@') {
12574                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12575                 fromY = DROP_RANK;
12576             } else {
12577                 fromX = currentMoveString[0] - AAA;
12578                 fromY = currentMoveString[1] - ONE;
12579             }
12580             toX = currentMoveString[2] - AAA;
12581             toY = currentMoveString[3] - ONE;
12582             promoChar = currentMoveString[4];
12583         }
12584         break;
12585
12586       case AmbiguousMove:
12587         if (appData.debugMode)
12588           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12589         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12590                 (forwardMostMove / 2) + 1,
12591                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12592         DisplayError(move, 0);
12593         done = TRUE;
12594         break;
12595
12596       default:
12597       case ImpossibleMove:
12598         if (appData.debugMode)
12599           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12600         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12601                 (forwardMostMove / 2) + 1,
12602                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12603         DisplayError(move, 0);
12604         done = TRUE;
12605         break;
12606     }
12607
12608     if (done) {
12609         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12610             DrawPosition(FALSE, boards[currentMove]);
12611             DisplayBothClocks();
12612             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12613               DisplayComment(currentMove - 1, commentList[currentMove]);
12614         }
12615         (void) StopLoadGameTimer();
12616         gameFileFP = NULL;
12617         cmailOldMove = forwardMostMove;
12618         return FALSE;
12619     } else {
12620         /* currentMoveString is set as a side-effect of yylex */
12621
12622         thinkOutput[0] = NULLCHAR;
12623         MakeMove(fromX, fromY, toX, toY, promoChar);
12624         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12625         currentMove = forwardMostMove;
12626         return TRUE;
12627     }
12628 }
12629
12630 /* Load the nth game from the given file */
12631 int
12632 LoadGameFromFile (char *filename, int n, char *title, int useList)
12633 {
12634     FILE *f;
12635     char buf[MSG_SIZ];
12636
12637     if (strcmp(filename, "-") == 0) {
12638         f = stdin;
12639         title = "stdin";
12640     } else {
12641         f = fopen(filename, "rb");
12642         if (f == NULL) {
12643           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12644             DisplayError(buf, errno);
12645             return FALSE;
12646         }
12647     }
12648     if (fseek(f, 0, 0) == -1) {
12649         /* f is not seekable; probably a pipe */
12650         useList = FALSE;
12651     }
12652     if (useList && n == 0) {
12653         int error = GameListBuild(f);
12654         if (error) {
12655             DisplayError(_("Cannot build game list"), error);
12656         } else if (!ListEmpty(&gameList) &&
12657                    ((ListGame *) gameList.tailPred)->number > 1) {
12658             GameListPopUp(f, title);
12659             return TRUE;
12660         }
12661         GameListDestroy();
12662         n = 1;
12663     }
12664     if (n == 0) n = 1;
12665     return LoadGame(f, n, title, FALSE);
12666 }
12667
12668
12669 void
12670 MakeRegisteredMove ()
12671 {
12672     int fromX, fromY, toX, toY;
12673     char promoChar;
12674     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12675         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12676           case CMAIL_MOVE:
12677           case CMAIL_DRAW:
12678             if (appData.debugMode)
12679               fprintf(debugFP, "Restoring %s for game %d\n",
12680                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12681
12682             thinkOutput[0] = NULLCHAR;
12683             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12684             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12685             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12686             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12687             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12688             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12689             MakeMove(fromX, fromY, toX, toY, promoChar);
12690             ShowMove(fromX, fromY, toX, toY);
12691
12692             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12693               case MT_NONE:
12694               case MT_CHECK:
12695                 break;
12696
12697               case MT_CHECKMATE:
12698               case MT_STAINMATE:
12699                 if (WhiteOnMove(currentMove)) {
12700                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12701                 } else {
12702                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12703                 }
12704                 break;
12705
12706               case MT_STALEMATE:
12707                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12708                 break;
12709             }
12710
12711             break;
12712
12713           case CMAIL_RESIGN:
12714             if (WhiteOnMove(currentMove)) {
12715                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12716             } else {
12717                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12718             }
12719             break;
12720
12721           case CMAIL_ACCEPT:
12722             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12723             break;
12724
12725           default:
12726             break;
12727         }
12728     }
12729
12730     return;
12731 }
12732
12733 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12734 int
12735 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12736 {
12737     int retVal;
12738
12739     if (gameNumber > nCmailGames) {
12740         DisplayError(_("No more games in this message"), 0);
12741         return FALSE;
12742     }
12743     if (f == lastLoadGameFP) {
12744         int offset = gameNumber - lastLoadGameNumber;
12745         if (offset == 0) {
12746             cmailMsg[0] = NULLCHAR;
12747             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12748                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12749                 nCmailMovesRegistered--;
12750             }
12751             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12752             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12753                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12754             }
12755         } else {
12756             if (! RegisterMove()) return FALSE;
12757         }
12758     }
12759
12760     retVal = LoadGame(f, gameNumber, title, useList);
12761
12762     /* Make move registered during previous look at this game, if any */
12763     MakeRegisteredMove();
12764
12765     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12766         commentList[currentMove]
12767           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12768         DisplayComment(currentMove - 1, commentList[currentMove]);
12769     }
12770
12771     return retVal;
12772 }
12773
12774 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12775 int
12776 ReloadGame (int offset)
12777 {
12778     int gameNumber = lastLoadGameNumber + offset;
12779     if (lastLoadGameFP == NULL) {
12780         DisplayError(_("No game has been loaded yet"), 0);
12781         return FALSE;
12782     }
12783     if (gameNumber <= 0) {
12784         DisplayError(_("Can't back up any further"), 0);
12785         return FALSE;
12786     }
12787     if (cmailMsgLoaded) {
12788         return CmailLoadGame(lastLoadGameFP, gameNumber,
12789                              lastLoadGameTitle, lastLoadGameUseList);
12790     } else {
12791         return LoadGame(lastLoadGameFP, gameNumber,
12792                         lastLoadGameTitle, lastLoadGameUseList);
12793     }
12794 }
12795
12796 int keys[EmptySquare+1];
12797
12798 int
12799 PositionMatches (Board b1, Board b2)
12800 {
12801     int r, f, sum=0;
12802     switch(appData.searchMode) {
12803         case 1: return CompareWithRights(b1, b2);
12804         case 2:
12805             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12806                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12807             }
12808             return TRUE;
12809         case 3:
12810             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12811               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12812                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12813             }
12814             return sum==0;
12815         case 4:
12816             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12817                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12818             }
12819             return sum==0;
12820     }
12821     return TRUE;
12822 }
12823
12824 #define Q_PROMO  4
12825 #define Q_EP     3
12826 #define Q_BCASTL 2
12827 #define Q_WCASTL 1
12828
12829 int pieceList[256], quickBoard[256];
12830 ChessSquare pieceType[256] = { EmptySquare };
12831 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12832 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12833 int soughtTotal, turn;
12834 Boolean epOK, flipSearch;
12835
12836 typedef struct {
12837     unsigned char piece, to;
12838 } Move;
12839
12840 #define DSIZE (250000)
12841
12842 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12843 Move *moveDatabase = initialSpace;
12844 unsigned int movePtr, dataSize = DSIZE;
12845
12846 int
12847 MakePieceList (Board board, int *counts)
12848 {
12849     int r, f, n=Q_PROMO, total=0;
12850     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12851     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12852         int sq = f + (r<<4);
12853         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12854             quickBoard[sq] = ++n;
12855             pieceList[n] = sq;
12856             pieceType[n] = board[r][f];
12857             counts[board[r][f]]++;
12858             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12859             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12860             total++;
12861         }
12862     }
12863     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12864     return total;
12865 }
12866
12867 void
12868 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12869 {
12870     int sq = fromX + (fromY<<4);
12871     int piece = quickBoard[sq], rook;
12872     quickBoard[sq] = 0;
12873     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12874     if(piece == pieceList[1] && fromY == toY) {
12875       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12876         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12877         moveDatabase[movePtr++].piece = Q_WCASTL;
12878         quickBoard[sq] = piece;
12879         piece = quickBoard[from]; quickBoard[from] = 0;
12880         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12881       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12882         quickBoard[sq] = 0; // remove Rook
12883         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12884         moveDatabase[movePtr++].piece = Q_WCASTL;
12885         quickBoard[sq] = pieceList[1]; // put King
12886         piece = rook;
12887         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12888       }
12889     } else
12890     if(piece == pieceList[2] && fromY == toY) {
12891       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12892         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12893         moveDatabase[movePtr++].piece = Q_BCASTL;
12894         quickBoard[sq] = piece;
12895         piece = quickBoard[from]; quickBoard[from] = 0;
12896         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12897       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12898         quickBoard[sq] = 0; // remove Rook
12899         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12900         moveDatabase[movePtr++].piece = Q_BCASTL;
12901         quickBoard[sq] = pieceList[2]; // put King
12902         piece = rook;
12903         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12904       }
12905     } else
12906     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12907         quickBoard[(fromY<<4)+toX] = 0;
12908         moveDatabase[movePtr].piece = Q_EP;
12909         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12910         moveDatabase[movePtr].to = sq;
12911     } else
12912     if(promoPiece != pieceType[piece]) {
12913         moveDatabase[movePtr++].piece = Q_PROMO;
12914         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12915     }
12916     moveDatabase[movePtr].piece = piece;
12917     quickBoard[sq] = piece;
12918     movePtr++;
12919 }
12920
12921 int
12922 PackGame (Board board)
12923 {
12924     Move *newSpace = NULL;
12925     moveDatabase[movePtr].piece = 0; // terminate previous game
12926     if(movePtr > dataSize) {
12927         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12928         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12929         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12930         if(newSpace) {
12931             int i;
12932             Move *p = moveDatabase, *q = newSpace;
12933             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12934             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12935             moveDatabase = newSpace;
12936         } else { // calloc failed, we must be out of memory. Too bad...
12937             dataSize = 0; // prevent calloc events for all subsequent games
12938             return 0;     // and signal this one isn't cached
12939         }
12940     }
12941     movePtr++;
12942     MakePieceList(board, counts);
12943     return movePtr;
12944 }
12945
12946 int
12947 QuickCompare (Board board, int *minCounts, int *maxCounts)
12948 {   // compare according to search mode
12949     int r, f;
12950     switch(appData.searchMode)
12951     {
12952       case 1: // exact position match
12953         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12954         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12955             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12956         }
12957         break;
12958       case 2: // can have extra material on empty squares
12959         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12960             if(board[r][f] == EmptySquare) continue;
12961             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12962         }
12963         break;
12964       case 3: // material with exact Pawn structure
12965         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12966             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12967             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12968         } // fall through to material comparison
12969       case 4: // exact material
12970         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12971         break;
12972       case 6: // material range with given imbalance
12973         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12974         // fall through to range comparison
12975       case 5: // material range
12976         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12977     }
12978     return TRUE;
12979 }
12980
12981 int
12982 QuickScan (Board board, Move *move)
12983 {   // reconstruct game,and compare all positions in it
12984     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12985     do {
12986         int piece = move->piece;
12987         int to = move->to, from = pieceList[piece];
12988         if(found < 0) { // if already found just scan to game end for final piece count
12989           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12990            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12991            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12992                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12993             ) {
12994             static int lastCounts[EmptySquare+1];
12995             int i;
12996             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12997             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12998           } else stretch = 0;
12999           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
13000           if(found >= 0 && !appData.minPieces) return found;
13001         }
13002         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
13003           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
13004           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
13005             piece = (++move)->piece;
13006             from = pieceList[piece];
13007             counts[pieceType[piece]]--;
13008             pieceType[piece] = (ChessSquare) move->to;
13009             counts[move->to]++;
13010           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
13011             counts[pieceType[quickBoard[to]]]--;
13012             quickBoard[to] = 0; total--;
13013             move++;
13014             continue;
13015           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
13016             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
13017             from  = pieceList[piece]; // so this must be King
13018             quickBoard[from] = 0;
13019             pieceList[piece] = to;
13020             from = pieceList[(++move)->piece]; // for FRC this has to be done here
13021             quickBoard[from] = 0; // rook
13022             quickBoard[to] = piece;
13023             to = move->to; piece = move->piece;
13024             goto aftercastle;
13025           }
13026         }
13027         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
13028         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
13029         quickBoard[from] = 0;
13030       aftercastle:
13031         quickBoard[to] = piece;
13032         pieceList[piece] = to;
13033         cnt++; turn ^= 3;
13034         move++;
13035     } while(1);
13036 }
13037
13038 void
13039 InitSearch ()
13040 {
13041     int r, f;
13042     flipSearch = FALSE;
13043     CopyBoard(soughtBoard, boards[currentMove]);
13044     soughtTotal = MakePieceList(soughtBoard, maxSought);
13045     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
13046     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
13047     CopyBoard(reverseBoard, boards[currentMove]);
13048     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13049         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
13050         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
13051         reverseBoard[r][f] = piece;
13052     }
13053     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
13054     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
13055     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
13056                  || (boards[currentMove][CASTLING][2] == NoRights ||
13057                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
13058                  && (boards[currentMove][CASTLING][5] == NoRights ||
13059                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
13060       ) {
13061         flipSearch = TRUE;
13062         CopyBoard(flipBoard, soughtBoard);
13063         CopyBoard(rotateBoard, reverseBoard);
13064         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13065             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
13066             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
13067         }
13068     }
13069     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
13070     if(appData.searchMode >= 5) {
13071         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
13072         MakePieceList(soughtBoard, minSought);
13073         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
13074     }
13075     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
13076         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
13077 }
13078
13079 GameInfo dummyInfo;
13080 static int creatingBook;
13081
13082 int
13083 GameContainsPosition (FILE *f, ListGame *lg)
13084 {
13085     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
13086     int fromX, fromY, toX, toY;
13087     char promoChar;
13088     static int initDone=FALSE;
13089
13090     // weed out games based on numerical tag comparison
13091     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
13092     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
13093     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
13094     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
13095     if(!initDone) {
13096         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
13097         initDone = TRUE;
13098     }
13099     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
13100     else CopyBoard(boards[scratch], initialPosition); // default start position
13101     if(lg->moves) {
13102         turn = btm + 1;
13103         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
13104         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13105     }
13106     if(btm) plyNr++;
13107     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13108     fseek(f, lg->offset, 0);
13109     yynewfile(f);
13110     while(1) {
13111         yyboardindex = scratch;
13112         quickFlag = plyNr+1;
13113         next = Myylex();
13114         quickFlag = 0;
13115         switch(next) {
13116             case PGNTag:
13117                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13118             default:
13119                 continue;
13120
13121             case XBoardGame:
13122             case GNUChessGame:
13123                 if(plyNr) return -1; // after we have seen moves, this is for new game
13124               continue;
13125
13126             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13127             case ImpossibleMove:
13128             case WhiteWins: // game ends here with these four
13129             case BlackWins:
13130             case GameIsDrawn:
13131             case GameUnfinished:
13132                 return -1;
13133
13134             case IllegalMove:
13135                 if(appData.testLegality) return -1;
13136             case WhiteCapturesEnPassant:
13137             case BlackCapturesEnPassant:
13138             case WhitePromotion:
13139             case BlackPromotion:
13140             case WhiteNonPromotion:
13141             case BlackNonPromotion:
13142             case NormalMove:
13143             case FirstLeg:
13144             case WhiteKingSideCastle:
13145             case WhiteQueenSideCastle:
13146             case BlackKingSideCastle:
13147             case BlackQueenSideCastle:
13148             case WhiteKingSideCastleWild:
13149             case WhiteQueenSideCastleWild:
13150             case BlackKingSideCastleWild:
13151             case BlackQueenSideCastleWild:
13152             case WhiteHSideCastleFR:
13153             case WhiteASideCastleFR:
13154             case BlackHSideCastleFR:
13155             case BlackASideCastleFR:
13156                 fromX = currentMoveString[0] - AAA;
13157                 fromY = currentMoveString[1] - ONE;
13158                 toX = currentMoveString[2] - AAA;
13159                 toY = currentMoveString[3] - ONE;
13160                 promoChar = currentMoveString[4];
13161                 break;
13162             case WhiteDrop:
13163             case BlackDrop:
13164                 fromX = next == WhiteDrop ?
13165                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13166                   (int) CharToPiece(ToLower(currentMoveString[0]));
13167                 fromY = DROP_RANK;
13168                 toX = currentMoveString[2] - AAA;
13169                 toY = currentMoveString[3] - ONE;
13170                 promoChar = 0;
13171                 break;
13172         }
13173         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13174         plyNr++;
13175         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13176         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13177         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13178         if(appData.findMirror) {
13179             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13180             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13181         }
13182     }
13183 }
13184
13185 /* Load the nth game from open file f */
13186 int
13187 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13188 {
13189     ChessMove cm;
13190     char buf[MSG_SIZ];
13191     int gn = gameNumber;
13192     ListGame *lg = NULL;
13193     int numPGNTags = 0, i;
13194     int err, pos = -1;
13195     GameMode oldGameMode;
13196     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13197     char oldName[MSG_SIZ];
13198
13199     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13200
13201     if (appData.debugMode)
13202         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13203
13204     if (gameMode == Training )
13205         SetTrainingModeOff();
13206
13207     oldGameMode = gameMode;
13208     if (gameMode != BeginningOfGame) {
13209       Reset(FALSE, TRUE);
13210     }
13211     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13212
13213     gameFileFP = f;
13214     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13215         fclose(lastLoadGameFP);
13216     }
13217
13218     if (useList) {
13219         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13220
13221         if (lg) {
13222             fseek(f, lg->offset, 0);
13223             GameListHighlight(gameNumber);
13224             pos = lg->position;
13225             gn = 1;
13226         }
13227         else {
13228             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13229               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13230             else
13231             DisplayError(_("Game number out of range"), 0);
13232             return FALSE;
13233         }
13234     } else {
13235         GameListDestroy();
13236         if (fseek(f, 0, 0) == -1) {
13237             if (f == lastLoadGameFP ?
13238                 gameNumber == lastLoadGameNumber + 1 :
13239                 gameNumber == 1) {
13240                 gn = 1;
13241             } else {
13242                 DisplayError(_("Can't seek on game file"), 0);
13243                 return FALSE;
13244             }
13245         }
13246     }
13247     lastLoadGameFP = f;
13248     lastLoadGameNumber = gameNumber;
13249     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13250     lastLoadGameUseList = useList;
13251
13252     yynewfile(f);
13253
13254     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13255       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13256                 lg->gameInfo.black);
13257             DisplayTitle(buf);
13258     } else if (*title != NULLCHAR) {
13259         if (gameNumber > 1) {
13260           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13261             DisplayTitle(buf);
13262         } else {
13263             DisplayTitle(title);
13264         }
13265     }
13266
13267     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13268         gameMode = PlayFromGameFile;
13269         ModeHighlight();
13270     }
13271
13272     currentMove = forwardMostMove = backwardMostMove = 0;
13273     CopyBoard(boards[0], initialPosition);
13274     StopClocks();
13275
13276     /*
13277      * Skip the first gn-1 games in the file.
13278      * Also skip over anything that precedes an identifiable
13279      * start of game marker, to avoid being confused by
13280      * garbage at the start of the file.  Currently
13281      * recognized start of game markers are the move number "1",
13282      * the pattern "gnuchess .* game", the pattern
13283      * "^[#;%] [^ ]* game file", and a PGN tag block.
13284      * A game that starts with one of the latter two patterns
13285      * will also have a move number 1, possibly
13286      * following a position diagram.
13287      * 5-4-02: Let's try being more lenient and allowing a game to
13288      * start with an unnumbered move.  Does that break anything?
13289      */
13290     cm = lastLoadGameStart = EndOfFile;
13291     while (gn > 0) {
13292         yyboardindex = forwardMostMove;
13293         cm = (ChessMove) Myylex();
13294         switch (cm) {
13295           case EndOfFile:
13296             if (cmailMsgLoaded) {
13297                 nCmailGames = CMAIL_MAX_GAMES - gn;
13298             } else {
13299                 Reset(TRUE, TRUE);
13300                 DisplayError(_("Game not found in file"), 0);
13301             }
13302             return FALSE;
13303
13304           case GNUChessGame:
13305           case XBoardGame:
13306             gn--;
13307             lastLoadGameStart = cm;
13308             break;
13309
13310           case MoveNumberOne:
13311             switch (lastLoadGameStart) {
13312               case GNUChessGame:
13313               case XBoardGame:
13314               case PGNTag:
13315                 break;
13316               case MoveNumberOne:
13317               case EndOfFile:
13318                 gn--;           /* count this game */
13319                 lastLoadGameStart = cm;
13320                 break;
13321               default:
13322                 /* impossible */
13323                 break;
13324             }
13325             break;
13326
13327           case PGNTag:
13328             switch (lastLoadGameStart) {
13329               case GNUChessGame:
13330               case PGNTag:
13331               case MoveNumberOne:
13332               case EndOfFile:
13333                 gn--;           /* count this game */
13334                 lastLoadGameStart = cm;
13335                 break;
13336               case XBoardGame:
13337                 lastLoadGameStart = cm; /* game counted already */
13338                 break;
13339               default:
13340                 /* impossible */
13341                 break;
13342             }
13343             if (gn > 0) {
13344                 do {
13345                     yyboardindex = forwardMostMove;
13346                     cm = (ChessMove) Myylex();
13347                 } while (cm == PGNTag || cm == Comment);
13348             }
13349             break;
13350
13351           case WhiteWins:
13352           case BlackWins:
13353           case GameIsDrawn:
13354             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13355                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13356                     != CMAIL_OLD_RESULT) {
13357                     nCmailResults ++ ;
13358                     cmailResult[  CMAIL_MAX_GAMES
13359                                 - gn - 1] = CMAIL_OLD_RESULT;
13360                 }
13361             }
13362             break;
13363
13364           case NormalMove:
13365           case FirstLeg:
13366             /* Only a NormalMove can be at the start of a game
13367              * without a position diagram. */
13368             if (lastLoadGameStart == EndOfFile ) {
13369               gn--;
13370               lastLoadGameStart = MoveNumberOne;
13371             }
13372             break;
13373
13374           default:
13375             break;
13376         }
13377     }
13378
13379     if (appData.debugMode)
13380       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13381
13382     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13383
13384     if (cm == XBoardGame) {
13385         /* Skip any header junk before position diagram and/or move 1 */
13386         for (;;) {
13387             yyboardindex = forwardMostMove;
13388             cm = (ChessMove) Myylex();
13389
13390             if (cm == EndOfFile ||
13391                 cm == GNUChessGame || cm == XBoardGame) {
13392                 /* Empty game; pretend end-of-file and handle later */
13393                 cm = EndOfFile;
13394                 break;
13395             }
13396
13397             if (cm == MoveNumberOne || cm == PositionDiagram ||
13398                 cm == PGNTag || cm == Comment)
13399               break;
13400         }
13401     } else if (cm == GNUChessGame) {
13402         if (gameInfo.event != NULL) {
13403             free(gameInfo.event);
13404         }
13405         gameInfo.event = StrSave(yy_text);
13406     }
13407
13408     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13409     while (cm == PGNTag) {
13410         if (appData.debugMode)
13411           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13412         err = ParsePGNTag(yy_text, &gameInfo);
13413         if (!err) numPGNTags++;
13414
13415         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13416         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13417             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13418             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13419             InitPosition(TRUE);
13420             oldVariant = gameInfo.variant;
13421             if (appData.debugMode)
13422               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13423         }
13424
13425
13426         if (gameInfo.fen != NULL) {
13427           Board initial_position;
13428           startedFromSetupPosition = TRUE;
13429           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13430             Reset(TRUE, TRUE);
13431             DisplayError(_("Bad FEN position in file"), 0);
13432             return FALSE;
13433           }
13434           CopyBoard(boards[0], initial_position);
13435           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13436             CopyBoard(initialPosition, initial_position);
13437           if (blackPlaysFirst) {
13438             currentMove = forwardMostMove = backwardMostMove = 1;
13439             CopyBoard(boards[1], initial_position);
13440             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13441             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13442             timeRemaining[0][1] = whiteTimeRemaining;
13443             timeRemaining[1][1] = blackTimeRemaining;
13444             if (commentList[0] != NULL) {
13445               commentList[1] = commentList[0];
13446               commentList[0] = NULL;
13447             }
13448           } else {
13449             currentMove = forwardMostMove = backwardMostMove = 0;
13450           }
13451           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13452           {   int i;
13453               initialRulePlies = FENrulePlies;
13454               for( i=0; i< nrCastlingRights; i++ )
13455                   initialRights[i] = initial_position[CASTLING][i];
13456           }
13457           yyboardindex = forwardMostMove;
13458           free(gameInfo.fen);
13459           gameInfo.fen = NULL;
13460         }
13461
13462         yyboardindex = forwardMostMove;
13463         cm = (ChessMove) Myylex();
13464
13465         /* Handle comments interspersed among the tags */
13466         while (cm == Comment) {
13467             char *p;
13468             if (appData.debugMode)
13469               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13470             p = yy_text;
13471             AppendComment(currentMove, p, FALSE);
13472             yyboardindex = forwardMostMove;
13473             cm = (ChessMove) Myylex();
13474         }
13475     }
13476
13477     /* don't rely on existence of Event tag since if game was
13478      * pasted from clipboard the Event tag may not exist
13479      */
13480     if (numPGNTags > 0){
13481         char *tags;
13482         if (gameInfo.variant == VariantNormal) {
13483           VariantClass v = StringToVariant(gameInfo.event);
13484           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13485           if(v < VariantShogi) gameInfo.variant = v;
13486         }
13487         if (!matchMode) {
13488           if( appData.autoDisplayTags ) {
13489             tags = PGNTags(&gameInfo);
13490             TagsPopUp(tags, CmailMsg());
13491             free(tags);
13492           }
13493         }
13494     } else {
13495         /* Make something up, but don't display it now */
13496         SetGameInfo();
13497         TagsPopDown();
13498     }
13499
13500     if (cm == PositionDiagram) {
13501         int i, j;
13502         char *p;
13503         Board initial_position;
13504
13505         if (appData.debugMode)
13506           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13507
13508         if (!startedFromSetupPosition) {
13509             p = yy_text;
13510             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13511               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13512                 switch (*p) {
13513                   case '{':
13514                   case '[':
13515                   case '-':
13516                   case ' ':
13517                   case '\t':
13518                   case '\n':
13519                   case '\r':
13520                     break;
13521                   default:
13522                     initial_position[i][j++] = CharToPiece(*p);
13523                     break;
13524                 }
13525             while (*p == ' ' || *p == '\t' ||
13526                    *p == '\n' || *p == '\r') p++;
13527
13528             if (strncmp(p, "black", strlen("black"))==0)
13529               blackPlaysFirst = TRUE;
13530             else
13531               blackPlaysFirst = FALSE;
13532             startedFromSetupPosition = TRUE;
13533
13534             CopyBoard(boards[0], initial_position);
13535             if (blackPlaysFirst) {
13536                 currentMove = forwardMostMove = backwardMostMove = 1;
13537                 CopyBoard(boards[1], initial_position);
13538                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13539                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13540                 timeRemaining[0][1] = whiteTimeRemaining;
13541                 timeRemaining[1][1] = blackTimeRemaining;
13542                 if (commentList[0] != NULL) {
13543                     commentList[1] = commentList[0];
13544                     commentList[0] = NULL;
13545                 }
13546             } else {
13547                 currentMove = forwardMostMove = backwardMostMove = 0;
13548             }
13549         }
13550         yyboardindex = forwardMostMove;
13551         cm = (ChessMove) Myylex();
13552     }
13553
13554   if(!creatingBook) {
13555     if (first.pr == NoProc) {
13556         StartChessProgram(&first);
13557     }
13558     InitChessProgram(&first, FALSE);
13559     if(gameInfo.variant == VariantUnknown && *oldName) {
13560         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13561         gameInfo.variant = v;
13562     }
13563     SendToProgram("force\n", &first);
13564     if (startedFromSetupPosition) {
13565         SendBoard(&first, forwardMostMove);
13566     if (appData.debugMode) {
13567         fprintf(debugFP, "Load Game\n");
13568     }
13569         DisplayBothClocks();
13570     }
13571   }
13572
13573     /* [HGM] server: flag to write setup moves in broadcast file as one */
13574     loadFlag = appData.suppressLoadMoves;
13575
13576     while (cm == Comment) {
13577         char *p;
13578         if (appData.debugMode)
13579           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13580         p = yy_text;
13581         AppendComment(currentMove, p, FALSE);
13582         yyboardindex = forwardMostMove;
13583         cm = (ChessMove) Myylex();
13584     }
13585
13586     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13587         cm == WhiteWins || cm == BlackWins ||
13588         cm == GameIsDrawn || cm == GameUnfinished) {
13589         DisplayMessage("", _("No moves in game"));
13590         if (cmailMsgLoaded) {
13591             if (appData.debugMode)
13592               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13593             ClearHighlights();
13594             flipView = FALSE;
13595         }
13596         DrawPosition(FALSE, boards[currentMove]);
13597         DisplayBothClocks();
13598         gameMode = EditGame;
13599         ModeHighlight();
13600         gameFileFP = NULL;
13601         cmailOldMove = 0;
13602         return TRUE;
13603     }
13604
13605     // [HGM] PV info: routine tests if comment empty
13606     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13607         DisplayComment(currentMove - 1, commentList[currentMove]);
13608     }
13609     if (!matchMode && appData.timeDelay != 0)
13610       DrawPosition(FALSE, boards[currentMove]);
13611
13612     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13613       programStats.ok_to_send = 1;
13614     }
13615
13616     /* if the first token after the PGN tags is a move
13617      * and not move number 1, retrieve it from the parser
13618      */
13619     if (cm != MoveNumberOne)
13620         LoadGameOneMove(cm);
13621
13622     /* load the remaining moves from the file */
13623     while (LoadGameOneMove(EndOfFile)) {
13624       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13625       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13626     }
13627
13628     /* rewind to the start of the game */
13629     currentMove = backwardMostMove;
13630
13631     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13632
13633     if (oldGameMode == AnalyzeFile) {
13634       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13635       AnalyzeFileEvent();
13636     } else
13637     if (oldGameMode == AnalyzeMode) {
13638       AnalyzeFileEvent();
13639     }
13640
13641     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13642         long int w, b; // [HGM] adjourn: restore saved clock times
13643         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13644         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13645             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13646             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13647         }
13648     }
13649
13650     if(creatingBook) return TRUE;
13651     if (!matchMode && pos > 0) {
13652         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13653     } else
13654     if (matchMode || appData.timeDelay == 0) {
13655       ToEndEvent();
13656     } else if (appData.timeDelay > 0) {
13657       AutoPlayGameLoop();
13658     }
13659
13660     if (appData.debugMode)
13661         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13662
13663     loadFlag = 0; /* [HGM] true game starts */
13664     return TRUE;
13665 }
13666
13667 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13668 int
13669 ReloadPosition (int offset)
13670 {
13671     int positionNumber = lastLoadPositionNumber + offset;
13672     if (lastLoadPositionFP == NULL) {
13673         DisplayError(_("No position has been loaded yet"), 0);
13674         return FALSE;
13675     }
13676     if (positionNumber <= 0) {
13677         DisplayError(_("Can't back up any further"), 0);
13678         return FALSE;
13679     }
13680     return LoadPosition(lastLoadPositionFP, positionNumber,
13681                         lastLoadPositionTitle);
13682 }
13683
13684 /* Load the nth position from the given file */
13685 int
13686 LoadPositionFromFile (char *filename, int n, char *title)
13687 {
13688     FILE *f;
13689     char buf[MSG_SIZ];
13690
13691     if (strcmp(filename, "-") == 0) {
13692         return LoadPosition(stdin, n, "stdin");
13693     } else {
13694         f = fopen(filename, "rb");
13695         if (f == NULL) {
13696             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13697             DisplayError(buf, errno);
13698             return FALSE;
13699         } else {
13700             return LoadPosition(f, n, title);
13701         }
13702     }
13703 }
13704
13705 /* Load the nth position from the given open file, and close it */
13706 int
13707 LoadPosition (FILE *f, int positionNumber, char *title)
13708 {
13709     char *p, line[MSG_SIZ];
13710     Board initial_position;
13711     int i, j, fenMode, pn;
13712
13713     if (gameMode == Training )
13714         SetTrainingModeOff();
13715
13716     if (gameMode != BeginningOfGame) {
13717         Reset(FALSE, TRUE);
13718     }
13719     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13720         fclose(lastLoadPositionFP);
13721     }
13722     if (positionNumber == 0) positionNumber = 1;
13723     lastLoadPositionFP = f;
13724     lastLoadPositionNumber = positionNumber;
13725     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13726     if (first.pr == NoProc && !appData.noChessProgram) {
13727       StartChessProgram(&first);
13728       InitChessProgram(&first, FALSE);
13729     }
13730     pn = positionNumber;
13731     if (positionNumber < 0) {
13732         /* Negative position number means to seek to that byte offset */
13733         if (fseek(f, -positionNumber, 0) == -1) {
13734             DisplayError(_("Can't seek on position file"), 0);
13735             return FALSE;
13736         };
13737         pn = 1;
13738     } else {
13739         if (fseek(f, 0, 0) == -1) {
13740             if (f == lastLoadPositionFP ?
13741                 positionNumber == lastLoadPositionNumber + 1 :
13742                 positionNumber == 1) {
13743                 pn = 1;
13744             } else {
13745                 DisplayError(_("Can't seek on position file"), 0);
13746                 return FALSE;
13747             }
13748         }
13749     }
13750     /* See if this file is FEN or old-style xboard */
13751     if (fgets(line, MSG_SIZ, f) == NULL) {
13752         DisplayError(_("Position not found in file"), 0);
13753         return FALSE;
13754     }
13755     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13756     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13757
13758     if (pn >= 2) {
13759         if (fenMode || line[0] == '#') pn--;
13760         while (pn > 0) {
13761             /* skip positions before number pn */
13762             if (fgets(line, MSG_SIZ, f) == NULL) {
13763                 Reset(TRUE, TRUE);
13764                 DisplayError(_("Position not found in file"), 0);
13765                 return FALSE;
13766             }
13767             if (fenMode || line[0] == '#') pn--;
13768         }
13769     }
13770
13771     if (fenMode) {
13772         char *p;
13773         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13774             DisplayError(_("Bad FEN position in file"), 0);
13775             return FALSE;
13776         }
13777         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13778             sscanf(p+4, "%[^;]", bestMove);
13779         } else *bestMove = NULLCHAR;
13780         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13781             sscanf(p+4, "%[^;]", avoidMove);
13782         } else *avoidMove = NULLCHAR;
13783     } else {
13784         (void) fgets(line, MSG_SIZ, f);
13785         (void) fgets(line, MSG_SIZ, f);
13786
13787         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13788             (void) fgets(line, MSG_SIZ, f);
13789             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13790                 if (*p == ' ')
13791                   continue;
13792                 initial_position[i][j++] = CharToPiece(*p);
13793             }
13794         }
13795
13796         blackPlaysFirst = FALSE;
13797         if (!feof(f)) {
13798             (void) fgets(line, MSG_SIZ, f);
13799             if (strncmp(line, "black", strlen("black"))==0)
13800               blackPlaysFirst = TRUE;
13801         }
13802     }
13803     startedFromSetupPosition = TRUE;
13804
13805     CopyBoard(boards[0], initial_position);
13806     if (blackPlaysFirst) {
13807         currentMove = forwardMostMove = backwardMostMove = 1;
13808         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13809         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13810         CopyBoard(boards[1], initial_position);
13811         DisplayMessage("", _("Black to play"));
13812     } else {
13813         currentMove = forwardMostMove = backwardMostMove = 0;
13814         DisplayMessage("", _("White to play"));
13815     }
13816     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13817     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13818         SendToProgram("force\n", &first);
13819         SendBoard(&first, forwardMostMove);
13820     }
13821     if (appData.debugMode) {
13822 int i, j;
13823   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13824   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13825         fprintf(debugFP, "Load Position\n");
13826     }
13827
13828     if (positionNumber > 1) {
13829       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13830         DisplayTitle(line);
13831     } else {
13832         DisplayTitle(title);
13833     }
13834     gameMode = EditGame;
13835     ModeHighlight();
13836     ResetClocks();
13837     timeRemaining[0][1] = whiteTimeRemaining;
13838     timeRemaining[1][1] = blackTimeRemaining;
13839     DrawPosition(FALSE, boards[currentMove]);
13840
13841     return TRUE;
13842 }
13843
13844
13845 void
13846 CopyPlayerNameIntoFileName (char **dest, char *src)
13847 {
13848     while (*src != NULLCHAR && *src != ',') {
13849         if (*src == ' ') {
13850             *(*dest)++ = '_';
13851             src++;
13852         } else {
13853             *(*dest)++ = *src++;
13854         }
13855     }
13856 }
13857
13858 char *
13859 DefaultFileName (char *ext)
13860 {
13861     static char def[MSG_SIZ];
13862     char *p;
13863
13864     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13865         p = def;
13866         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13867         *p++ = '-';
13868         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13869         *p++ = '.';
13870         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13871     } else {
13872         def[0] = NULLCHAR;
13873     }
13874     return def;
13875 }
13876
13877 /* Save the current game to the given file */
13878 int
13879 SaveGameToFile (char *filename, int append)
13880 {
13881     FILE *f;
13882     char buf[MSG_SIZ];
13883     int result, i, t,tot=0;
13884
13885     if (strcmp(filename, "-") == 0) {
13886         return SaveGame(stdout, 0, NULL);
13887     } else {
13888         for(i=0; i<10; i++) { // upto 10 tries
13889              f = fopen(filename, append ? "a" : "w");
13890              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13891              if(f || errno != 13) break;
13892              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13893              tot += t;
13894         }
13895         if (f == NULL) {
13896             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13897             DisplayError(buf, errno);
13898             return FALSE;
13899         } else {
13900             safeStrCpy(buf, lastMsg, MSG_SIZ);
13901             DisplayMessage(_("Waiting for access to save file"), "");
13902             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13903             DisplayMessage(_("Saving game"), "");
13904             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13905             result = SaveGame(f, 0, NULL);
13906             DisplayMessage(buf, "");
13907             return result;
13908         }
13909     }
13910 }
13911
13912 char *
13913 SavePart (char *str)
13914 {
13915     static char buf[MSG_SIZ];
13916     char *p;
13917
13918     p = strchr(str, ' ');
13919     if (p == NULL) return str;
13920     strncpy(buf, str, p - str);
13921     buf[p - str] = NULLCHAR;
13922     return buf;
13923 }
13924
13925 #define PGN_MAX_LINE 75
13926
13927 #define PGN_SIDE_WHITE  0
13928 #define PGN_SIDE_BLACK  1
13929
13930 static int
13931 FindFirstMoveOutOfBook (int side)
13932 {
13933     int result = -1;
13934
13935     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13936         int index = backwardMostMove;
13937         int has_book_hit = 0;
13938
13939         if( (index % 2) != side ) {
13940             index++;
13941         }
13942
13943         while( index < forwardMostMove ) {
13944             /* Check to see if engine is in book */
13945             int depth = pvInfoList[index].depth;
13946             int score = pvInfoList[index].score;
13947             int in_book = 0;
13948
13949             if( depth <= 2 ) {
13950                 in_book = 1;
13951             }
13952             else if( score == 0 && depth == 63 ) {
13953                 in_book = 1; /* Zappa */
13954             }
13955             else if( score == 2 && depth == 99 ) {
13956                 in_book = 1; /* Abrok */
13957             }
13958
13959             has_book_hit += in_book;
13960
13961             if( ! in_book ) {
13962                 result = index;
13963
13964                 break;
13965             }
13966
13967             index += 2;
13968         }
13969     }
13970
13971     return result;
13972 }
13973
13974 void
13975 GetOutOfBookInfo (char * buf)
13976 {
13977     int oob[2];
13978     int i;
13979     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13980
13981     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13982     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13983
13984     *buf = '\0';
13985
13986     if( oob[0] >= 0 || oob[1] >= 0 ) {
13987         for( i=0; i<2; i++ ) {
13988             int idx = oob[i];
13989
13990             if( idx >= 0 ) {
13991                 if( i > 0 && oob[0] >= 0 ) {
13992                     strcat( buf, "   " );
13993                 }
13994
13995                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13996                 sprintf( buf+strlen(buf), "%s%.2f",
13997                     pvInfoList[idx].score >= 0 ? "+" : "",
13998                     pvInfoList[idx].score / 100.0 );
13999             }
14000         }
14001     }
14002 }
14003
14004 /* Save game in PGN style */
14005 static void
14006 SaveGamePGN2 (FILE *f)
14007 {
14008     int i, offset, linelen, newblock;
14009 //    char *movetext;
14010     char numtext[32];
14011     int movelen, numlen, blank;
14012     char move_buffer[100]; /* [AS] Buffer for move+PV info */
14013
14014     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14015
14016     PrintPGNTags(f, &gameInfo);
14017
14018     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
14019
14020     if (backwardMostMove > 0 || startedFromSetupPosition) {
14021         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
14022         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
14023         fprintf(f, "\n{--------------\n");
14024         PrintPosition(f, backwardMostMove);
14025         fprintf(f, "--------------}\n");
14026         free(fen);
14027     }
14028     else {
14029         /* [AS] Out of book annotation */
14030         if( appData.saveOutOfBookInfo ) {
14031             char buf[64];
14032
14033             GetOutOfBookInfo( buf );
14034
14035             if( buf[0] != '\0' ) {
14036                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
14037             }
14038         }
14039
14040         fprintf(f, "\n");
14041     }
14042
14043     i = backwardMostMove;
14044     linelen = 0;
14045     newblock = TRUE;
14046
14047     while (i < forwardMostMove) {
14048         /* Print comments preceding this move */
14049         if (commentList[i] != NULL) {
14050             if (linelen > 0) fprintf(f, "\n");
14051             fprintf(f, "%s", commentList[i]);
14052             linelen = 0;
14053             newblock = TRUE;
14054         }
14055
14056         /* Format move number */
14057         if ((i % 2) == 0)
14058           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
14059         else
14060           if (newblock)
14061             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
14062           else
14063             numtext[0] = NULLCHAR;
14064
14065         numlen = strlen(numtext);
14066         newblock = FALSE;
14067
14068         /* Print move number */
14069         blank = linelen > 0 && numlen > 0;
14070         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
14071             fprintf(f, "\n");
14072             linelen = 0;
14073             blank = 0;
14074         }
14075         if (blank) {
14076             fprintf(f, " ");
14077             linelen++;
14078         }
14079         fprintf(f, "%s", numtext);
14080         linelen += numlen;
14081
14082         /* Get move */
14083         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
14084         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
14085
14086         /* Print move */
14087         blank = linelen > 0 && movelen > 0;
14088         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14089             fprintf(f, "\n");
14090             linelen = 0;
14091             blank = 0;
14092         }
14093         if (blank) {
14094             fprintf(f, " ");
14095             linelen++;
14096         }
14097         fprintf(f, "%s", move_buffer);
14098         linelen += movelen;
14099
14100         /* [AS] Add PV info if present */
14101         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
14102             /* [HGM] add time */
14103             char buf[MSG_SIZ]; int seconds;
14104
14105             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14106
14107             if( seconds <= 0)
14108               buf[0] = 0;
14109             else
14110               if( seconds < 30 )
14111                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14112               else
14113                 {
14114                   seconds = (seconds + 4)/10; // round to full seconds
14115                   if( seconds < 60 )
14116                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14117                   else
14118                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14119                 }
14120
14121             if(appData.cumulativeTimePGN) {
14122                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14123             }
14124
14125             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14126                       pvInfoList[i].score >= 0 ? "+" : "",
14127                       pvInfoList[i].score / 100.0,
14128                       pvInfoList[i].depth,
14129                       buf );
14130
14131             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14132
14133             /* Print score/depth */
14134             blank = linelen > 0 && movelen > 0;
14135             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14136                 fprintf(f, "\n");
14137                 linelen = 0;
14138                 blank = 0;
14139             }
14140             if (blank) {
14141                 fprintf(f, " ");
14142                 linelen++;
14143             }
14144             fprintf(f, "%s", move_buffer);
14145             linelen += movelen;
14146         }
14147
14148         i++;
14149     }
14150
14151     /* Start a new line */
14152     if (linelen > 0) fprintf(f, "\n");
14153
14154     /* Print comments after last move */
14155     if (commentList[i] != NULL) {
14156         fprintf(f, "%s\n", commentList[i]);
14157     }
14158
14159     /* Print result */
14160     if (gameInfo.resultDetails != NULL &&
14161         gameInfo.resultDetails[0] != NULLCHAR) {
14162         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14163         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14164            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14165             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14166         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14167     } else {
14168         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14169     }
14170 }
14171
14172 /* Save game in PGN style and close the file */
14173 int
14174 SaveGamePGN (FILE *f)
14175 {
14176     SaveGamePGN2(f);
14177     fclose(f);
14178     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14179     return TRUE;
14180 }
14181
14182 /* Save game in old style and close the file */
14183 int
14184 SaveGameOldStyle (FILE *f)
14185 {
14186     int i, offset;
14187     time_t tm;
14188
14189     tm = time((time_t *) NULL);
14190
14191     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14192     PrintOpponents(f);
14193
14194     if (backwardMostMove > 0 || startedFromSetupPosition) {
14195         fprintf(f, "\n[--------------\n");
14196         PrintPosition(f, backwardMostMove);
14197         fprintf(f, "--------------]\n");
14198     } else {
14199         fprintf(f, "\n");
14200     }
14201
14202     i = backwardMostMove;
14203     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14204
14205     while (i < forwardMostMove) {
14206         if (commentList[i] != NULL) {
14207             fprintf(f, "[%s]\n", commentList[i]);
14208         }
14209
14210         if ((i % 2) == 1) {
14211             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14212             i++;
14213         } else {
14214             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14215             i++;
14216             if (commentList[i] != NULL) {
14217                 fprintf(f, "\n");
14218                 continue;
14219             }
14220             if (i >= forwardMostMove) {
14221                 fprintf(f, "\n");
14222                 break;
14223             }
14224             fprintf(f, "%s\n", parseList[i]);
14225             i++;
14226         }
14227     }
14228
14229     if (commentList[i] != NULL) {
14230         fprintf(f, "[%s]\n", commentList[i]);
14231     }
14232
14233     /* This isn't really the old style, but it's close enough */
14234     if (gameInfo.resultDetails != NULL &&
14235         gameInfo.resultDetails[0] != NULLCHAR) {
14236         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14237                 gameInfo.resultDetails);
14238     } else {
14239         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14240     }
14241
14242     fclose(f);
14243     return TRUE;
14244 }
14245
14246 /* Save the current game to open file f and close the file */
14247 int
14248 SaveGame (FILE *f, int dummy, char *dummy2)
14249 {
14250     if (gameMode == EditPosition) EditPositionDone(TRUE);
14251     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14252     if (appData.oldSaveStyle)
14253       return SaveGameOldStyle(f);
14254     else
14255       return SaveGamePGN(f);
14256 }
14257
14258 /* Save the current position to the given file */
14259 int
14260 SavePositionToFile (char *filename)
14261 {
14262     FILE *f;
14263     char buf[MSG_SIZ];
14264
14265     if (strcmp(filename, "-") == 0) {
14266         return SavePosition(stdout, 0, NULL);
14267     } else {
14268         f = fopen(filename, "a");
14269         if (f == NULL) {
14270             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14271             DisplayError(buf, errno);
14272             return FALSE;
14273         } else {
14274             safeStrCpy(buf, lastMsg, MSG_SIZ);
14275             DisplayMessage(_("Waiting for access to save file"), "");
14276             flock(fileno(f), LOCK_EX); // [HGM] lock
14277             DisplayMessage(_("Saving position"), "");
14278             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14279             SavePosition(f, 0, NULL);
14280             DisplayMessage(buf, "");
14281             return TRUE;
14282         }
14283     }
14284 }
14285
14286 /* Save the current position to the given open file and close the file */
14287 int
14288 SavePosition (FILE *f, int dummy, char *dummy2)
14289 {
14290     time_t tm;
14291     char *fen;
14292
14293     if (gameMode == EditPosition) EditPositionDone(TRUE);
14294     if (appData.oldSaveStyle) {
14295         tm = time((time_t *) NULL);
14296
14297         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14298         PrintOpponents(f);
14299         fprintf(f, "[--------------\n");
14300         PrintPosition(f, currentMove);
14301         fprintf(f, "--------------]\n");
14302     } else {
14303         fen = PositionToFEN(currentMove, NULL, 1);
14304         fprintf(f, "%s\n", fen);
14305         free(fen);
14306     }
14307     fclose(f);
14308     return TRUE;
14309 }
14310
14311 void
14312 ReloadCmailMsgEvent (int unregister)
14313 {
14314 #if !WIN32
14315     static char *inFilename = NULL;
14316     static char *outFilename;
14317     int i;
14318     struct stat inbuf, outbuf;
14319     int status;
14320
14321     /* Any registered moves are unregistered if unregister is set, */
14322     /* i.e. invoked by the signal handler */
14323     if (unregister) {
14324         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14325             cmailMoveRegistered[i] = FALSE;
14326             if (cmailCommentList[i] != NULL) {
14327                 free(cmailCommentList[i]);
14328                 cmailCommentList[i] = NULL;
14329             }
14330         }
14331         nCmailMovesRegistered = 0;
14332     }
14333
14334     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14335         cmailResult[i] = CMAIL_NOT_RESULT;
14336     }
14337     nCmailResults = 0;
14338
14339     if (inFilename == NULL) {
14340         /* Because the filenames are static they only get malloced once  */
14341         /* and they never get freed                                      */
14342         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14343         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14344
14345         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14346         sprintf(outFilename, "%s.out", appData.cmailGameName);
14347     }
14348
14349     status = stat(outFilename, &outbuf);
14350     if (status < 0) {
14351         cmailMailedMove = FALSE;
14352     } else {
14353         status = stat(inFilename, &inbuf);
14354         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14355     }
14356
14357     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14358        counts the games, notes how each one terminated, etc.
14359
14360        It would be nice to remove this kludge and instead gather all
14361        the information while building the game list.  (And to keep it
14362        in the game list nodes instead of having a bunch of fixed-size
14363        parallel arrays.)  Note this will require getting each game's
14364        termination from the PGN tags, as the game list builder does
14365        not process the game moves.  --mann
14366        */
14367     cmailMsgLoaded = TRUE;
14368     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14369
14370     /* Load first game in the file or popup game menu */
14371     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14372
14373 #endif /* !WIN32 */
14374     return;
14375 }
14376
14377 int
14378 RegisterMove ()
14379 {
14380     FILE *f;
14381     char string[MSG_SIZ];
14382
14383     if (   cmailMailedMove
14384         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14385         return TRUE;            /* Allow free viewing  */
14386     }
14387
14388     /* Unregister move to ensure that we don't leave RegisterMove        */
14389     /* with the move registered when the conditions for registering no   */
14390     /* longer hold                                                       */
14391     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14392         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14393         nCmailMovesRegistered --;
14394
14395         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14396           {
14397               free(cmailCommentList[lastLoadGameNumber - 1]);
14398               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14399           }
14400     }
14401
14402     if (cmailOldMove == -1) {
14403         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14404         return FALSE;
14405     }
14406
14407     if (currentMove > cmailOldMove + 1) {
14408         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14409         return FALSE;
14410     }
14411
14412     if (currentMove < cmailOldMove) {
14413         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14414         return FALSE;
14415     }
14416
14417     if (forwardMostMove > currentMove) {
14418         /* Silently truncate extra moves */
14419         TruncateGame();
14420     }
14421
14422     if (   (currentMove == cmailOldMove + 1)
14423         || (   (currentMove == cmailOldMove)
14424             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14425                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14426         if (gameInfo.result != GameUnfinished) {
14427             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14428         }
14429
14430         if (commentList[currentMove] != NULL) {
14431             cmailCommentList[lastLoadGameNumber - 1]
14432               = StrSave(commentList[currentMove]);
14433         }
14434         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14435
14436         if (appData.debugMode)
14437           fprintf(debugFP, "Saving %s for game %d\n",
14438                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14439
14440         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14441
14442         f = fopen(string, "w");
14443         if (appData.oldSaveStyle) {
14444             SaveGameOldStyle(f); /* also closes the file */
14445
14446             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14447             f = fopen(string, "w");
14448             SavePosition(f, 0, NULL); /* also closes the file */
14449         } else {
14450             fprintf(f, "{--------------\n");
14451             PrintPosition(f, currentMove);
14452             fprintf(f, "--------------}\n\n");
14453
14454             SaveGame(f, 0, NULL); /* also closes the file*/
14455         }
14456
14457         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14458         nCmailMovesRegistered ++;
14459     } else if (nCmailGames == 1) {
14460         DisplayError(_("You have not made a move yet"), 0);
14461         return FALSE;
14462     }
14463
14464     return TRUE;
14465 }
14466
14467 void
14468 MailMoveEvent ()
14469 {
14470 #if !WIN32
14471     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14472     FILE *commandOutput;
14473     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14474     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14475     int nBuffers;
14476     int i;
14477     int archived;
14478     char *arcDir;
14479
14480     if (! cmailMsgLoaded) {
14481         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14482         return;
14483     }
14484
14485     if (nCmailGames == nCmailResults) {
14486         DisplayError(_("No unfinished games"), 0);
14487         return;
14488     }
14489
14490 #if CMAIL_PROHIBIT_REMAIL
14491     if (cmailMailedMove) {
14492       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);
14493         DisplayError(msg, 0);
14494         return;
14495     }
14496 #endif
14497
14498     if (! (cmailMailedMove || RegisterMove())) return;
14499
14500     if (   cmailMailedMove
14501         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14502       snprintf(string, MSG_SIZ, partCommandString,
14503                appData.debugMode ? " -v" : "", appData.cmailGameName);
14504         commandOutput = popen(string, "r");
14505
14506         if (commandOutput == NULL) {
14507             DisplayError(_("Failed to invoke cmail"), 0);
14508         } else {
14509             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14510                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14511             }
14512             if (nBuffers > 1) {
14513                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14514                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14515                 nBytes = MSG_SIZ - 1;
14516             } else {
14517                 (void) memcpy(msg, buffer, nBytes);
14518             }
14519             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14520
14521             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14522                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14523
14524                 archived = TRUE;
14525                 for (i = 0; i < nCmailGames; i ++) {
14526                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14527                         archived = FALSE;
14528                     }
14529                 }
14530                 if (   archived
14531                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14532                         != NULL)) {
14533                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14534                            arcDir,
14535                            appData.cmailGameName,
14536                            gameInfo.date);
14537                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14538                     cmailMsgLoaded = FALSE;
14539                 }
14540             }
14541
14542             DisplayInformation(msg);
14543             pclose(commandOutput);
14544         }
14545     } else {
14546         if ((*cmailMsg) != '\0') {
14547             DisplayInformation(cmailMsg);
14548         }
14549     }
14550
14551     return;
14552 #endif /* !WIN32 */
14553 }
14554
14555 char *
14556 CmailMsg ()
14557 {
14558 #if WIN32
14559     return NULL;
14560 #else
14561     int  prependComma = 0;
14562     char number[5];
14563     char string[MSG_SIZ];       /* Space for game-list */
14564     int  i;
14565
14566     if (!cmailMsgLoaded) return "";
14567
14568     if (cmailMailedMove) {
14569       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14570     } else {
14571         /* Create a list of games left */
14572       snprintf(string, MSG_SIZ, "[");
14573         for (i = 0; i < nCmailGames; i ++) {
14574             if (! (   cmailMoveRegistered[i]
14575                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14576                 if (prependComma) {
14577                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14578                 } else {
14579                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14580                     prependComma = 1;
14581                 }
14582
14583                 strcat(string, number);
14584             }
14585         }
14586         strcat(string, "]");
14587
14588         if (nCmailMovesRegistered + nCmailResults == 0) {
14589             switch (nCmailGames) {
14590               case 1:
14591                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14592                 break;
14593
14594               case 2:
14595                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14596                 break;
14597
14598               default:
14599                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14600                          nCmailGames);
14601                 break;
14602             }
14603         } else {
14604             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14605               case 1:
14606                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14607                          string);
14608                 break;
14609
14610               case 0:
14611                 if (nCmailResults == nCmailGames) {
14612                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14613                 } else {
14614                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14615                 }
14616                 break;
14617
14618               default:
14619                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14620                          string);
14621             }
14622         }
14623     }
14624     return cmailMsg;
14625 #endif /* WIN32 */
14626 }
14627
14628 void
14629 ResetGameEvent ()
14630 {
14631     if (gameMode == Training)
14632       SetTrainingModeOff();
14633
14634     Reset(TRUE, TRUE);
14635     cmailMsgLoaded = FALSE;
14636     if (appData.icsActive) {
14637       SendToICS(ics_prefix);
14638       SendToICS("refresh\n");
14639     }
14640 }
14641
14642 void
14643 ExitEvent (int status)
14644 {
14645     exiting++;
14646     if (exiting > 2) {
14647       /* Give up on clean exit */
14648       exit(status);
14649     }
14650     if (exiting > 1) {
14651       /* Keep trying for clean exit */
14652       return;
14653     }
14654
14655     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14656     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14657
14658     if (telnetISR != NULL) {
14659       RemoveInputSource(telnetISR);
14660     }
14661     if (icsPR != NoProc) {
14662       DestroyChildProcess(icsPR, TRUE);
14663     }
14664
14665     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14666     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14667
14668     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14669     /* make sure this other one finishes before killing it!                  */
14670     if(endingGame) { int count = 0;
14671         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14672         while(endingGame && count++ < 10) DoSleep(1);
14673         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14674     }
14675
14676     /* Kill off chess programs */
14677     if (first.pr != NoProc) {
14678         ExitAnalyzeMode();
14679
14680         DoSleep( appData.delayBeforeQuit );
14681         SendToProgram("quit\n", &first);
14682         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14683     }
14684     if (second.pr != NoProc) {
14685         DoSleep( appData.delayBeforeQuit );
14686         SendToProgram("quit\n", &second);
14687         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14688     }
14689     if (first.isr != NULL) {
14690         RemoveInputSource(first.isr);
14691     }
14692     if (second.isr != NULL) {
14693         RemoveInputSource(second.isr);
14694     }
14695
14696     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14697     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14698
14699     ShutDownFrontEnd();
14700     exit(status);
14701 }
14702
14703 void
14704 PauseEngine (ChessProgramState *cps)
14705 {
14706     SendToProgram("pause\n", cps);
14707     cps->pause = 2;
14708 }
14709
14710 void
14711 UnPauseEngine (ChessProgramState *cps)
14712 {
14713     SendToProgram("resume\n", cps);
14714     cps->pause = 1;
14715 }
14716
14717 void
14718 PauseEvent ()
14719 {
14720     if (appData.debugMode)
14721         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14722     if (pausing) {
14723         pausing = FALSE;
14724         ModeHighlight();
14725         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14726             StartClocks();
14727             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14728                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14729                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14730             }
14731             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14732             HandleMachineMove(stashedInputMove, stalledEngine);
14733             stalledEngine = NULL;
14734             return;
14735         }
14736         if (gameMode == MachinePlaysWhite ||
14737             gameMode == TwoMachinesPlay   ||
14738             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14739             if(first.pause)  UnPauseEngine(&first);
14740             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14741             if(second.pause) UnPauseEngine(&second);
14742             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14743             StartClocks();
14744         } else {
14745             DisplayBothClocks();
14746         }
14747         if (gameMode == PlayFromGameFile) {
14748             if (appData.timeDelay >= 0)
14749                 AutoPlayGameLoop();
14750         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14751             Reset(FALSE, TRUE);
14752             SendToICS(ics_prefix);
14753             SendToICS("refresh\n");
14754         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14755             ForwardInner(forwardMostMove);
14756         }
14757         pauseExamInvalid = FALSE;
14758     } else {
14759         switch (gameMode) {
14760           default:
14761             return;
14762           case IcsExamining:
14763             pauseExamForwardMostMove = forwardMostMove;
14764             pauseExamInvalid = FALSE;
14765             /* fall through */
14766           case IcsObserving:
14767           case IcsPlayingWhite:
14768           case IcsPlayingBlack:
14769             pausing = TRUE;
14770             ModeHighlight();
14771             return;
14772           case PlayFromGameFile:
14773             (void) StopLoadGameTimer();
14774             pausing = TRUE;
14775             ModeHighlight();
14776             break;
14777           case BeginningOfGame:
14778             if (appData.icsActive) return;
14779             /* else fall through */
14780           case MachinePlaysWhite:
14781           case MachinePlaysBlack:
14782           case TwoMachinesPlay:
14783             if (forwardMostMove == 0)
14784               return;           /* don't pause if no one has moved */
14785             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14786                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14787                 if(onMove->pause) {           // thinking engine can be paused
14788                     PauseEngine(onMove);      // do it
14789                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14790                         PauseEngine(onMove->other);
14791                     else
14792                         SendToProgram("easy\n", onMove->other);
14793                     StopClocks();
14794                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14795             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14796                 if(first.pause) {
14797                     PauseEngine(&first);
14798                     StopClocks();
14799                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14800             } else { // human on move, pause pondering by either method
14801                 if(first.pause)
14802                     PauseEngine(&first);
14803                 else if(appData.ponderNextMove)
14804                     SendToProgram("easy\n", &first);
14805                 StopClocks();
14806             }
14807             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14808           case AnalyzeMode:
14809             pausing = TRUE;
14810             ModeHighlight();
14811             break;
14812         }
14813     }
14814 }
14815
14816 void
14817 EditCommentEvent ()
14818 {
14819     char title[MSG_SIZ];
14820
14821     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14822       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14823     } else {
14824       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14825                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14826                parseList[currentMove - 1]);
14827     }
14828
14829     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14830 }
14831
14832
14833 void
14834 EditTagsEvent ()
14835 {
14836     char *tags = PGNTags(&gameInfo);
14837     bookUp = FALSE;
14838     EditTagsPopUp(tags, NULL);
14839     free(tags);
14840 }
14841
14842 void
14843 StartSecond ()
14844 {
14845     if(WaitForEngine(&second, StartSecond)) return;
14846     InitChessProgram(&second, FALSE);
14847     FeedMovesToProgram(&second, currentMove);
14848
14849     SendToProgram("analyze\n", &second);
14850     second.analyzing = TRUE;
14851     ThawUI();
14852 }
14853
14854 void
14855 ToggleSecond ()
14856 {
14857   if(second.analyzing) {
14858     SendToProgram("exit\n", &second);
14859     second.analyzing = FALSE;
14860   } else {
14861     StartSecond();
14862   }
14863 }
14864
14865 /* Toggle ShowThinking */
14866 void
14867 ToggleShowThinking()
14868 {
14869   appData.showThinking = !appData.showThinking;
14870   ShowThinkingEvent();
14871 }
14872
14873 int
14874 AnalyzeModeEvent ()
14875 {
14876     char buf[MSG_SIZ];
14877
14878     if (!first.analysisSupport) {
14879       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14880       DisplayError(buf, 0);
14881       return 0;
14882     }
14883     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14884     if (appData.icsActive) {
14885         if (gameMode != IcsObserving) {
14886           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14887             DisplayError(buf, 0);
14888             /* secure check */
14889             if (appData.icsEngineAnalyze) {
14890                 if (appData.debugMode)
14891                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14892                 ExitAnalyzeMode();
14893                 ModeHighlight();
14894             }
14895             return 0;
14896         }
14897         /* if enable, user wants to disable icsEngineAnalyze */
14898         if (appData.icsEngineAnalyze) {
14899                 ExitAnalyzeMode();
14900                 ModeHighlight();
14901                 return 0;
14902         }
14903         appData.icsEngineAnalyze = TRUE;
14904         if (appData.debugMode)
14905             fprintf(debugFP, "ICS engine analyze starting... \n");
14906     }
14907
14908     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14909     if (appData.noChessProgram || gameMode == AnalyzeMode)
14910       return 0;
14911
14912     if (gameMode != AnalyzeFile) {
14913         if (!appData.icsEngineAnalyze) {
14914                EditGameEvent();
14915                if (gameMode != EditGame) return 0;
14916         }
14917         if (!appData.showThinking) ToggleShowThinking();
14918         ResurrectChessProgram();
14919         SendToProgram("analyze\n", &first);
14920         first.analyzing = TRUE;
14921         /*first.maybeThinking = TRUE;*/
14922         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14923         EngineOutputPopUp();
14924     }
14925     if (!appData.icsEngineAnalyze) {
14926         gameMode = AnalyzeMode;
14927         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14928     }
14929     pausing = FALSE;
14930     ModeHighlight();
14931     SetGameInfo();
14932
14933     StartAnalysisClock();
14934     GetTimeMark(&lastNodeCountTime);
14935     lastNodeCount = 0;
14936     return 1;
14937 }
14938
14939 void
14940 AnalyzeFileEvent ()
14941 {
14942     if (appData.noChessProgram || gameMode == AnalyzeFile)
14943       return;
14944
14945     if (!first.analysisSupport) {
14946       char buf[MSG_SIZ];
14947       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14948       DisplayError(buf, 0);
14949       return;
14950     }
14951
14952     if (gameMode != AnalyzeMode) {
14953         keepInfo = 1; // mere annotating should not alter PGN tags
14954         EditGameEvent();
14955         keepInfo = 0;
14956         if (gameMode != EditGame) return;
14957         if (!appData.showThinking) ToggleShowThinking();
14958         ResurrectChessProgram();
14959         SendToProgram("analyze\n", &first);
14960         first.analyzing = TRUE;
14961         /*first.maybeThinking = TRUE;*/
14962         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14963         EngineOutputPopUp();
14964     }
14965     gameMode = AnalyzeFile;
14966     pausing = FALSE;
14967     ModeHighlight();
14968
14969     StartAnalysisClock();
14970     GetTimeMark(&lastNodeCountTime);
14971     lastNodeCount = 0;
14972     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14973     AnalysisPeriodicEvent(1);
14974 }
14975
14976 void
14977 MachineWhiteEvent ()
14978 {
14979     char buf[MSG_SIZ];
14980     char *bookHit = NULL;
14981
14982     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14983       return;
14984
14985
14986     if (gameMode == PlayFromGameFile ||
14987         gameMode == TwoMachinesPlay  ||
14988         gameMode == Training         ||
14989         gameMode == AnalyzeMode      ||
14990         gameMode == EndOfGame)
14991         EditGameEvent();
14992
14993     if (gameMode == EditPosition)
14994         EditPositionDone(TRUE);
14995
14996     if (!WhiteOnMove(currentMove)) {
14997         DisplayError(_("It is not White's turn"), 0);
14998         return;
14999     }
15000
15001     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15002       ExitAnalyzeMode();
15003
15004     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15005         gameMode == AnalyzeFile)
15006         TruncateGame();
15007
15008     ResurrectChessProgram();    /* in case it isn't running */
15009     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
15010         gameMode = MachinePlaysWhite;
15011         ResetClocks();
15012     } else
15013     gameMode = MachinePlaysWhite;
15014     pausing = FALSE;
15015     ModeHighlight();
15016     SetGameInfo();
15017     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15018     DisplayTitle(buf);
15019     if (first.sendName) {
15020       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
15021       SendToProgram(buf, &first);
15022     }
15023     if (first.sendTime) {
15024       if (first.useColors) {
15025         SendToProgram("black\n", &first); /*gnu kludge*/
15026       }
15027       SendTimeRemaining(&first, TRUE);
15028     }
15029     if (first.useColors) {
15030       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
15031     }
15032     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15033     SetMachineThinkingEnables();
15034     first.maybeThinking = TRUE;
15035     StartClocks();
15036     firstMove = FALSE;
15037
15038     if (appData.autoFlipView && !flipView) {
15039       flipView = !flipView;
15040       DrawPosition(FALSE, NULL);
15041       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15042     }
15043
15044     if(bookHit) { // [HGM] book: simulate book reply
15045         static char bookMove[MSG_SIZ]; // a bit generous?
15046
15047         programStats.nodes = programStats.depth = programStats.time =
15048         programStats.score = programStats.got_only_move = 0;
15049         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15050
15051         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15052         strcat(bookMove, bookHit);
15053         savedMessage = bookMove; // args for deferred call
15054         savedState = &first;
15055         ScheduleDelayedEvent(DeferredBookMove, 1);
15056     }
15057 }
15058
15059 void
15060 MachineBlackEvent ()
15061 {
15062   char buf[MSG_SIZ];
15063   char *bookHit = NULL;
15064
15065     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
15066         return;
15067
15068
15069     if (gameMode == PlayFromGameFile ||
15070         gameMode == TwoMachinesPlay  ||
15071         gameMode == Training         ||
15072         gameMode == AnalyzeMode      ||
15073         gameMode == EndOfGame)
15074         EditGameEvent();
15075
15076     if (gameMode == EditPosition)
15077         EditPositionDone(TRUE);
15078
15079     if (WhiteOnMove(currentMove)) {
15080         DisplayError(_("It is not Black's turn"), 0);
15081         return;
15082     }
15083
15084     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15085       ExitAnalyzeMode();
15086
15087     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15088         gameMode == AnalyzeFile)
15089         TruncateGame();
15090
15091     ResurrectChessProgram();    /* in case it isn't running */
15092     gameMode = MachinePlaysBlack;
15093     pausing = FALSE;
15094     ModeHighlight();
15095     SetGameInfo();
15096     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15097     DisplayTitle(buf);
15098     if (first.sendName) {
15099       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
15100       SendToProgram(buf, &first);
15101     }
15102     if (first.sendTime) {
15103       if (first.useColors) {
15104         SendToProgram("white\n", &first); /*gnu kludge*/
15105       }
15106       SendTimeRemaining(&first, FALSE);
15107     }
15108     if (first.useColors) {
15109       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
15110     }
15111     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15112     SetMachineThinkingEnables();
15113     first.maybeThinking = TRUE;
15114     StartClocks();
15115
15116     if (appData.autoFlipView && flipView) {
15117       flipView = !flipView;
15118       DrawPosition(FALSE, NULL);
15119       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15120     }
15121     if(bookHit) { // [HGM] book: simulate book reply
15122         static char bookMove[MSG_SIZ]; // a bit generous?
15123
15124         programStats.nodes = programStats.depth = programStats.time =
15125         programStats.score = programStats.got_only_move = 0;
15126         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15127
15128         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15129         strcat(bookMove, bookHit);
15130         savedMessage = bookMove; // args for deferred call
15131         savedState = &first;
15132         ScheduleDelayedEvent(DeferredBookMove, 1);
15133     }
15134 }
15135
15136
15137 void
15138 DisplayTwoMachinesTitle ()
15139 {
15140     char buf[MSG_SIZ];
15141     if (appData.matchGames > 0) {
15142         if(appData.tourneyFile[0]) {
15143           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15144                    gameInfo.white, _("vs."), gameInfo.black,
15145                    nextGame+1, appData.matchGames+1,
15146                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15147         } else
15148         if (first.twoMachinesColor[0] == 'w') {
15149           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15150                    gameInfo.white, _("vs."),  gameInfo.black,
15151                    first.matchWins, second.matchWins,
15152                    matchGame - 1 - (first.matchWins + second.matchWins));
15153         } else {
15154           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15155                    gameInfo.white, _("vs."), gameInfo.black,
15156                    second.matchWins, first.matchWins,
15157                    matchGame - 1 - (first.matchWins + second.matchWins));
15158         }
15159     } else {
15160       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15161     }
15162     DisplayTitle(buf);
15163 }
15164
15165 void
15166 SettingsMenuIfReady ()
15167 {
15168   if (second.lastPing != second.lastPong) {
15169     DisplayMessage("", _("Waiting for second chess program"));
15170     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15171     return;
15172   }
15173   ThawUI();
15174   DisplayMessage("", "");
15175   SettingsPopUp(&second);
15176 }
15177
15178 int
15179 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15180 {
15181     char buf[MSG_SIZ];
15182     if (cps->pr == NoProc) {
15183         StartChessProgram(cps);
15184         if (cps->protocolVersion == 1) {
15185           retry();
15186           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15187         } else {
15188           /* kludge: allow timeout for initial "feature" command */
15189           if(retry != TwoMachinesEventIfReady) FreezeUI();
15190           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15191           DisplayMessage("", buf);
15192           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15193         }
15194         return 1;
15195     }
15196     return 0;
15197 }
15198
15199 void
15200 TwoMachinesEvent P((void))
15201 {
15202     int i, move = forwardMostMove;
15203     char buf[MSG_SIZ];
15204     ChessProgramState *onmove;
15205     char *bookHit = NULL;
15206     static int stalling = 0;
15207     TimeMark now;
15208     long wait;
15209
15210     if (appData.noChessProgram) return;
15211
15212     switch (gameMode) {
15213       case TwoMachinesPlay:
15214         return;
15215       case MachinePlaysWhite:
15216       case MachinePlaysBlack:
15217         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15218             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15219             return;
15220         }
15221         /* fall through */
15222       case BeginningOfGame:
15223       case PlayFromGameFile:
15224       case EndOfGame:
15225         EditGameEvent();
15226         if (gameMode != EditGame) return;
15227         break;
15228       case EditPosition:
15229         EditPositionDone(TRUE);
15230         break;
15231       case AnalyzeMode:
15232       case AnalyzeFile:
15233         ExitAnalyzeMode();
15234         break;
15235       case EditGame:
15236       default:
15237         break;
15238     }
15239
15240 //    forwardMostMove = currentMove;
15241     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15242     startingEngine = TRUE;
15243
15244     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15245
15246     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15247     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15248       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15249       return;
15250     }
15251   if(!appData.epd) {
15252     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15253
15254     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15255                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15256         startingEngine = matchMode = FALSE;
15257         DisplayError("second engine does not play this", 0);
15258         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15259         EditGameEvent(); // switch back to EditGame mode
15260         return;
15261     }
15262
15263     if(!stalling) {
15264       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15265       SendToProgram("force\n", &second);
15266       stalling = 1;
15267       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15268       return;
15269     }
15270   }
15271     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15272     if(appData.matchPause>10000 || appData.matchPause<10)
15273                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15274     wait = SubtractTimeMarks(&now, &pauseStart);
15275     if(wait < appData.matchPause) {
15276         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15277         return;
15278     }
15279     // we are now committed to starting the game
15280     stalling = 0;
15281     DisplayMessage("", "");
15282   if(!appData.epd) {
15283     if (startedFromSetupPosition) {
15284         SendBoard(&second, backwardMostMove);
15285     if (appData.debugMode) {
15286         fprintf(debugFP, "Two Machines\n");
15287     }
15288     }
15289     for (i = backwardMostMove; i < forwardMostMove; i++) {
15290         SendMoveToProgram(i, &second);
15291     }
15292   }
15293
15294     gameMode = TwoMachinesPlay;
15295     pausing = startingEngine = FALSE;
15296     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15297     SetGameInfo();
15298     DisplayTwoMachinesTitle();
15299     firstMove = TRUE;
15300     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15301         onmove = &first;
15302     } else {
15303         onmove = &second;
15304     }
15305     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15306     SendToProgram(first.computerString, &first);
15307     if (first.sendName) {
15308       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15309       SendToProgram(buf, &first);
15310     }
15311   if(!appData.epd) {
15312     SendToProgram(second.computerString, &second);
15313     if (second.sendName) {
15314       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15315       SendToProgram(buf, &second);
15316     }
15317   }
15318
15319     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15320         ResetClocks();
15321         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15322         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15323     }
15324     if (onmove->sendTime) {
15325       if (onmove->useColors) {
15326         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15327       }
15328       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15329     }
15330     if (onmove->useColors) {
15331       SendToProgram(onmove->twoMachinesColor, onmove);
15332     }
15333     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15334 //    SendToProgram("go\n", onmove);
15335     onmove->maybeThinking = TRUE;
15336     SetMachineThinkingEnables();
15337
15338     StartClocks();
15339
15340     if(bookHit) { // [HGM] book: simulate book reply
15341         static char bookMove[MSG_SIZ]; // a bit generous?
15342
15343         programStats.nodes = programStats.depth = programStats.time =
15344         programStats.score = programStats.got_only_move = 0;
15345         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15346
15347         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15348         strcat(bookMove, bookHit);
15349         savedMessage = bookMove; // args for deferred call
15350         savedState = onmove;
15351         ScheduleDelayedEvent(DeferredBookMove, 1);
15352     }
15353 }
15354
15355 void
15356 TrainingEvent ()
15357 {
15358     if (gameMode == Training) {
15359       SetTrainingModeOff();
15360       gameMode = PlayFromGameFile;
15361       DisplayMessage("", _("Training mode off"));
15362     } else {
15363       gameMode = Training;
15364       animateTraining = appData.animate;
15365
15366       /* make sure we are not already at the end of the game */
15367       if (currentMove < forwardMostMove) {
15368         SetTrainingModeOn();
15369         DisplayMessage("", _("Training mode on"));
15370       } else {
15371         gameMode = PlayFromGameFile;
15372         DisplayError(_("Already at end of game"), 0);
15373       }
15374     }
15375     ModeHighlight();
15376 }
15377
15378 void
15379 IcsClientEvent ()
15380 {
15381     if (!appData.icsActive) return;
15382     switch (gameMode) {
15383       case IcsPlayingWhite:
15384       case IcsPlayingBlack:
15385       case IcsObserving:
15386       case IcsIdle:
15387       case BeginningOfGame:
15388       case IcsExamining:
15389         return;
15390
15391       case EditGame:
15392         break;
15393
15394       case EditPosition:
15395         EditPositionDone(TRUE);
15396         break;
15397
15398       case AnalyzeMode:
15399       case AnalyzeFile:
15400         ExitAnalyzeMode();
15401         break;
15402
15403       default:
15404         EditGameEvent();
15405         break;
15406     }
15407
15408     gameMode = IcsIdle;
15409     ModeHighlight();
15410     return;
15411 }
15412
15413 void
15414 EditGameEvent ()
15415 {
15416     int i;
15417
15418     switch (gameMode) {
15419       case Training:
15420         SetTrainingModeOff();
15421         break;
15422       case MachinePlaysWhite:
15423       case MachinePlaysBlack:
15424       case BeginningOfGame:
15425         SendToProgram("force\n", &first);
15426         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15427             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15428                 char buf[MSG_SIZ];
15429                 abortEngineThink = TRUE;
15430                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15431                 SendToProgram(buf, &first);
15432                 DisplayMessage("Aborting engine think", "");
15433                 FreezeUI();
15434             }
15435         }
15436         SetUserThinkingEnables();
15437         break;
15438       case PlayFromGameFile:
15439         (void) StopLoadGameTimer();
15440         if (gameFileFP != NULL) {
15441             gameFileFP = NULL;
15442         }
15443         break;
15444       case EditPosition:
15445         EditPositionDone(TRUE);
15446         break;
15447       case AnalyzeMode:
15448       case AnalyzeFile:
15449         ExitAnalyzeMode();
15450         SendToProgram("force\n", &first);
15451         break;
15452       case TwoMachinesPlay:
15453         GameEnds(EndOfFile, NULL, GE_PLAYER);
15454         ResurrectChessProgram();
15455         SetUserThinkingEnables();
15456         break;
15457       case EndOfGame:
15458         ResurrectChessProgram();
15459         break;
15460       case IcsPlayingBlack:
15461       case IcsPlayingWhite:
15462         DisplayError(_("Warning: You are still playing a game"), 0);
15463         break;
15464       case IcsObserving:
15465         DisplayError(_("Warning: You are still observing a game"), 0);
15466         break;
15467       case IcsExamining:
15468         DisplayError(_("Warning: You are still examining a game"), 0);
15469         break;
15470       case IcsIdle:
15471         break;
15472       case EditGame:
15473       default:
15474         return;
15475     }
15476
15477     pausing = FALSE;
15478     StopClocks();
15479     first.offeredDraw = second.offeredDraw = 0;
15480
15481     if (gameMode == PlayFromGameFile) {
15482         whiteTimeRemaining = timeRemaining[0][currentMove];
15483         blackTimeRemaining = timeRemaining[1][currentMove];
15484         DisplayTitle("");
15485     }
15486
15487     if (gameMode == MachinePlaysWhite ||
15488         gameMode == MachinePlaysBlack ||
15489         gameMode == TwoMachinesPlay ||
15490         gameMode == EndOfGame) {
15491         i = forwardMostMove;
15492         while (i > currentMove) {
15493             SendToProgram("undo\n", &first);
15494             i--;
15495         }
15496         if(!adjustedClock) {
15497         whiteTimeRemaining = timeRemaining[0][currentMove];
15498         blackTimeRemaining = timeRemaining[1][currentMove];
15499         DisplayBothClocks();
15500         }
15501         if (whiteFlag || blackFlag) {
15502             whiteFlag = blackFlag = 0;
15503         }
15504         DisplayTitle("");
15505     }
15506
15507     gameMode = EditGame;
15508     ModeHighlight();
15509     SetGameInfo();
15510 }
15511
15512 void
15513 EditPositionEvent ()
15514 {
15515     int i;
15516     if (gameMode == EditPosition) {
15517         EditGameEvent();
15518         return;
15519     }
15520
15521     EditGameEvent();
15522     if (gameMode != EditGame) return;
15523
15524     gameMode = EditPosition;
15525     ModeHighlight();
15526     SetGameInfo();
15527     CopyBoard(rightsBoard, nullBoard);
15528     if (currentMove > 0)
15529       CopyBoard(boards[0], boards[currentMove]);
15530     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15531       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15532
15533     blackPlaysFirst = !WhiteOnMove(currentMove);
15534     ResetClocks();
15535     currentMove = forwardMostMove = backwardMostMove = 0;
15536     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15537     DisplayMove(-1);
15538     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15539 }
15540
15541 void
15542 ExitAnalyzeMode ()
15543 {
15544     /* [DM] icsEngineAnalyze - possible call from other functions */
15545     if (appData.icsEngineAnalyze) {
15546         appData.icsEngineAnalyze = FALSE;
15547
15548         DisplayMessage("",_("Close ICS engine analyze..."));
15549     }
15550     if (first.analysisSupport && first.analyzing) {
15551       SendToBoth("exit\n");
15552       first.analyzing = second.analyzing = FALSE;
15553     }
15554     thinkOutput[0] = NULLCHAR;
15555 }
15556
15557 void
15558 EditPositionDone (Boolean fakeRights)
15559 {
15560     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15561
15562     startedFromSetupPosition = TRUE;
15563     InitChessProgram(&first, FALSE);
15564     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15565       int r, f;
15566       boards[0][EP_STATUS] = EP_NONE;
15567       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15568       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15569         if(rightsBoard[r][f]) {
15570           ChessSquare p = boards[0][r][f];
15571           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15572           else if(p == king) boards[0][CASTLING][2] = f;
15573           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15574           else rightsBoard[r][f] = 2; // mark for second pass
15575         }
15576       }
15577       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15578         if(rightsBoard[r][f] == 2) {
15579           ChessSquare p = boards[0][r][f];
15580           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15581           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15582         }
15583       }
15584     }
15585     SendToProgram("force\n", &first);
15586     if (blackPlaysFirst) {
15587         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15588         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15589         currentMove = forwardMostMove = backwardMostMove = 1;
15590         CopyBoard(boards[1], boards[0]);
15591     } else {
15592         currentMove = forwardMostMove = backwardMostMove = 0;
15593     }
15594     SendBoard(&first, forwardMostMove);
15595     if (appData.debugMode) {
15596         fprintf(debugFP, "EditPosDone\n");
15597     }
15598     DisplayTitle("");
15599     DisplayMessage("", "");
15600     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15601     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15602     gameMode = EditGame;
15603     ModeHighlight();
15604     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15605     ClearHighlights(); /* [AS] */
15606 }
15607
15608 /* Pause for `ms' milliseconds */
15609 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15610 void
15611 TimeDelay (long ms)
15612 {
15613     TimeMark m1, m2;
15614
15615     GetTimeMark(&m1);
15616     do {
15617         GetTimeMark(&m2);
15618     } while (SubtractTimeMarks(&m2, &m1) < ms);
15619 }
15620
15621 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15622 void
15623 SendMultiLineToICS (char *buf)
15624 {
15625     char temp[MSG_SIZ+1], *p;
15626     int len;
15627
15628     len = strlen(buf);
15629     if (len > MSG_SIZ)
15630       len = MSG_SIZ;
15631
15632     strncpy(temp, buf, len);
15633     temp[len] = 0;
15634
15635     p = temp;
15636     while (*p) {
15637         if (*p == '\n' || *p == '\r')
15638           *p = ' ';
15639         ++p;
15640     }
15641
15642     strcat(temp, "\n");
15643     SendToICS(temp);
15644     SendToPlayer(temp, strlen(temp));
15645 }
15646
15647 void
15648 SetWhiteToPlayEvent ()
15649 {
15650     if (gameMode == EditPosition) {
15651         blackPlaysFirst = FALSE;
15652         DisplayBothClocks();    /* works because currentMove is 0 */
15653     } else if (gameMode == IcsExamining) {
15654         SendToICS(ics_prefix);
15655         SendToICS("tomove white\n");
15656     }
15657 }
15658
15659 void
15660 SetBlackToPlayEvent ()
15661 {
15662     if (gameMode == EditPosition) {
15663         blackPlaysFirst = TRUE;
15664         currentMove = 1;        /* kludge */
15665         DisplayBothClocks();
15666         currentMove = 0;
15667     } else if (gameMode == IcsExamining) {
15668         SendToICS(ics_prefix);
15669         SendToICS("tomove black\n");
15670     }
15671 }
15672
15673 void
15674 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15675 {
15676     char buf[MSG_SIZ];
15677     ChessSquare piece = boards[0][y][x];
15678     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15679     static int lastVariant;
15680     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15681
15682     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15683
15684     switch (selection) {
15685       case ClearBoard:
15686         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15687         MarkTargetSquares(1);
15688         CopyBoard(currentBoard, boards[0]);
15689         CopyBoard(menuBoard, initialPosition);
15690         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15691             SendToICS(ics_prefix);
15692             SendToICS("bsetup clear\n");
15693         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15694             SendToICS(ics_prefix);
15695             SendToICS("clearboard\n");
15696         } else {
15697             int nonEmpty = 0;
15698             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15699                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15700                 for (y = 0; y < BOARD_HEIGHT; y++) {
15701                     if (gameMode == IcsExamining) {
15702                         if (boards[currentMove][y][x] != EmptySquare) {
15703                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15704                                     AAA + x, ONE + y);
15705                             SendToICS(buf);
15706                         }
15707                     } else if(boards[0][y][x] != DarkSquare) {
15708                         if(boards[0][y][x] != p) nonEmpty++;
15709                         boards[0][y][x] = p;
15710                     }
15711                 }
15712             }
15713             CopyBoard(rightsBoard, nullBoard);
15714             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15715                 int r, i;
15716                 for(r = 0; r < BOARD_HEIGHT; r++) {
15717                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15718                     ChessSquare p = menuBoard[r][x];
15719                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15720                   }
15721                 }
15722                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15723                 DisplayMessage("Clicking clock again restores position", "");
15724                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15725                 if(!nonEmpty) { // asked to clear an empty board
15726                     CopyBoard(boards[0], menuBoard);
15727                 } else
15728                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15729                     CopyBoard(boards[0], initialPosition);
15730                 } else
15731                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15732                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15733                     CopyBoard(boards[0], erasedBoard);
15734                 } else
15735                     CopyBoard(erasedBoard, currentBoard);
15736
15737                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15738                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15739             }
15740         }
15741         if (gameMode == EditPosition) {
15742             DrawPosition(FALSE, boards[0]);
15743         }
15744         break;
15745
15746       case WhitePlay:
15747         SetWhiteToPlayEvent();
15748         break;
15749
15750       case BlackPlay:
15751         SetBlackToPlayEvent();
15752         break;
15753
15754       case EmptySquare:
15755         if (gameMode == IcsExamining) {
15756             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15757             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15758             SendToICS(buf);
15759         } else {
15760             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15761                 if(x == BOARD_LEFT-2) {
15762                     if(y < handSize-1-gameInfo.holdingsSize) break;
15763                     boards[0][y][1] = 0;
15764                 } else
15765                 if(x == BOARD_RGHT+1) {
15766                     if(y >= gameInfo.holdingsSize) break;
15767                     boards[0][y][BOARD_WIDTH-2] = 0;
15768                 } else break;
15769             }
15770             boards[0][y][x] = EmptySquare;
15771             DrawPosition(FALSE, boards[0]);
15772         }
15773         break;
15774
15775       case PromotePiece:
15776         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15777            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15778             selection = (ChessSquare) (PROMOTED(piece));
15779         } else if(piece == EmptySquare) selection = WhiteSilver;
15780         else selection = (ChessSquare)((int)piece - 1);
15781         goto defaultlabel;
15782
15783       case DemotePiece:
15784         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15785            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15786             selection = (ChessSquare) (DEMOTED(piece));
15787         } else if(piece == EmptySquare) selection = BlackSilver;
15788         else selection = (ChessSquare)((int)piece + 1);
15789         goto defaultlabel;
15790
15791       case WhiteQueen:
15792       case BlackQueen:
15793         if(gameInfo.variant == VariantShatranj ||
15794            gameInfo.variant == VariantXiangqi  ||
15795            gameInfo.variant == VariantCourier  ||
15796            gameInfo.variant == VariantASEAN    ||
15797            gameInfo.variant == VariantMakruk     )
15798             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15799         goto defaultlabel;
15800
15801       case WhiteRook:
15802         baseRank = 0;
15803       case BlackRook:
15804         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15805         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15806         goto defaultlabel;
15807
15808       case WhiteKing:
15809         baseRank = 0;
15810       case BlackKing:
15811         if(gameInfo.variant == VariantXiangqi)
15812             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15813         if(gameInfo.variant == VariantKnightmate)
15814             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15815         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15816       default:
15817         defaultlabel:
15818         if (gameMode == IcsExamining) {
15819             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15820             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15821                      PieceToChar(selection), AAA + x, ONE + y);
15822             SendToICS(buf);
15823         } else {
15824             rightsBoard[y][x] = hasRights;
15825             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15826                 int n;
15827                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15828                     n = PieceToNumber(selection - BlackPawn);
15829                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15830                     boards[0][handSize-1-n][0] = selection;
15831                     boards[0][handSize-1-n][1]++;
15832                 } else
15833                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15834                     n = PieceToNumber(selection);
15835                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15836                     boards[0][n][BOARD_WIDTH-1] = selection;
15837                     boards[0][n][BOARD_WIDTH-2]++;
15838                 }
15839             } else
15840             boards[0][y][x] = selection;
15841             DrawPosition(TRUE, boards[0]);
15842             ClearHighlights();
15843             fromX = fromY = -1;
15844         }
15845         break;
15846     }
15847 }
15848
15849
15850 void
15851 DropMenuEvent (ChessSquare selection, int x, int y)
15852 {
15853     ChessMove moveType;
15854
15855     switch (gameMode) {
15856       case IcsPlayingWhite:
15857       case MachinePlaysBlack:
15858         if (!WhiteOnMove(currentMove)) {
15859             DisplayMoveError(_("It is Black's turn"));
15860             return;
15861         }
15862         moveType = WhiteDrop;
15863         break;
15864       case IcsPlayingBlack:
15865       case MachinePlaysWhite:
15866         if (WhiteOnMove(currentMove)) {
15867             DisplayMoveError(_("It is White's turn"));
15868             return;
15869         }
15870         moveType = BlackDrop;
15871         break;
15872       case EditGame:
15873         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15874         break;
15875       default:
15876         return;
15877     }
15878
15879     if (moveType == BlackDrop && selection < BlackPawn) {
15880       selection = (ChessSquare) ((int) selection
15881                                  + (int) BlackPawn - (int) WhitePawn);
15882     }
15883     if (boards[currentMove][y][x] != EmptySquare) {
15884         DisplayMoveError(_("That square is occupied"));
15885         return;
15886     }
15887
15888     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15889 }
15890
15891 void
15892 AcceptEvent ()
15893 {
15894     /* Accept a pending offer of any kind from opponent */
15895
15896     if (appData.icsActive) {
15897         SendToICS(ics_prefix);
15898         SendToICS("accept\n");
15899     } else if (cmailMsgLoaded) {
15900         if (currentMove == cmailOldMove &&
15901             commentList[cmailOldMove] != NULL &&
15902             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15903                    "Black offers a draw" : "White offers a draw")) {
15904             TruncateGame();
15905             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15906             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15907         } else {
15908             DisplayError(_("There is no pending offer on this move"), 0);
15909             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15910         }
15911     } else {
15912         /* Not used for offers from chess program */
15913     }
15914 }
15915
15916 void
15917 DeclineEvent ()
15918 {
15919     /* Decline a pending offer of any kind from opponent */
15920
15921     if (appData.icsActive) {
15922         SendToICS(ics_prefix);
15923         SendToICS("decline\n");
15924     } else if (cmailMsgLoaded) {
15925         if (currentMove == cmailOldMove &&
15926             commentList[cmailOldMove] != NULL &&
15927             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15928                    "Black offers a draw" : "White offers a draw")) {
15929 #ifdef NOTDEF
15930             AppendComment(cmailOldMove, "Draw declined", TRUE);
15931             DisplayComment(cmailOldMove - 1, "Draw declined");
15932 #endif /*NOTDEF*/
15933         } else {
15934             DisplayError(_("There is no pending offer on this move"), 0);
15935         }
15936     } else {
15937         /* Not used for offers from chess program */
15938     }
15939 }
15940
15941 void
15942 RematchEvent ()
15943 {
15944     /* Issue ICS rematch command */
15945     if (appData.icsActive) {
15946         SendToICS(ics_prefix);
15947         SendToICS("rematch\n");
15948     }
15949 }
15950
15951 void
15952 CallFlagEvent ()
15953 {
15954     /* Call your opponent's flag (claim a win on time) */
15955     if (appData.icsActive) {
15956         SendToICS(ics_prefix);
15957         SendToICS("flag\n");
15958     } else {
15959         switch (gameMode) {
15960           default:
15961             return;
15962           case MachinePlaysWhite:
15963             if (whiteFlag) {
15964                 if (blackFlag)
15965                   GameEnds(GameIsDrawn, "Both players ran out of time",
15966                            GE_PLAYER);
15967                 else
15968                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15969             } else {
15970                 DisplayError(_("Your opponent is not out of time"), 0);
15971             }
15972             break;
15973           case MachinePlaysBlack:
15974             if (blackFlag) {
15975                 if (whiteFlag)
15976                   GameEnds(GameIsDrawn, "Both players ran out of time",
15977                            GE_PLAYER);
15978                 else
15979                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15980             } else {
15981                 DisplayError(_("Your opponent is not out of time"), 0);
15982             }
15983             break;
15984         }
15985     }
15986 }
15987
15988 void
15989 ClockClick (int which)
15990 {       // [HGM] code moved to back-end from winboard.c
15991         if(which) { // black clock
15992           if (gameMode == EditPosition || gameMode == IcsExamining) {
15993             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15994             SetBlackToPlayEvent();
15995           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15996                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15997           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15998           } else if (shiftKey) {
15999             AdjustClock(which, -1);
16000           } else if (gameMode == IcsPlayingWhite ||
16001                      gameMode == MachinePlaysBlack) {
16002             CallFlagEvent();
16003           }
16004         } else { // white clock
16005           if (gameMode == EditPosition || gameMode == IcsExamining) {
16006             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
16007             SetWhiteToPlayEvent();
16008           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
16009                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
16010           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
16011           } else if (shiftKey) {
16012             AdjustClock(which, -1);
16013           } else if (gameMode == IcsPlayingBlack ||
16014                    gameMode == MachinePlaysWhite) {
16015             CallFlagEvent();
16016           }
16017         }
16018 }
16019
16020 void
16021 DrawEvent ()
16022 {
16023     /* Offer draw or accept pending draw offer from opponent */
16024
16025     if (appData.icsActive) {
16026         /* Note: tournament rules require draw offers to be
16027            made after you make your move but before you punch
16028            your clock.  Currently ICS doesn't let you do that;
16029            instead, you immediately punch your clock after making
16030            a move, but you can offer a draw at any time. */
16031
16032         SendToICS(ics_prefix);
16033         SendToICS("draw\n");
16034         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
16035     } else if (cmailMsgLoaded) {
16036         if (currentMove == cmailOldMove &&
16037             commentList[cmailOldMove] != NULL &&
16038             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
16039                    "Black offers a draw" : "White offers a draw")) {
16040             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
16041             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
16042         } else if (currentMove == cmailOldMove + 1) {
16043             char *offer = WhiteOnMove(cmailOldMove) ?
16044               "White offers a draw" : "Black offers a draw";
16045             AppendComment(currentMove, offer, TRUE);
16046             DisplayComment(currentMove - 1, offer);
16047             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
16048         } else {
16049             DisplayError(_("You must make your move before offering a draw"), 0);
16050             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
16051         }
16052     } else if (first.offeredDraw) {
16053         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
16054     } else {
16055         if (first.sendDrawOffers) {
16056             SendToProgram("draw\n", &first);
16057             userOfferedDraw = TRUE;
16058         }
16059     }
16060 }
16061
16062 void
16063 AdjournEvent ()
16064 {
16065     /* Offer Adjourn or accept pending Adjourn offer from opponent */
16066
16067     if (appData.icsActive) {
16068         SendToICS(ics_prefix);
16069         SendToICS("adjourn\n");
16070     } else {
16071         /* Currently GNU Chess doesn't offer or accept Adjourns */
16072     }
16073 }
16074
16075
16076 void
16077 AbortEvent ()
16078 {
16079     /* Offer Abort or accept pending Abort offer from opponent */
16080
16081     if (appData.icsActive) {
16082         SendToICS(ics_prefix);
16083         SendToICS("abort\n");
16084     } else {
16085         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
16086     }
16087 }
16088
16089 void
16090 ResignEvent ()
16091 {
16092     /* Resign.  You can do this even if it's not your turn. */
16093
16094     if (appData.icsActive) {
16095         SendToICS(ics_prefix);
16096         SendToICS("resign\n");
16097     } else {
16098         switch (gameMode) {
16099           case MachinePlaysWhite:
16100             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16101             break;
16102           case MachinePlaysBlack:
16103             GameEnds(BlackWins, "White resigns", GE_PLAYER);
16104             break;
16105           case EditGame:
16106             if (cmailMsgLoaded) {
16107                 TruncateGame();
16108                 if (WhiteOnMove(cmailOldMove)) {
16109                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
16110                 } else {
16111                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16112                 }
16113                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16114             }
16115             break;
16116           default:
16117             break;
16118         }
16119     }
16120 }
16121
16122
16123 void
16124 StopObservingEvent ()
16125 {
16126     /* Stop observing current games */
16127     SendToICS(ics_prefix);
16128     SendToICS("unobserve\n");
16129 }
16130
16131 void
16132 StopExaminingEvent ()
16133 {
16134     /* Stop observing current game */
16135     SendToICS(ics_prefix);
16136     SendToICS("unexamine\n");
16137 }
16138
16139 void
16140 ForwardInner (int target)
16141 {
16142     int limit; int oldSeekGraphUp = seekGraphUp;
16143
16144     if (appData.debugMode)
16145         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16146                 target, currentMove, forwardMostMove);
16147
16148     if (gameMode == EditPosition)
16149       return;
16150
16151     seekGraphUp = FALSE;
16152     MarkTargetSquares(1);
16153     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16154
16155     if (gameMode == PlayFromGameFile && !pausing)
16156       PauseEvent();
16157
16158     if (gameMode == IcsExamining && pausing)
16159       limit = pauseExamForwardMostMove;
16160     else
16161       limit = forwardMostMove;
16162
16163     if (target > limit) target = limit;
16164
16165     if (target > 0 && moveList[target - 1][0]) {
16166         int fromX, fromY, toX, toY;
16167         toX = moveList[target - 1][2] - AAA;
16168         toY = moveList[target - 1][3] - ONE;
16169         if (moveList[target - 1][1] == '@') {
16170             if (appData.highlightLastMove) {
16171                 SetHighlights(-1, -1, toX, toY);
16172             }
16173         } else {
16174             fromX = moveList[target - 1][0] - AAA;
16175             fromY = moveList[target - 1][1] - ONE;
16176             if (target == currentMove + 1) {
16177                 if(moveList[target - 1][4] == ';') { // multi-leg
16178                     killX = moveList[target - 1][5] - AAA;
16179                     killY = moveList[target - 1][6] - ONE;
16180                 }
16181                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16182                 killX = killY = -1;
16183             }
16184             if (appData.highlightLastMove) {
16185                 SetHighlights(fromX, fromY, toX, toY);
16186             }
16187         }
16188     }
16189     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16190         gameMode == Training || gameMode == PlayFromGameFile ||
16191         gameMode == AnalyzeFile) {
16192         while (currentMove < target) {
16193             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16194             SendMoveToProgram(currentMove++, &first);
16195         }
16196     } else {
16197         currentMove = target;
16198     }
16199
16200     if (gameMode == EditGame || gameMode == EndOfGame) {
16201         whiteTimeRemaining = timeRemaining[0][currentMove];
16202         blackTimeRemaining = timeRemaining[1][currentMove];
16203     }
16204     DisplayBothClocks();
16205     DisplayMove(currentMove - 1);
16206     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16207     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16208     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16209         DisplayComment(currentMove - 1, commentList[currentMove]);
16210     }
16211     ClearMap(); // [HGM] exclude: invalidate map
16212 }
16213
16214
16215 void
16216 ForwardEvent ()
16217 {
16218     if (gameMode == IcsExamining && !pausing) {
16219         SendToICS(ics_prefix);
16220         SendToICS("forward\n");
16221     } else {
16222         ForwardInner(currentMove + 1);
16223     }
16224 }
16225
16226 void
16227 ToEndEvent ()
16228 {
16229     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16230         /* to optimze, we temporarily turn off analysis mode while we feed
16231          * the remaining moves to the engine. Otherwise we get analysis output
16232          * after each move.
16233          */
16234         if (first.analysisSupport) {
16235           SendToProgram("exit\nforce\n", &first);
16236           first.analyzing = FALSE;
16237         }
16238     }
16239
16240     if (gameMode == IcsExamining && !pausing) {
16241         SendToICS(ics_prefix);
16242         SendToICS("forward 999999\n");
16243     } else {
16244         ForwardInner(forwardMostMove);
16245     }
16246
16247     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16248         /* we have fed all the moves, so reactivate analysis mode */
16249         SendToProgram("analyze\n", &first);
16250         first.analyzing = TRUE;
16251         /*first.maybeThinking = TRUE;*/
16252         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16253     }
16254 }
16255
16256 void
16257 BackwardInner (int target)
16258 {
16259     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16260
16261     if (appData.debugMode)
16262         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16263                 target, currentMove, forwardMostMove);
16264
16265     if (gameMode == EditPosition) return;
16266     seekGraphUp = FALSE;
16267     MarkTargetSquares(1);
16268     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16269     if (currentMove <= backwardMostMove) {
16270         ClearHighlights();
16271         DrawPosition(full_redraw, boards[currentMove]);
16272         return;
16273     }
16274     if (gameMode == PlayFromGameFile && !pausing)
16275       PauseEvent();
16276
16277     if (moveList[target][0]) {
16278         int fromX, fromY, toX, toY;
16279         toX = moveList[target][2] - AAA;
16280         toY = moveList[target][3] - ONE;
16281         if (moveList[target][1] == '@') {
16282             if (appData.highlightLastMove) {
16283                 SetHighlights(-1, -1, toX, toY);
16284             }
16285         } else {
16286             fromX = moveList[target][0] - AAA;
16287             fromY = moveList[target][1] - ONE;
16288             if (target == currentMove - 1) {
16289                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16290             }
16291             if (appData.highlightLastMove) {
16292                 SetHighlights(fromX, fromY, toX, toY);
16293             }
16294         }
16295     }
16296     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16297         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16298         while (currentMove > target) {
16299             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16300                 // null move cannot be undone. Reload program with move history before it.
16301                 int i;
16302                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16303                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16304                 }
16305                 SendBoard(&first, i);
16306               if(second.analyzing) SendBoard(&second, i);
16307                 for(currentMove=i; currentMove<target; currentMove++) {
16308                     SendMoveToProgram(currentMove, &first);
16309                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16310                 }
16311                 break;
16312             }
16313             SendToBoth("undo\n");
16314             currentMove--;
16315         }
16316     } else {
16317         currentMove = target;
16318     }
16319
16320     if (gameMode == EditGame || gameMode == EndOfGame) {
16321         whiteTimeRemaining = timeRemaining[0][currentMove];
16322         blackTimeRemaining = timeRemaining[1][currentMove];
16323     }
16324     DisplayBothClocks();
16325     DisplayMove(currentMove - 1);
16326     DrawPosition(full_redraw, boards[currentMove]);
16327     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16328     // [HGM] PV info: routine tests if comment empty
16329     DisplayComment(currentMove - 1, commentList[currentMove]);
16330     ClearMap(); // [HGM] exclude: invalidate map
16331 }
16332
16333 void
16334 BackwardEvent ()
16335 {
16336     if (gameMode == IcsExamining && !pausing) {
16337         SendToICS(ics_prefix);
16338         SendToICS("backward\n");
16339     } else {
16340         BackwardInner(currentMove - 1);
16341     }
16342 }
16343
16344 void
16345 ToStartEvent ()
16346 {
16347     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16348         /* to optimize, we temporarily turn off analysis mode while we undo
16349          * all the moves. Otherwise we get analysis output after each undo.
16350          */
16351         if (first.analysisSupport) {
16352           SendToProgram("exit\nforce\n", &first);
16353           first.analyzing = FALSE;
16354         }
16355     }
16356
16357     if (gameMode == IcsExamining && !pausing) {
16358         SendToICS(ics_prefix);
16359         SendToICS("backward 999999\n");
16360     } else {
16361         BackwardInner(backwardMostMove);
16362     }
16363
16364     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16365         /* we have fed all the moves, so reactivate analysis mode */
16366         SendToProgram("analyze\n", &first);
16367         first.analyzing = TRUE;
16368         /*first.maybeThinking = TRUE;*/
16369         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16370     }
16371 }
16372
16373 void
16374 ToNrEvent (int to)
16375 {
16376   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16377   if (to >= forwardMostMove) to = forwardMostMove;
16378   if (to <= backwardMostMove) to = backwardMostMove;
16379   if (to < currentMove) {
16380     BackwardInner(to);
16381   } else {
16382     ForwardInner(to);
16383   }
16384 }
16385
16386 void
16387 RevertEvent (Boolean annotate)
16388 {
16389     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16390         return;
16391     }
16392     if (gameMode != IcsExamining) {
16393         DisplayError(_("You are not examining a game"), 0);
16394         return;
16395     }
16396     if (pausing) {
16397         DisplayError(_("You can't revert while pausing"), 0);
16398         return;
16399     }
16400     SendToICS(ics_prefix);
16401     SendToICS("revert\n");
16402 }
16403
16404 void
16405 RetractMoveEvent ()
16406 {
16407     switch (gameMode) {
16408       case MachinePlaysWhite:
16409       case MachinePlaysBlack:
16410         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16411             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16412             return;
16413         }
16414         if (forwardMostMove < 2) return;
16415         currentMove = forwardMostMove = forwardMostMove - 2;
16416         whiteTimeRemaining = timeRemaining[0][currentMove];
16417         blackTimeRemaining = timeRemaining[1][currentMove];
16418         DisplayBothClocks();
16419         DisplayMove(currentMove - 1);
16420         ClearHighlights();/*!! could figure this out*/
16421         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16422         SendToProgram("remove\n", &first);
16423         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16424         break;
16425
16426       case BeginningOfGame:
16427       default:
16428         break;
16429
16430       case IcsPlayingWhite:
16431       case IcsPlayingBlack:
16432         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16433             SendToICS(ics_prefix);
16434             SendToICS("takeback 2\n");
16435         } else {
16436             SendToICS(ics_prefix);
16437             SendToICS("takeback 1\n");
16438         }
16439         break;
16440     }
16441 }
16442
16443 void
16444 MoveNowEvent ()
16445 {
16446     ChessProgramState *cps;
16447
16448     switch (gameMode) {
16449       case MachinePlaysWhite:
16450         if (!WhiteOnMove(forwardMostMove)) {
16451             DisplayError(_("It is your turn"), 0);
16452             return;
16453         }
16454         cps = &first;
16455         break;
16456       case MachinePlaysBlack:
16457         if (WhiteOnMove(forwardMostMove)) {
16458             DisplayError(_("It is your turn"), 0);
16459             return;
16460         }
16461         cps = &first;
16462         break;
16463       case TwoMachinesPlay:
16464         if (WhiteOnMove(forwardMostMove) ==
16465             (first.twoMachinesColor[0] == 'w')) {
16466             cps = &first;
16467         } else {
16468             cps = &second;
16469         }
16470         break;
16471       case BeginningOfGame:
16472       default:
16473         return;
16474     }
16475     SendToProgram("?\n", cps);
16476 }
16477
16478 void
16479 TruncateGameEvent ()
16480 {
16481     EditGameEvent();
16482     if (gameMode != EditGame) return;
16483     TruncateGame();
16484 }
16485
16486 void
16487 TruncateGame ()
16488 {
16489     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16490     if (forwardMostMove > currentMove) {
16491         if (gameInfo.resultDetails != NULL) {
16492             free(gameInfo.resultDetails);
16493             gameInfo.resultDetails = NULL;
16494             gameInfo.result = GameUnfinished;
16495         }
16496         forwardMostMove = currentMove;
16497         HistorySet(parseList, backwardMostMove, forwardMostMove,
16498                    currentMove-1);
16499     }
16500 }
16501
16502 void
16503 HintEvent ()
16504 {
16505     if (appData.noChessProgram) return;
16506     switch (gameMode) {
16507       case MachinePlaysWhite:
16508         if (WhiteOnMove(forwardMostMove)) {
16509             DisplayError(_("Wait until your turn."), 0);
16510             return;
16511         }
16512         break;
16513       case BeginningOfGame:
16514       case MachinePlaysBlack:
16515         if (!WhiteOnMove(forwardMostMove)) {
16516             DisplayError(_("Wait until your turn."), 0);
16517             return;
16518         }
16519         break;
16520       default:
16521         DisplayError(_("No hint available"), 0);
16522         return;
16523     }
16524     SendToProgram("hint\n", &first);
16525     hintRequested = TRUE;
16526 }
16527
16528 int
16529 SaveSelected (FILE *g, int dummy, char *dummy2)
16530 {
16531     ListGame * lg = (ListGame *) gameList.head;
16532     int nItem, cnt=0;
16533     FILE *f;
16534
16535     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16536         DisplayError(_("Game list not loaded or empty"), 0);
16537         return 0;
16538     }
16539
16540     creatingBook = TRUE; // suppresses stuff during load game
16541
16542     /* Get list size */
16543     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16544         if(lg->position >= 0) { // selected?
16545             LoadGame(f, nItem, "", TRUE);
16546             SaveGamePGN2(g); // leaves g open
16547             cnt++; DoEvents();
16548         }
16549         lg = (ListGame *) lg->node.succ;
16550     }
16551
16552     fclose(g);
16553     creatingBook = FALSE;
16554
16555     return cnt;
16556 }
16557
16558 void
16559 CreateBookEvent ()
16560 {
16561     ListGame * lg = (ListGame *) gameList.head;
16562     FILE *f, *g;
16563     int nItem;
16564     static int secondTime = FALSE;
16565
16566     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16567         DisplayError(_("Game list not loaded or empty"), 0);
16568         return;
16569     }
16570
16571     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16572         fclose(g);
16573         secondTime++;
16574         DisplayNote(_("Book file exists! Try again for overwrite."));
16575         return;
16576     }
16577
16578     creatingBook = TRUE;
16579     secondTime = FALSE;
16580
16581     /* Get list size */
16582     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16583         if(lg->position >= 0) {
16584             LoadGame(f, nItem, "", TRUE);
16585             AddGameToBook(TRUE);
16586             DoEvents();
16587         }
16588         lg = (ListGame *) lg->node.succ;
16589     }
16590
16591     creatingBook = FALSE;
16592     FlushBook();
16593 }
16594
16595 void
16596 BookEvent ()
16597 {
16598     if (appData.noChessProgram) return;
16599     switch (gameMode) {
16600       case MachinePlaysWhite:
16601         if (WhiteOnMove(forwardMostMove)) {
16602             DisplayError(_("Wait until your turn."), 0);
16603             return;
16604         }
16605         break;
16606       case BeginningOfGame:
16607       case MachinePlaysBlack:
16608         if (!WhiteOnMove(forwardMostMove)) {
16609             DisplayError(_("Wait until your turn."), 0);
16610             return;
16611         }
16612         break;
16613       case EditPosition:
16614         EditPositionDone(TRUE);
16615         break;
16616       case TwoMachinesPlay:
16617         return;
16618       default:
16619         break;
16620     }
16621     SendToProgram("bk\n", &first);
16622     bookOutput[0] = NULLCHAR;
16623     bookRequested = TRUE;
16624 }
16625
16626 void
16627 AboutGameEvent ()
16628 {
16629     char *tags = PGNTags(&gameInfo);
16630     TagsPopUp(tags, CmailMsg());
16631     free(tags);
16632 }
16633
16634 /* end button procedures */
16635
16636 void
16637 PrintPosition (FILE *fp, int move)
16638 {
16639     int i, j;
16640
16641     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16642         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16643             char c = PieceToChar(boards[move][i][j]);
16644             fputc(c == '?' ? '.' : c, fp);
16645             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16646         }
16647     }
16648     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16649       fprintf(fp, "white to play\n");
16650     else
16651       fprintf(fp, "black to play\n");
16652 }
16653
16654 void
16655 PrintOpponents (FILE *fp)
16656 {
16657     if (gameInfo.white != NULL) {
16658         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16659     } else {
16660         fprintf(fp, "\n");
16661     }
16662 }
16663
16664 /* Find last component of program's own name, using some heuristics */
16665 void
16666 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16667 {
16668     char *p, *q, c;
16669     int local = (strcmp(host, "localhost") == 0);
16670     while (!local && (p = strchr(prog, ';')) != NULL) {
16671         p++;
16672         while (*p == ' ') p++;
16673         prog = p;
16674     }
16675     if (*prog == '"' || *prog == '\'') {
16676         q = strchr(prog + 1, *prog);
16677     } else {
16678         q = strchr(prog, ' ');
16679     }
16680     if (q == NULL) q = prog + strlen(prog);
16681     p = q;
16682     while (p >= prog && *p != '/' && *p != '\\') p--;
16683     p++;
16684     if(p == prog && *p == '"') p++;
16685     c = *q; *q = 0;
16686     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16687     memcpy(buf, p, q - p);
16688     buf[q - p] = NULLCHAR;
16689     if (!local) {
16690         strcat(buf, "@");
16691         strcat(buf, host);
16692     }
16693 }
16694
16695 char *
16696 TimeControlTagValue ()
16697 {
16698     char buf[MSG_SIZ];
16699     if (!appData.clockMode) {
16700       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16701     } else if (movesPerSession > 0) {
16702       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16703     } else if (timeIncrement == 0) {
16704       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16705     } else {
16706       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16707     }
16708     return StrSave(buf);
16709 }
16710
16711 void
16712 SetGameInfo ()
16713 {
16714     /* This routine is used only for certain modes */
16715     VariantClass v = gameInfo.variant;
16716     ChessMove r = GameUnfinished;
16717     char *p = NULL;
16718
16719     if(keepInfo) return;
16720
16721     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16722         r = gameInfo.result;
16723         p = gameInfo.resultDetails;
16724         gameInfo.resultDetails = NULL;
16725     }
16726     ClearGameInfo(&gameInfo);
16727     gameInfo.variant = v;
16728
16729     switch (gameMode) {
16730       case MachinePlaysWhite:
16731         gameInfo.event = StrSave( appData.pgnEventHeader );
16732         gameInfo.site = StrSave(HostName());
16733         gameInfo.date = PGNDate();
16734         gameInfo.round = StrSave("-");
16735         gameInfo.white = StrSave(first.tidy);
16736         gameInfo.black = StrSave(UserName());
16737         gameInfo.timeControl = TimeControlTagValue();
16738         break;
16739
16740       case MachinePlaysBlack:
16741         gameInfo.event = StrSave( appData.pgnEventHeader );
16742         gameInfo.site = StrSave(HostName());
16743         gameInfo.date = PGNDate();
16744         gameInfo.round = StrSave("-");
16745         gameInfo.white = StrSave(UserName());
16746         gameInfo.black = StrSave(first.tidy);
16747         gameInfo.timeControl = TimeControlTagValue();
16748         break;
16749
16750       case TwoMachinesPlay:
16751         gameInfo.event = StrSave( appData.pgnEventHeader );
16752         gameInfo.site = StrSave(HostName());
16753         gameInfo.date = PGNDate();
16754         if (roundNr > 0) {
16755             char buf[MSG_SIZ];
16756             snprintf(buf, MSG_SIZ, "%d", roundNr);
16757             gameInfo.round = StrSave(buf);
16758         } else {
16759             gameInfo.round = StrSave("-");
16760         }
16761         if (first.twoMachinesColor[0] == 'w') {
16762             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16763             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16764         } else {
16765             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16766             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16767         }
16768         gameInfo.timeControl = TimeControlTagValue();
16769         break;
16770
16771       case EditGame:
16772         gameInfo.event = StrSave("Edited game");
16773         gameInfo.site = StrSave(HostName());
16774         gameInfo.date = PGNDate();
16775         gameInfo.round = StrSave("-");
16776         gameInfo.white = StrSave("-");
16777         gameInfo.black = StrSave("-");
16778         gameInfo.result = r;
16779         gameInfo.resultDetails = p;
16780         break;
16781
16782       case EditPosition:
16783         gameInfo.event = StrSave("Edited position");
16784         gameInfo.site = StrSave(HostName());
16785         gameInfo.date = PGNDate();
16786         gameInfo.round = StrSave("-");
16787         gameInfo.white = StrSave("-");
16788         gameInfo.black = StrSave("-");
16789         break;
16790
16791       case IcsPlayingWhite:
16792       case IcsPlayingBlack:
16793       case IcsObserving:
16794       case IcsExamining:
16795         break;
16796
16797       case PlayFromGameFile:
16798         gameInfo.event = StrSave("Game from non-PGN file");
16799         gameInfo.site = StrSave(HostName());
16800         gameInfo.date = PGNDate();
16801         gameInfo.round = StrSave("-");
16802         gameInfo.white = StrSave("?");
16803         gameInfo.black = StrSave("?");
16804         break;
16805
16806       default:
16807         break;
16808     }
16809 }
16810
16811 void
16812 ReplaceComment (int index, char *text)
16813 {
16814     int len;
16815     char *p;
16816     float score;
16817
16818     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16819        pvInfoList[index-1].depth == len &&
16820        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16821        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16822     while (*text == '\n') text++;
16823     len = strlen(text);
16824     while (len > 0 && text[len - 1] == '\n') len--;
16825
16826     if (commentList[index] != NULL)
16827       free(commentList[index]);
16828
16829     if (len == 0) {
16830         commentList[index] = NULL;
16831         return;
16832     }
16833   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16834       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16835       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16836     commentList[index] = (char *) malloc(len + 2);
16837     strncpy(commentList[index], text, len);
16838     commentList[index][len] = '\n';
16839     commentList[index][len + 1] = NULLCHAR;
16840   } else {
16841     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16842     char *p;
16843     commentList[index] = (char *) malloc(len + 7);
16844     safeStrCpy(commentList[index], "{\n", 3);
16845     safeStrCpy(commentList[index]+2, text, len+1);
16846     commentList[index][len+2] = NULLCHAR;
16847     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16848     strcat(commentList[index], "\n}\n");
16849   }
16850 }
16851
16852 void
16853 CrushCRs (char *text)
16854 {
16855   char *p = text;
16856   char *q = text;
16857   char ch;
16858
16859   do {
16860     ch = *p++;
16861     if (ch == '\r') continue;
16862     *q++ = ch;
16863   } while (ch != '\0');
16864 }
16865
16866 void
16867 AppendComment (int index, char *text, Boolean addBraces)
16868 /* addBraces  tells if we should add {} */
16869 {
16870     int oldlen, len;
16871     char *old;
16872
16873 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16874     if(addBraces == 3) addBraces = 0; else // force appending literally
16875     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16876
16877     CrushCRs(text);
16878     while (*text == '\n') text++;
16879     len = strlen(text);
16880     while (len > 0 && text[len - 1] == '\n') len--;
16881     text[len] = NULLCHAR;
16882
16883     if (len == 0) return;
16884
16885     if (commentList[index] != NULL) {
16886       Boolean addClosingBrace = addBraces;
16887         old = commentList[index];
16888         oldlen = strlen(old);
16889         while(commentList[index][oldlen-1] ==  '\n')
16890           commentList[index][--oldlen] = NULLCHAR;
16891         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16892         safeStrCpy(commentList[index], old, oldlen + len + 6);
16893         free(old);
16894         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16895         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16896           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16897           while (*text == '\n') { text++; len--; }
16898           commentList[index][--oldlen] = NULLCHAR;
16899       }
16900         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16901         else          strcat(commentList[index], "\n");
16902         strcat(commentList[index], text);
16903         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16904         else          strcat(commentList[index], "\n");
16905     } else {
16906         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16907         if(addBraces)
16908           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16909         else commentList[index][0] = NULLCHAR;
16910         strcat(commentList[index], text);
16911         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16912         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16913     }
16914 }
16915
16916 static char *
16917 FindStr (char * text, char * sub_text)
16918 {
16919     char * result = strstr( text, sub_text );
16920
16921     if( result != NULL ) {
16922         result += strlen( sub_text );
16923     }
16924
16925     return result;
16926 }
16927
16928 /* [AS] Try to extract PV info from PGN comment */
16929 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16930 char *
16931 GetInfoFromComment (int index, char * text)
16932 {
16933     char * sep = text, *p;
16934
16935     if( text != NULL && index > 0 ) {
16936         int score = 0;
16937         int depth = 0;
16938         int time = -1, sec = 0, deci;
16939         char * s_eval = FindStr( text, "[%eval " );
16940         char * s_emt = FindStr( text, "[%emt " );
16941 #if 0
16942         if( s_eval != NULL || s_emt != NULL ) {
16943 #else
16944         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16945 #endif
16946             /* New style */
16947             char delim;
16948
16949             if( s_eval != NULL ) {
16950                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16951                     return text;
16952                 }
16953
16954                 if( delim != ']' ) {
16955                     return text;
16956                 }
16957             }
16958
16959             if( s_emt != NULL ) {
16960             }
16961                 return text;
16962         }
16963         else {
16964             /* We expect something like: [+|-]nnn.nn/dd */
16965             int score_lo = 0;
16966
16967             if(*text != '{') return text; // [HGM] braces: must be normal comment
16968
16969             sep = strchr( text, '/' );
16970             if( sep == NULL || sep < (text+4) ) {
16971                 return text;
16972             }
16973
16974             p = text;
16975             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16976             if(p[1] == '(') { // comment starts with PV
16977                p = strchr(p, ')'); // locate end of PV
16978                if(p == NULL || sep < p+5) return text;
16979                // at this point we have something like "{(.*) +0.23/6 ..."
16980                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16981                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16982                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16983             }
16984             time = -1; sec = -1; deci = -1;
16985             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16986                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16987                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16988                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16989                 return text;
16990             }
16991
16992             if( score_lo < 0 || score_lo >= 100 ) {
16993                 return text;
16994             }
16995
16996             if(sec >= 0) time = 600*time + 10*sec; else
16997             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16998
16999             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
17000
17001             /* [HGM] PV time: now locate end of PV info */
17002             while( *++sep >= '0' && *sep <= '9'); // strip depth
17003             if(time >= 0)
17004             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
17005             if(sec >= 0)
17006             while( *++sep >= '0' && *sep <= '9'); // strip seconds
17007             if(deci >= 0)
17008             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
17009             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
17010         }
17011
17012         if( depth <= 0 ) {
17013             return text;
17014         }
17015
17016         if( time < 0 ) {
17017             time = -1;
17018         }
17019
17020         pvInfoList[index-1].depth = depth;
17021         pvInfoList[index-1].score = score;
17022         pvInfoList[index-1].time  = 10*time; // centi-sec
17023         if(*sep == '}') *sep = 0; else *--sep = '{';
17024         if(p != text) {
17025             while(*p++ = *sep++)
17026                                 ;
17027             sep = text;
17028         } // squeeze out space between PV and comment, and return both
17029     }
17030     return sep;
17031 }
17032
17033 void
17034 SendToProgram (char *message, ChessProgramState *cps)
17035 {
17036     int count, outCount, error;
17037     char buf[MSG_SIZ];
17038
17039     if (cps->pr == NoProc) return;
17040     Attention(cps);
17041
17042     if (appData.debugMode) {
17043         TimeMark now;
17044         GetTimeMark(&now);
17045         fprintf(debugFP, "%ld >%-6s: %s",
17046                 SubtractTimeMarks(&now, &programStartTime),
17047                 cps->which, message);
17048         if(serverFP)
17049             fprintf(serverFP, "%ld >%-6s: %s",
17050                 SubtractTimeMarks(&now, &programStartTime),
17051                 cps->which, message), fflush(serverFP);
17052     }
17053
17054     count = strlen(message);
17055     outCount = OutputToProcess(cps->pr, message, count, &error);
17056     if (outCount < count && !exiting
17057                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
17058       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
17059       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
17060         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17061             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17062                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17063                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17064                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17065             } else {
17066                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17067                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17068                 gameInfo.result = res;
17069             }
17070             gameInfo.resultDetails = StrSave(buf);
17071         }
17072         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17073         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17074     }
17075 }
17076
17077 void
17078 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
17079 {
17080     char *end_str;
17081     char buf[MSG_SIZ];
17082     ChessProgramState *cps = (ChessProgramState *)closure;
17083
17084     if (isr != cps->isr) return; /* Killed intentionally */
17085     if (count <= 0) {
17086         if (count == 0) {
17087             RemoveInputSource(cps->isr);
17088             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
17089                     _(cps->which), cps->program);
17090             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
17091             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17092                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17093                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17094                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17095                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17096                 } else {
17097                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17098                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17099                     gameInfo.result = res;
17100                 }
17101                 gameInfo.resultDetails = StrSave(buf);
17102             }
17103             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17104             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
17105         } else {
17106             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
17107                     _(cps->which), cps->program);
17108             RemoveInputSource(cps->isr);
17109
17110             /* [AS] Program is misbehaving badly... kill it */
17111             if( count == -2 ) {
17112                 DestroyChildProcess( cps->pr, 9 );
17113                 cps->pr = NoProc;
17114             }
17115
17116             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17117         }
17118         return;
17119     }
17120
17121     if ((end_str = strchr(message, '\r')) != NULL)
17122       *end_str = NULLCHAR;
17123     if ((end_str = strchr(message, '\n')) != NULL)
17124       *end_str = NULLCHAR;
17125
17126     if (appData.debugMode) {
17127         TimeMark now; int print = 1;
17128         char *quote = ""; char c; int i;
17129
17130         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17131                 char start = message[0];
17132                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17133                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17134                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17135                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17136                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17137                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17138                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17139                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17140                    sscanf(message, "hint: %c", &c)!=1 &&
17141                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17142                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17143                     print = (appData.engineComments >= 2);
17144                 }
17145                 message[0] = start; // restore original message
17146         }
17147         if(print) {
17148                 GetTimeMark(&now);
17149                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17150                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17151                         quote,
17152                         message);
17153                 if(serverFP)
17154                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17155                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17156                         quote,
17157                         message), fflush(serverFP);
17158         }
17159     }
17160
17161     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17162     if (appData.icsEngineAnalyze) {
17163         if (strstr(message, "whisper") != NULL ||
17164              strstr(message, "kibitz") != NULL ||
17165             strstr(message, "tellics") != NULL) return;
17166     }
17167
17168     HandleMachineMove(message, cps);
17169 }
17170
17171
17172 void
17173 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17174 {
17175     char buf[MSG_SIZ];
17176     int seconds;
17177
17178     if( timeControl_2 > 0 ) {
17179         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17180             tc = timeControl_2;
17181         }
17182     }
17183     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17184     inc /= cps->timeOdds;
17185     st  /= cps->timeOdds;
17186
17187     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17188
17189     if (st > 0) {
17190       /* Set exact time per move, normally using st command */
17191       if (cps->stKludge) {
17192         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17193         seconds = st % 60;
17194         if (seconds == 0) {
17195           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17196         } else {
17197           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17198         }
17199       } else {
17200         snprintf(buf, MSG_SIZ, "st %d\n", st);
17201       }
17202     } else {
17203       /* Set conventional or incremental time control, using level command */
17204       if (seconds == 0) {
17205         /* Note old gnuchess bug -- minutes:seconds used to not work.
17206            Fixed in later versions, but still avoid :seconds
17207            when seconds is 0. */
17208         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17209       } else {
17210         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17211                  seconds, inc/1000.);
17212       }
17213     }
17214     SendToProgram(buf, cps);
17215
17216     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17217     /* Orthogonally, limit search to given depth */
17218     if (sd > 0) {
17219       if (cps->sdKludge) {
17220         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17221       } else {
17222         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17223       }
17224       SendToProgram(buf, cps);
17225     }
17226
17227     if(cps->nps >= 0) { /* [HGM] nps */
17228         if(cps->supportsNPS == FALSE)
17229           cps->nps = -1; // don't use if engine explicitly says not supported!
17230         else {
17231           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17232           SendToProgram(buf, cps);
17233         }
17234     }
17235 }
17236
17237 ChessProgramState *
17238 WhitePlayer ()
17239 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17240 {
17241     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17242        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17243         return &second;
17244     return &first;
17245 }
17246
17247 void
17248 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17249 {
17250     char message[MSG_SIZ];
17251     long time, otime;
17252
17253     /* Note: this routine must be called when the clocks are stopped
17254        or when they have *just* been set or switched; otherwise
17255        it will be off by the time since the current tick started.
17256     */
17257     if (machineWhite) {
17258         time = whiteTimeRemaining / 10;
17259         otime = blackTimeRemaining / 10;
17260     } else {
17261         time = blackTimeRemaining / 10;
17262         otime = whiteTimeRemaining / 10;
17263     }
17264     /* [HGM] translate opponent's time by time-odds factor */
17265     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17266
17267     if (time <= 0) time = 1;
17268     if (otime <= 0) otime = 1;
17269
17270     snprintf(message, MSG_SIZ, "time %ld\n", time);
17271     SendToProgram(message, cps);
17272
17273     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17274     SendToProgram(message, cps);
17275 }
17276
17277 char *
17278 EngineDefinedVariant (ChessProgramState *cps, int n)
17279 {   // return name of n-th unknown variant that engine supports
17280     static char buf[MSG_SIZ];
17281     char *p, *s = cps->variants;
17282     if(!s) return NULL;
17283     do { // parse string from variants feature
17284       VariantClass v;
17285         p = strchr(s, ',');
17286         if(p) *p = NULLCHAR;
17287       v = StringToVariant(s);
17288       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17289         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17290             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17291                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17292                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17293                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17294             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17295         }
17296         if(p) *p++ = ',';
17297         if(n < 0) return buf;
17298     } while(s = p);
17299     return NULL;
17300 }
17301
17302 int
17303 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17304 {
17305   char buf[MSG_SIZ];
17306   int len = strlen(name);
17307   int val;
17308
17309   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17310     (*p) += len + 1;
17311     sscanf(*p, "%d", &val);
17312     *loc = (val != 0);
17313     while (**p && **p != ' ')
17314       (*p)++;
17315     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17316     SendToProgram(buf, cps);
17317     return TRUE;
17318   }
17319   return FALSE;
17320 }
17321
17322 int
17323 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17324 {
17325   char buf[MSG_SIZ];
17326   int len = strlen(name);
17327   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17328     (*p) += len + 1;
17329     sscanf(*p, "%d", loc);
17330     while (**p && **p != ' ') (*p)++;
17331     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17332     SendToProgram(buf, cps);
17333     return TRUE;
17334   }
17335   return FALSE;
17336 }
17337
17338 int
17339 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17340 {
17341   char buf[MSG_SIZ];
17342   int len = strlen(name);
17343   if (strncmp((*p), name, len) == 0
17344       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17345     (*p) += len + 2;
17346     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
17347     FREE(*loc); *loc = malloc(len);
17348     strncpy(*loc, *p, len);
17349     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17350     while (**p && **p != '\"') (*p)++;
17351     if (**p == '\"') (*p)++;
17352     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17353     SendToProgram(buf, cps);
17354     return TRUE;
17355   }
17356   return FALSE;
17357 }
17358
17359 int
17360 ParseOption (Option *opt, ChessProgramState *cps)
17361 // [HGM] options: process the string that defines an engine option, and determine
17362 // name, type, default value, and allowed value range
17363 {
17364         char *p, *q, buf[MSG_SIZ];
17365         int n, min = (-1)<<31, max = 1<<31, def;
17366
17367         opt->target = &opt->value;   // OK for spin/slider and checkbox
17368         if(p = strstr(opt->name, " -spin ")) {
17369             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17370             if(max < min) max = min; // enforce consistency
17371             if(def < min) def = min;
17372             if(def > max) def = max;
17373             opt->value = def;
17374             opt->min = min;
17375             opt->max = max;
17376             opt->type = Spin;
17377         } else if((p = strstr(opt->name, " -slider "))) {
17378             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17379             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17380             if(max < min) max = min; // enforce consistency
17381             if(def < min) def = min;
17382             if(def > max) def = max;
17383             opt->value = def;
17384             opt->min = min;
17385             opt->max = max;
17386             opt->type = Spin; // Slider;
17387         } else if((p = strstr(opt->name, " -string "))) {
17388             opt->textValue = p+9;
17389             opt->type = TextBox;
17390             opt->target = &opt->textValue;
17391         } else if((p = strstr(opt->name, " -file "))) {
17392             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17393             opt->target = opt->textValue = p+7;
17394             opt->type = FileName; // FileName;
17395             opt->target = &opt->textValue;
17396         } else if((p = strstr(opt->name, " -path "))) {
17397             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17398             opt->target = opt->textValue = p+7;
17399             opt->type = PathName; // PathName;
17400             opt->target = &opt->textValue;
17401         } else if(p = strstr(opt->name, " -check ")) {
17402             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17403             opt->value = (def != 0);
17404             opt->type = CheckBox;
17405         } else if(p = strstr(opt->name, " -combo ")) {
17406             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17407             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17408             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17409             opt->value = n = 0;
17410             while(q = StrStr(q, " /// ")) {
17411                 n++; *q = 0;    // count choices, and null-terminate each of them
17412                 q += 5;
17413                 if(*q == '*') { // remember default, which is marked with * prefix
17414                     q++;
17415                     opt->value = n;
17416                 }
17417                 cps->comboList[cps->comboCnt++] = q;
17418             }
17419             cps->comboList[cps->comboCnt++] = NULL;
17420             opt->max = n + 1;
17421             opt->type = ComboBox;
17422         } else if(p = strstr(opt->name, " -button")) {
17423             opt->type = Button;
17424         } else if(p = strstr(opt->name, " -save")) {
17425             opt->type = SaveButton;
17426         } else return FALSE;
17427         *p = 0; // terminate option name
17428         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17429         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17430         // now look if the command-line options define a setting for this engine option.
17431         if(cps->optionSettings && cps->optionSettings[0])
17432             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17433         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17434           snprintf(buf, MSG_SIZ, "option %s", p);
17435                 if(p = strstr(buf, ",")) *p = 0;
17436                 if(q = strchr(buf, '=')) switch(opt->type) {
17437                     case ComboBox:
17438                         for(n=0; n<opt->max; n++)
17439                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17440                         break;
17441                     case TextBox:
17442                     case FileName:
17443                     case PathName:
17444                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17445                         break;
17446                     case Spin:
17447                     case CheckBox:
17448                         opt->value = atoi(q+1);
17449                     default:
17450                         break;
17451                 }
17452                 strcat(buf, "\n");
17453                 SendToProgram(buf, cps);
17454         }
17455         return TRUE;
17456 }
17457
17458 void
17459 FeatureDone (ChessProgramState *cps, int val)
17460 {
17461   DelayedEventCallback cb = GetDelayedEvent();
17462   if ((cb == InitBackEnd3 && cps == &first) ||
17463       (cb == SettingsMenuIfReady && cps == &second) ||
17464       (cb == LoadEngine) || (cb == StartSecond) ||
17465       (cb == TwoMachinesEventIfReady)) {
17466     CancelDelayedEvent();
17467     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17468   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17469   cps->initDone = val;
17470   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17471 }
17472
17473 /* Parse feature command from engine */
17474 void
17475 ParseFeatures (char *args, ChessProgramState *cps)
17476 {
17477   char *p = args;
17478   char *q = NULL;
17479   int val;
17480   char buf[MSG_SIZ];
17481
17482   for (;;) {
17483     while (*p == ' ') p++;
17484     if (*p == NULLCHAR) return;
17485
17486     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17487     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17488     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17489     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17490     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17491     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17492     if (BoolFeature(&p, "reuse", &val, cps)) {
17493       /* Engine can disable reuse, but can't enable it if user said no */
17494       if (!val) cps->reuse = FALSE;
17495       continue;
17496     }
17497     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17498     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17499       if (gameMode == TwoMachinesPlay) {
17500         DisplayTwoMachinesTitle();
17501       } else {
17502         DisplayTitle("");
17503       }
17504       continue;
17505     }
17506     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17507     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17508     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17509     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17510     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17511     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17512     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17513     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17514     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17515     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17516     if (IntFeature(&p, "done", &val, cps)) {
17517       FeatureDone(cps, val);
17518       continue;
17519     }
17520     /* Added by Tord: */
17521     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17522     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17523     /* End of additions by Tord */
17524
17525     /* [HGM] added features: */
17526     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17527     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17528     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17529     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17530     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17531     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17532     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17533     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17534         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17535         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17536         FREE(cps->option[cps->nrOptions].name);
17537         cps->option[cps->nrOptions].name = q; q = NULL;
17538         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17539           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17540             SendToProgram(buf, cps);
17541             continue;
17542         }
17543         if(cps->nrOptions >= MAX_OPTIONS) {
17544             cps->nrOptions--;
17545             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17546             DisplayError(buf, 0);
17547         }
17548         continue;
17549     }
17550     /* End of additions by HGM */
17551
17552     /* unknown feature: complain and skip */
17553     q = p;
17554     while (*q && *q != '=') q++;
17555     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17556     SendToProgram(buf, cps);
17557     p = q;
17558     if (*p == '=') {
17559       p++;
17560       if (*p == '\"') {
17561         p++;
17562         while (*p && *p != '\"') p++;
17563         if (*p == '\"') p++;
17564       } else {
17565         while (*p && *p != ' ') p++;
17566       }
17567     }
17568   }
17569
17570 }
17571
17572 void
17573 PeriodicUpdatesEvent (int newState)
17574 {
17575     if (newState == appData.periodicUpdates)
17576       return;
17577
17578     appData.periodicUpdates=newState;
17579
17580     /* Display type changes, so update it now */
17581 //    DisplayAnalysis();
17582
17583     /* Get the ball rolling again... */
17584     if (newState) {
17585         AnalysisPeriodicEvent(1);
17586         StartAnalysisClock();
17587     }
17588 }
17589
17590 void
17591 PonderNextMoveEvent (int newState)
17592 {
17593     if (newState == appData.ponderNextMove) return;
17594     if (gameMode == EditPosition) EditPositionDone(TRUE);
17595     if (newState) {
17596         SendToProgram("hard\n", &first);
17597         if (gameMode == TwoMachinesPlay) {
17598             SendToProgram("hard\n", &second);
17599         }
17600     } else {
17601         SendToProgram("easy\n", &first);
17602         thinkOutput[0] = NULLCHAR;
17603         if (gameMode == TwoMachinesPlay) {
17604             SendToProgram("easy\n", &second);
17605         }
17606     }
17607     appData.ponderNextMove = newState;
17608 }
17609
17610 void
17611 NewSettingEvent (int option, int *feature, char *command, int value)
17612 {
17613     char buf[MSG_SIZ];
17614
17615     if (gameMode == EditPosition) EditPositionDone(TRUE);
17616     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17617     if(feature == NULL || *feature) SendToProgram(buf, &first);
17618     if (gameMode == TwoMachinesPlay) {
17619         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17620     }
17621 }
17622
17623 void
17624 ShowThinkingEvent ()
17625 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17626 {
17627     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17628     int newState = appData.showThinking
17629         // [HGM] thinking: other features now need thinking output as well
17630         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17631
17632     if (oldState == newState) return;
17633     oldState = newState;
17634     if (gameMode == EditPosition) EditPositionDone(TRUE);
17635     if (oldState) {
17636         SendToProgram("post\n", &first);
17637         if (gameMode == TwoMachinesPlay) {
17638             SendToProgram("post\n", &second);
17639         }
17640     } else {
17641         SendToProgram("nopost\n", &first);
17642         thinkOutput[0] = NULLCHAR;
17643         if (gameMode == TwoMachinesPlay) {
17644             SendToProgram("nopost\n", &second);
17645         }
17646     }
17647 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17648 }
17649
17650 void
17651 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17652 {
17653   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17654   if (pr == NoProc) return;
17655   AskQuestion(title, question, replyPrefix, pr);
17656 }
17657
17658 void
17659 TypeInEvent (char firstChar)
17660 {
17661     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17662         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17663         gameMode == AnalyzeMode || gameMode == EditGame ||
17664         gameMode == EditPosition || gameMode == IcsExamining ||
17665         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17666         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17667                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17668                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17669         gameMode == Training) PopUpMoveDialog(firstChar);
17670 }
17671
17672 void
17673 TypeInDoneEvent (char *move)
17674 {
17675         Board board;
17676         int n, fromX, fromY, toX, toY;
17677         char promoChar;
17678         ChessMove moveType;
17679
17680         // [HGM] FENedit
17681         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17682                 EditPositionPasteFEN(move);
17683                 return;
17684         }
17685         // [HGM] movenum: allow move number to be typed in any mode
17686         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17687           ToNrEvent(2*n-1);
17688           return;
17689         }
17690         // undocumented kludge: allow command-line option to be typed in!
17691         // (potentially fatal, and does not implement the effect of the option.)
17692         // should only be used for options that are values on which future decisions will be made,
17693         // and definitely not on options that would be used during initialization.
17694         if(strstr(move, "!!! -") == move) {
17695             ParseArgsFromString(move+4);
17696             return;
17697         }
17698
17699       if (gameMode != EditGame && currentMove != forwardMostMove &&
17700         gameMode != Training) {
17701         DisplayMoveError(_("Displayed move is not current"));
17702       } else {
17703         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17704           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17705         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17706         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17707           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17708           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17709         } else {
17710           DisplayMoveError(_("Could not parse move"));
17711         }
17712       }
17713 }
17714
17715 void
17716 DisplayMove (int moveNumber)
17717 {
17718     char message[MSG_SIZ];
17719     char res[MSG_SIZ];
17720     char cpThinkOutput[MSG_SIZ];
17721
17722     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17723
17724     if (moveNumber == forwardMostMove - 1 ||
17725         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17726
17727         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17728
17729         if (strchr(cpThinkOutput, '\n')) {
17730             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17731         }
17732     } else {
17733         *cpThinkOutput = NULLCHAR;
17734     }
17735
17736     /* [AS] Hide thinking from human user */
17737     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17738         *cpThinkOutput = NULLCHAR;
17739         if( thinkOutput[0] != NULLCHAR ) {
17740             int i;
17741
17742             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17743                 cpThinkOutput[i] = '.';
17744             }
17745             cpThinkOutput[i] = NULLCHAR;
17746             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17747         }
17748     }
17749
17750     if (moveNumber == forwardMostMove - 1 &&
17751         gameInfo.resultDetails != NULL) {
17752         if (gameInfo.resultDetails[0] == NULLCHAR) {
17753           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17754         } else {
17755           snprintf(res, MSG_SIZ, " {%s} %s",
17756                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17757         }
17758     } else {
17759         res[0] = NULLCHAR;
17760     }
17761
17762     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17763         DisplayMessage(res, cpThinkOutput);
17764     } else {
17765       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17766                 WhiteOnMove(moveNumber) ? " " : ".. ",
17767                 parseList[moveNumber], res);
17768         DisplayMessage(message, cpThinkOutput);
17769     }
17770 }
17771
17772 void
17773 DisplayComment (int moveNumber, char *text)
17774 {
17775     char title[MSG_SIZ];
17776
17777     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17778       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17779     } else {
17780       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17781               WhiteOnMove(moveNumber) ? " " : ".. ",
17782               parseList[moveNumber]);
17783     }
17784     if (text != NULL && (appData.autoDisplayComment || commentUp))
17785         CommentPopUp(title, text);
17786 }
17787
17788 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17789  * might be busy thinking or pondering.  It can be omitted if your
17790  * gnuchess is configured to stop thinking immediately on any user
17791  * input.  However, that gnuchess feature depends on the FIONREAD
17792  * ioctl, which does not work properly on some flavors of Unix.
17793  */
17794 void
17795 Attention (ChessProgramState *cps)
17796 {
17797 #if ATTENTION
17798     if (!cps->useSigint) return;
17799     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17800     switch (gameMode) {
17801       case MachinePlaysWhite:
17802       case MachinePlaysBlack:
17803       case TwoMachinesPlay:
17804       case IcsPlayingWhite:
17805       case IcsPlayingBlack:
17806       case AnalyzeMode:
17807       case AnalyzeFile:
17808         /* Skip if we know it isn't thinking */
17809         if (!cps->maybeThinking) return;
17810         if (appData.debugMode)
17811           fprintf(debugFP, "Interrupting %s\n", cps->which);
17812         InterruptChildProcess(cps->pr);
17813         cps->maybeThinking = FALSE;
17814         break;
17815       default:
17816         break;
17817     }
17818 #endif /*ATTENTION*/
17819 }
17820
17821 int
17822 CheckFlags ()
17823 {
17824     if (whiteTimeRemaining <= 0) {
17825         if (!whiteFlag) {
17826             whiteFlag = TRUE;
17827             if (appData.icsActive) {
17828                 if (appData.autoCallFlag &&
17829                     gameMode == IcsPlayingBlack && !blackFlag) {
17830                   SendToICS(ics_prefix);
17831                   SendToICS("flag\n");
17832                 }
17833             } else {
17834                 if (blackFlag) {
17835                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17836                 } else {
17837                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17838                     if (appData.autoCallFlag) {
17839                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17840                         return TRUE;
17841                     }
17842                 }
17843             }
17844         }
17845     }
17846     if (blackTimeRemaining <= 0) {
17847         if (!blackFlag) {
17848             blackFlag = TRUE;
17849             if (appData.icsActive) {
17850                 if (appData.autoCallFlag &&
17851                     gameMode == IcsPlayingWhite && !whiteFlag) {
17852                   SendToICS(ics_prefix);
17853                   SendToICS("flag\n");
17854                 }
17855             } else {
17856                 if (whiteFlag) {
17857                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17858                 } else {
17859                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17860                     if (appData.autoCallFlag) {
17861                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17862                         return TRUE;
17863                     }
17864                 }
17865             }
17866         }
17867     }
17868     return FALSE;
17869 }
17870
17871 void
17872 CheckTimeControl ()
17873 {
17874     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17875         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17876
17877     /*
17878      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17879      */
17880     if ( !WhiteOnMove(forwardMostMove) ) {
17881         /* White made time control */
17882         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17883         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17884         /* [HGM] time odds: correct new time quota for time odds! */
17885                                             / WhitePlayer()->timeOdds;
17886         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17887     } else {
17888         lastBlack -= blackTimeRemaining;
17889         /* Black made time control */
17890         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17891                                             / WhitePlayer()->other->timeOdds;
17892         lastWhite = whiteTimeRemaining;
17893     }
17894 }
17895
17896 void
17897 DisplayBothClocks ()
17898 {
17899     int wom = gameMode == EditPosition ?
17900       !blackPlaysFirst : WhiteOnMove(currentMove);
17901     DisplayWhiteClock(whiteTimeRemaining, wom);
17902     DisplayBlackClock(blackTimeRemaining, !wom);
17903 }
17904
17905
17906 /* Timekeeping seems to be a portability nightmare.  I think everyone
17907    has ftime(), but I'm really not sure, so I'm including some ifdefs
17908    to use other calls if you don't.  Clocks will be less accurate if
17909    you have neither ftime nor gettimeofday.
17910 */
17911
17912 /* VS 2008 requires the #include outside of the function */
17913 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17914 #include <sys/timeb.h>
17915 #endif
17916
17917 /* Get the current time as a TimeMark */
17918 void
17919 GetTimeMark (TimeMark *tm)
17920 {
17921 #if HAVE_GETTIMEOFDAY
17922
17923     struct timeval timeVal;
17924     struct timezone timeZone;
17925
17926     gettimeofday(&timeVal, &timeZone);
17927     tm->sec = (long) timeVal.tv_sec;
17928     tm->ms = (int) (timeVal.tv_usec / 1000L);
17929
17930 #else /*!HAVE_GETTIMEOFDAY*/
17931 #if HAVE_FTIME
17932
17933 // include <sys/timeb.h> / moved to just above start of function
17934     struct timeb timeB;
17935
17936     ftime(&timeB);
17937     tm->sec = (long) timeB.time;
17938     tm->ms = (int) timeB.millitm;
17939
17940 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17941     tm->sec = (long) time(NULL);
17942     tm->ms = 0;
17943 #endif
17944 #endif
17945 }
17946
17947 /* Return the difference in milliseconds between two
17948    time marks.  We assume the difference will fit in a long!
17949 */
17950 long
17951 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17952 {
17953     return 1000L*(tm2->sec - tm1->sec) +
17954            (long) (tm2->ms - tm1->ms);
17955 }
17956
17957
17958 /*
17959  * Code to manage the game clocks.
17960  *
17961  * In tournament play, black starts the clock and then white makes a move.
17962  * We give the human user a slight advantage if he is playing white---the
17963  * clocks don't run until he makes his first move, so it takes zero time.
17964  * Also, we don't account for network lag, so we could get out of sync
17965  * with GNU Chess's clock -- but then, referees are always right.
17966  */
17967
17968 static TimeMark tickStartTM;
17969 static long intendedTickLength;
17970
17971 long
17972 NextTickLength (long timeRemaining)
17973 {
17974     long nominalTickLength, nextTickLength;
17975
17976     if (timeRemaining > 0L && timeRemaining <= 10000L)
17977       nominalTickLength = 100L;
17978     else
17979       nominalTickLength = 1000L;
17980     nextTickLength = timeRemaining % nominalTickLength;
17981     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17982
17983     return nextTickLength;
17984 }
17985
17986 /* Adjust clock one minute up or down */
17987 void
17988 AdjustClock (Boolean which, int dir)
17989 {
17990     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17991     if(which) blackTimeRemaining += 60000*dir;
17992     else      whiteTimeRemaining += 60000*dir;
17993     DisplayBothClocks();
17994     adjustedClock = TRUE;
17995 }
17996
17997 /* Stop clocks and reset to a fresh time control */
17998 void
17999 ResetClocks ()
18000 {
18001     (void) StopClockTimer();
18002     if (appData.icsActive) {
18003         whiteTimeRemaining = blackTimeRemaining = 0;
18004     } else if (searchTime) {
18005         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18006         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18007     } else { /* [HGM] correct new time quote for time odds */
18008         whiteTC = blackTC = fullTimeControlString;
18009         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
18010         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
18011     }
18012     if (whiteFlag || blackFlag) {
18013         DisplayTitle("");
18014         whiteFlag = blackFlag = FALSE;
18015     }
18016     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
18017     DisplayBothClocks();
18018     adjustedClock = FALSE;
18019 }
18020
18021 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
18022
18023 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
18024
18025 /* Decrement running clock by amount of time that has passed */
18026 void
18027 DecrementClocks ()
18028 {
18029     long tRemaining;
18030     long lastTickLength, fudge;
18031     TimeMark now;
18032
18033     if (!appData.clockMode) return;
18034     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
18035
18036     GetTimeMark(&now);
18037
18038     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18039
18040     /* Fudge if we woke up a little too soon */
18041     fudge = intendedTickLength - lastTickLength;
18042     if (fudge < 0 || fudge > FUDGE) fudge = 0;
18043
18044     if (WhiteOnMove(forwardMostMove)) {
18045         if(whiteNPS >= 0) lastTickLength = 0;
18046          tRemaining = whiteTimeRemaining -= lastTickLength;
18047         if( tRemaining < 0 && !appData.icsActive) {
18048             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
18049             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
18050                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
18051                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
18052             }
18053         }
18054         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
18055         DisplayWhiteClock(whiteTimeRemaining - fudge,
18056                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18057         timeSuffix = 0;
18058     } else {
18059         if(blackNPS >= 0) lastTickLength = 0;
18060          tRemaining = blackTimeRemaining -= lastTickLength;
18061         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
18062             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
18063             if(suddenDeath) {
18064                 blackStartMove = forwardMostMove;
18065                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
18066             }
18067         }
18068         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
18069         DisplayBlackClock(blackTimeRemaining - fudge,
18070                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18071         timeSuffix = 0;
18072     }
18073     if (CheckFlags()) return;
18074
18075     if(twoBoards) { // count down secondary board's clocks as well
18076         activePartnerTime -= lastTickLength;
18077         partnerUp = 1;
18078         if(activePartner == 'W')
18079             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
18080         else
18081             DisplayBlackClock(activePartnerTime, TRUE);
18082         partnerUp = 0;
18083     }
18084
18085     tickStartTM = now;
18086     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
18087     StartClockTimer(intendedTickLength);
18088
18089     /* if the time remaining has fallen below the alarm threshold, sound the
18090      * alarm. if the alarm has sounded and (due to a takeback or time control
18091      * with increment) the time remaining has increased to a level above the
18092      * threshold, reset the alarm so it can sound again.
18093      */
18094
18095     if (appData.icsActive && appData.icsAlarm) {
18096
18097         /* make sure we are dealing with the user's clock */
18098         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
18099                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
18100            )) return;
18101
18102         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
18103             alarmSounded = FALSE;
18104         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
18105             PlayAlarmSound();
18106             alarmSounded = TRUE;
18107         }
18108     }
18109 }
18110
18111
18112 /* A player has just moved, so stop the previously running
18113    clock and (if in clock mode) start the other one.
18114    We redisplay both clocks in case we're in ICS mode, because
18115    ICS gives us an update to both clocks after every move.
18116    Note that this routine is called *after* forwardMostMove
18117    is updated, so the last fractional tick must be subtracted
18118    from the color that is *not* on move now.
18119 */
18120 void
18121 SwitchClocks (int newMoveNr)
18122 {
18123     long lastTickLength;
18124     TimeMark now;
18125     int flagged = FALSE;
18126
18127     GetTimeMark(&now);
18128
18129     if (StopClockTimer() && appData.clockMode) {
18130         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18131         if (!WhiteOnMove(forwardMostMove)) {
18132             if(blackNPS >= 0) lastTickLength = 0;
18133             blackTimeRemaining -= lastTickLength;
18134            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18135 //         if(pvInfoList[forwardMostMove].time == -1)
18136                  pvInfoList[forwardMostMove].time =               // use GUI time
18137                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18138         } else {
18139            if(whiteNPS >= 0) lastTickLength = 0;
18140            whiteTimeRemaining -= lastTickLength;
18141            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18142 //         if(pvInfoList[forwardMostMove].time == -1)
18143                  pvInfoList[forwardMostMove].time =
18144                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18145         }
18146         flagged = CheckFlags();
18147     }
18148     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18149     CheckTimeControl();
18150
18151     if (flagged || !appData.clockMode) return;
18152
18153     switch (gameMode) {
18154       case MachinePlaysBlack:
18155       case MachinePlaysWhite:
18156       case BeginningOfGame:
18157         if (pausing) return;
18158         break;
18159
18160       case EditGame:
18161       case PlayFromGameFile:
18162       case IcsExamining:
18163         return;
18164
18165       default:
18166         break;
18167     }
18168
18169     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18170         if(WhiteOnMove(forwardMostMove))
18171              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18172         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18173     }
18174
18175     tickStartTM = now;
18176     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18177       whiteTimeRemaining : blackTimeRemaining);
18178     StartClockTimer(intendedTickLength);
18179 }
18180
18181
18182 /* Stop both clocks */
18183 void
18184 StopClocks ()
18185 {
18186     long lastTickLength;
18187     TimeMark now;
18188
18189     if (!StopClockTimer()) return;
18190     if (!appData.clockMode) return;
18191
18192     GetTimeMark(&now);
18193
18194     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18195     if (WhiteOnMove(forwardMostMove)) {
18196         if(whiteNPS >= 0) lastTickLength = 0;
18197         whiteTimeRemaining -= lastTickLength;
18198         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18199     } else {
18200         if(blackNPS >= 0) lastTickLength = 0;
18201         blackTimeRemaining -= lastTickLength;
18202         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18203     }
18204     CheckFlags();
18205 }
18206
18207 /* Start clock of player on move.  Time may have been reset, so
18208    if clock is already running, stop and restart it. */
18209 void
18210 StartClocks ()
18211 {
18212     (void) StopClockTimer(); /* in case it was running already */
18213     DisplayBothClocks();
18214     if (CheckFlags()) return;
18215
18216     if (!appData.clockMode) return;
18217     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18218
18219     GetTimeMark(&tickStartTM);
18220     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18221       whiteTimeRemaining : blackTimeRemaining);
18222
18223    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18224     whiteNPS = blackNPS = -1;
18225     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18226        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18227         whiteNPS = first.nps;
18228     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18229        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18230         blackNPS = first.nps;
18231     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18232         whiteNPS = second.nps;
18233     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18234         blackNPS = second.nps;
18235     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18236
18237     StartClockTimer(intendedTickLength);
18238 }
18239
18240 char *
18241 TimeString (long ms)
18242 {
18243     long second, minute, hour, day;
18244     char *sign = "";
18245     static char buf[40], moveTime[8];
18246
18247     if (ms > 0 && ms <= 9900) {
18248       /* convert milliseconds to tenths, rounding up */
18249       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18250
18251       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18252       return buf;
18253     }
18254
18255     /* convert milliseconds to seconds, rounding up */
18256     /* use floating point to avoid strangeness of integer division
18257        with negative dividends on many machines */
18258     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18259
18260     if (second < 0) {
18261         sign = "-";
18262         second = -second;
18263     }
18264
18265     day = second / (60 * 60 * 24);
18266     second = second % (60 * 60 * 24);
18267     hour = second / (60 * 60);
18268     second = second % (60 * 60);
18269     minute = second / 60;
18270     second = second % 60;
18271
18272     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18273     else *moveTime = NULLCHAR;
18274
18275     if (day > 0)
18276       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18277               sign, day, hour, minute, second, moveTime);
18278     else if (hour > 0)
18279       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18280     else
18281       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18282
18283     return buf;
18284 }
18285
18286
18287 /*
18288  * This is necessary because some C libraries aren't ANSI C compliant yet.
18289  */
18290 char *
18291 StrStr (char *string, char *match)
18292 {
18293     int i, length;
18294
18295     length = strlen(match);
18296
18297     for (i = strlen(string) - length; i >= 0; i--, string++)
18298       if (!strncmp(match, string, length))
18299         return string;
18300
18301     return NULL;
18302 }
18303
18304 char *
18305 StrCaseStr (char *string, char *match)
18306 {
18307     int i, j, length;
18308
18309     length = strlen(match);
18310
18311     for (i = strlen(string) - length; i >= 0; i--, string++) {
18312         for (j = 0; j < length; j++) {
18313             if (ToLower(match[j]) != ToLower(string[j]))
18314               break;
18315         }
18316         if (j == length) return string;
18317     }
18318
18319     return NULL;
18320 }
18321
18322 #ifndef _amigados
18323 int
18324 StrCaseCmp (char *s1, char *s2)
18325 {
18326     char c1, c2;
18327
18328     for (;;) {
18329         c1 = ToLower(*s1++);
18330         c2 = ToLower(*s2++);
18331         if (c1 > c2) return 1;
18332         if (c1 < c2) return -1;
18333         if (c1 == NULLCHAR) return 0;
18334     }
18335 }
18336
18337
18338 int
18339 ToLower (int c)
18340 {
18341     return isupper(c) ? tolower(c) : c;
18342 }
18343
18344
18345 int
18346 ToUpper (int c)
18347 {
18348     return islower(c) ? toupper(c) : c;
18349 }
18350 #endif /* !_amigados    */
18351
18352 char *
18353 StrSave (char *s)
18354 {
18355   char *ret;
18356
18357   if ((ret = (char *) malloc(strlen(s) + 1)))
18358     {
18359       safeStrCpy(ret, s, strlen(s)+1);
18360     }
18361   return ret;
18362 }
18363
18364 char *
18365 StrSavePtr (char *s, char **savePtr)
18366 {
18367     if (*savePtr) {
18368         free(*savePtr);
18369     }
18370     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18371       safeStrCpy(*savePtr, s, strlen(s)+1);
18372     }
18373     return(*savePtr);
18374 }
18375
18376 char *
18377 PGNDate ()
18378 {
18379     time_t clock;
18380     struct tm *tm;
18381     char buf[MSG_SIZ];
18382
18383     clock = time((time_t *)NULL);
18384     tm = localtime(&clock);
18385     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18386             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18387     return StrSave(buf);
18388 }
18389
18390
18391 char *
18392 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18393 {
18394     int i, j, fromX, fromY, toX, toY;
18395     int whiteToPlay, haveRights = nrCastlingRights;
18396     char buf[MSG_SIZ];
18397     char *p, *q;
18398     int emptycount;
18399     ChessSquare piece;
18400
18401     whiteToPlay = (gameMode == EditPosition) ?
18402       !blackPlaysFirst : (move % 2 == 0);
18403     p = buf;
18404
18405     /* Piece placement data */
18406     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18407         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18408         emptycount = 0;
18409         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18410             if (boards[move][i][j] == EmptySquare) {
18411                 emptycount++;
18412             } else { ChessSquare piece = boards[move][i][j];
18413                 if (emptycount > 0) {
18414                     if(emptycount<10) /* [HGM] can be >= 10 */
18415                         *p++ = '0' + emptycount;
18416                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18417                     emptycount = 0;
18418                 }
18419                 if(PieceToChar(piece) == '+') {
18420                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18421                     *p++ = '+';
18422                     piece = (ChessSquare)(CHUDEMOTED(piece));
18423                 }
18424                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18425                 if(*p = PieceSuffix(piece)) p++;
18426                 if(p[-1] == '~') {
18427                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18428                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18429                     *p++ = '~';
18430                 }
18431             }
18432         }
18433         if (emptycount > 0) {
18434             if(emptycount<10) /* [HGM] can be >= 10 */
18435                 *p++ = '0' + emptycount;
18436             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18437             emptycount = 0;
18438         }
18439         *p++ = '/';
18440     }
18441     *(p - 1) = ' ';
18442
18443     /* [HGM] print Crazyhouse or Shogi holdings */
18444     if( gameInfo.holdingsWidth ) {
18445         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18446         q = p;
18447         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18448             piece = boards[move][i][BOARD_WIDTH-1];
18449             if( piece != EmptySquare )
18450               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18451                   *p++ = PieceToChar(piece);
18452         }
18453         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18454             piece = boards[move][handSize-i-1][0];
18455             if( piece != EmptySquare )
18456               for(j=0; j<(int) boards[move][handSize-i-1][1]; j++)
18457                   *p++ = PieceToChar(piece);
18458         }
18459
18460         if( q == p ) *p++ = '-';
18461         *p++ = ']';
18462         *p++ = ' ';
18463     }
18464
18465     /* Active color */
18466     *p++ = whiteToPlay ? 'w' : 'b';
18467     *p++ = ' ';
18468
18469   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18470     haveRights = 0; q = p;
18471     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18472       piece = boards[move][0][i];
18473       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18474         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18475       }
18476     }
18477     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18478       piece = boards[move][BOARD_HEIGHT-1][i];
18479       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18480         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18481       }
18482     }
18483     if(p == q) *p++ = '-';
18484     *p++ = ' ';
18485   }
18486
18487   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18488     while(*p++ = *q++)
18489                       ;
18490     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18491   } else {
18492   if(haveRights) {
18493      int handW=0, handB=0;
18494      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18495         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18496         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18497      }
18498      q = p;
18499      if(appData.fischerCastling) {
18500         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18501            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18502                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18503         } else {
18504        /* [HGM] write directly from rights */
18505            if(boards[move][CASTLING][2] != NoRights &&
18506               boards[move][CASTLING][0] != NoRights   )
18507                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18508            if(boards[move][CASTLING][2] != NoRights &&
18509               boards[move][CASTLING][1] != NoRights   )
18510                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18511         }
18512         if(handB) {
18513            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18514                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18515         } else {
18516            if(boards[move][CASTLING][5] != NoRights &&
18517               boards[move][CASTLING][3] != NoRights   )
18518                 *p++ = boards[move][CASTLING][3] + AAA;
18519            if(boards[move][CASTLING][5] != NoRights &&
18520               boards[move][CASTLING][4] != NoRights   )
18521                 *p++ = boards[move][CASTLING][4] + AAA;
18522         }
18523      } else {
18524
18525         /* [HGM] write true castling rights */
18526         if( nrCastlingRights == 6 ) {
18527             int q, k=0;
18528             if(boards[move][CASTLING][0] != NoRights &&
18529                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18530             q = (boards[move][CASTLING][1] != NoRights &&
18531                  boards[move][CASTLING][2] != NoRights  );
18532             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18533                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18534                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18535                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18536             }
18537             if(q) *p++ = 'Q';
18538             k = 0;
18539             if(boards[move][CASTLING][3] != NoRights &&
18540                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18541             q = (boards[move][CASTLING][4] != NoRights &&
18542                  boards[move][CASTLING][5] != NoRights  );
18543             if(handB) {
18544                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18545                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18546                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18547             }
18548             if(q) *p++ = 'q';
18549         }
18550      }
18551      if (q == p) *p++ = '-'; /* No castling rights */
18552      *p++ = ' ';
18553   }
18554
18555   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18556      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18557      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18558     /* En passant target square */
18559     if (move > backwardMostMove) {
18560         fromX = moveList[move - 1][0] - AAA;
18561         fromY = moveList[move - 1][1] - ONE;
18562         toX = moveList[move - 1][2] - AAA;
18563         toY = moveList[move - 1][3] - ONE;
18564         if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18565             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18566             /* 2-square pawn move just happened */
18567             *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18568             *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18569             if(gameInfo.variant == VariantBerolina) {
18570                 *p++ = toX + AAA;
18571                 *p++ = toY + ONE;
18572             }
18573         } else {
18574             *p++ = '-';
18575         }
18576     } else if(move == backwardMostMove) {
18577         // [HGM] perhaps we should always do it like this, and forget the above?
18578         if((signed char)boards[move][EP_STATUS] >= 0) {
18579             *p++ = boards[move][EP_STATUS] + AAA;
18580             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18581         } else {
18582             *p++ = '-';
18583         }
18584     } else {
18585         *p++ = '-';
18586     }
18587     *p++ = ' ';
18588   }
18589   }
18590
18591     i = boards[move][CHECK_COUNT];
18592     if(i) {
18593         sprintf(p, "%d+%d ", i&255, i>>8);
18594         while(*p) p++;
18595     }
18596
18597     if(moveCounts)
18598     {   int i = 0, j=move;
18599
18600         /* [HGM] find reversible plies */
18601         if (appData.debugMode) { int k;
18602             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18603             for(k=backwardMostMove; k<=forwardMostMove; k++)
18604                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18605
18606         }
18607
18608         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18609         if( j == backwardMostMove ) i += initialRulePlies;
18610         sprintf(p, "%d ", i);
18611         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18612
18613         /* Fullmove number */
18614         sprintf(p, "%d", (move / 2) + 1);
18615     } else *--p = NULLCHAR;
18616
18617     return StrSave(buf);
18618 }
18619
18620 Boolean
18621 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18622 {
18623     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18624     char *p, c;
18625     int emptycount, virgin[BOARD_FILES];
18626     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18627
18628     p = fen;
18629
18630     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18631
18632     /* Piece placement data */
18633     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18634         j = 0;
18635         for (;;) {
18636             if (*p == '/' || *p == ' ' || *p == '[' ) {
18637                 if(j > w) w = j;
18638                 emptycount = gameInfo.boardWidth - j;
18639                 while (emptycount--)
18640                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18641                 if (*p == '/') p++;
18642                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18643                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18644                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18645                     }
18646                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18647                 }
18648                 break;
18649 #if(BOARD_FILES >= 10)*0
18650             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18651                 p++; emptycount=10;
18652                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18653                 while (emptycount--)
18654                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18655 #endif
18656             } else if (*p == '*') {
18657                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18658             } else if (isdigit(*p)) {
18659                 emptycount = *p++ - '0';
18660                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18661                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18662                 while (emptycount--)
18663                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18664             } else if (*p == '<') {
18665                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18666                 else if (i != 0 || !shuffle) return FALSE;
18667                 p++;
18668             } else if (shuffle && *p == '>') {
18669                 p++; // for now ignore closing shuffle range, and assume rank-end
18670             } else if (*p == '?') {
18671                 if (j >= gameInfo.boardWidth) return FALSE;
18672                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18673                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18674             } else if (*p == '+' || isalpha(*p)) {
18675                 char *q, *s = SUFFIXES;
18676                 if (j >= gameInfo.boardWidth) return FALSE;
18677                 if(*p=='+') {
18678                     char c = *++p;
18679                     if(q = strchr(s, p[1])) p++;
18680                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18681                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18682                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18683                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18684                 } else {
18685                     char c = *p++;
18686                     if(q = strchr(s, *p)) p++;
18687                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18688                 }
18689
18690                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18691                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18692                     piece = (ChessSquare) (PROMOTED(piece));
18693                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18694                     p++;
18695                 }
18696                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18697                 if(piece == king) wKingRank = i;
18698                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18699             } else {
18700                 return FALSE;
18701             }
18702         }
18703     }
18704     while (*p == '/' || *p == ' ') p++;
18705
18706     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18707
18708     /* [HGM] by default clear Crazyhouse holdings, if present */
18709     if(gameInfo.holdingsWidth) {
18710        for(i=0; i<handSize; i++) {
18711            board[i][0]             = EmptySquare; /* black holdings */
18712            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18713            board[i][1]             = (ChessSquare) 0; /* black counts */
18714            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18715        }
18716     }
18717
18718     /* [HGM] look for Crazyhouse holdings here */
18719     while(*p==' ') p++;
18720     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18721         int swap=0, wcnt=0, bcnt=0;
18722         if(*p == '[') p++;
18723         if(*p == '<') swap++, p++;
18724         if(*p == '-' ) p++; /* empty holdings */ else {
18725             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18726             /* if we would allow FEN reading to set board size, we would   */
18727             /* have to add holdings and shift the board read so far here   */
18728             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18729                 p++;
18730                 if((int) piece >= (int) BlackPawn ) {
18731                     i = (int)piece - (int)BlackPawn;
18732                     i = PieceToNumber((ChessSquare)i);
18733                     if( i >= gameInfo.holdingsSize ) return FALSE;
18734                     board[handSize-1-i][0] = piece; /* black holdings */
18735                     board[handSize-1-i][1]++;       /* black counts   */
18736                     bcnt++;
18737                 } else {
18738                     i = (int)piece - (int)WhitePawn;
18739                     i = PieceToNumber((ChessSquare)i);
18740                     if( i >= gameInfo.holdingsSize ) return FALSE;
18741                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18742                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18743                     wcnt++;
18744                 }
18745             }
18746             if(subst) { // substitute back-rank question marks by holdings pieces
18747                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18748                     int k, m, n = bcnt + 1;
18749                     if(board[0][j] == ClearBoard) {
18750                         if(!wcnt) return FALSE;
18751                         n = rand() % wcnt;
18752                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18753                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18754                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18755                             break;
18756                         }
18757                     }
18758                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18759                         if(!bcnt) return FALSE;
18760                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18761                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[handSize-1-k][1]) < 0) {
18762                             board[BOARD_HEIGHT-1][j] = board[handSize-1-k][0]; bcnt--;
18763                             if(--board[handSize-1-k][1] == 0) board[handSize-1-k][0] = EmptySquare;
18764                             break;
18765                         }
18766                     }
18767                 }
18768                 subst = 0;
18769             }
18770         }
18771         if(*p == ']') p++;
18772     }
18773
18774     if(subst) return FALSE; // substitution requested, but no holdings
18775
18776     while(*p == ' ') p++;
18777
18778     /* Active color */
18779     c = *p++;
18780     if(appData.colorNickNames) {
18781       if( c == appData.colorNickNames[0] ) c = 'w'; else
18782       if( c == appData.colorNickNames[1] ) c = 'b';
18783     }
18784     switch (c) {
18785       case 'w':
18786         *blackPlaysFirst = FALSE;
18787         break;
18788       case 'b':
18789         *blackPlaysFirst = TRUE;
18790         break;
18791       default:
18792         return FALSE;
18793     }
18794
18795     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18796     /* return the extra info in global variiables             */
18797
18798     while(*p==' ') p++;
18799
18800     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18801         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18802         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18803     }
18804
18805     /* set defaults in case FEN is incomplete */
18806     board[EP_STATUS] = EP_UNKNOWN;
18807     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18808     for(i=0; i<nrCastlingRights; i++ ) {
18809         board[CASTLING][i] =
18810             appData.fischerCastling ? NoRights : initialRights[i];
18811     }   /* assume possible unless obviously impossible */
18812     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18813     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18814     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18815                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18816     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18817     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18818     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18819                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18820     FENrulePlies = 0;
18821
18822     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18823       char *q = p;
18824       int w=0, b=0;
18825       while(isalpha(*p)) {
18826         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18827         if(islower(*p)) b |= 1 << (*p++ - 'a');
18828       }
18829       if(*p == '-') p++;
18830       if(p != q) {
18831         board[TOUCHED_W] = ~w;
18832         board[TOUCHED_B] = ~b;
18833         while(*p == ' ') p++;
18834       }
18835     } else
18836
18837     if(nrCastlingRights) {
18838       int fischer = 0;
18839       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18840       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18841           /* castling indicator present, so default becomes no castlings */
18842           for(i=0; i<nrCastlingRights; i++ ) {
18843                  board[CASTLING][i] = NoRights;
18844           }
18845       }
18846       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18847              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18848              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18849              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18850         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18851
18852         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18853             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18854             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18855         }
18856         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18857             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18858         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18859                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18860         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18861                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18862         switch(c) {
18863           case'K':
18864               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18865               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18866               board[CASTLING][2] = whiteKingFile;
18867               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18868               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18869               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18870               break;
18871           case'Q':
18872               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18873               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18874               board[CASTLING][2] = whiteKingFile;
18875               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18876               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18877               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18878               break;
18879           case'k':
18880               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18881               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18882               board[CASTLING][5] = blackKingFile;
18883               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18884               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18885               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18886               break;
18887           case'q':
18888               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18889               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18890               board[CASTLING][5] = blackKingFile;
18891               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18892               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18893               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18894           case '-':
18895               break;
18896           default: /* FRC castlings */
18897               if(c >= 'a') { /* black rights */
18898                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18899                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18900                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18901                   if(i == BOARD_RGHT) break;
18902                   board[CASTLING][5] = i;
18903                   c -= AAA;
18904                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18905                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18906                   if(c > i)
18907                       board[CASTLING][3] = c;
18908                   else
18909                       board[CASTLING][4] = c;
18910               } else { /* white rights */
18911                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18912                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18913                     if(board[0][i] == WhiteKing) break;
18914                   if(i == BOARD_RGHT) break;
18915                   board[CASTLING][2] = i;
18916                   c -= AAA - 'a' + 'A';
18917                   if(board[0][c] >= WhiteKing) break;
18918                   if(c > i)
18919                       board[CASTLING][0] = c;
18920                   else
18921                       board[CASTLING][1] = c;
18922               }
18923         }
18924       }
18925       for(i=0; i<nrCastlingRights; i++)
18926         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18927       if(gameInfo.variant == VariantSChess)
18928         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18929       if(fischer && shuffle) appData.fischerCastling = TRUE;
18930     if (appData.debugMode) {
18931         fprintf(debugFP, "FEN castling rights:");
18932         for(i=0; i<nrCastlingRights; i++)
18933         fprintf(debugFP, " %d", board[CASTLING][i]);
18934         fprintf(debugFP, "\n");
18935     }
18936
18937       while(*p==' ') p++;
18938     }
18939
18940     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18941
18942     /* read e.p. field in games that know e.p. capture */
18943     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18944        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18945        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18946       if(*p=='-') {
18947         p++; board[EP_STATUS] = EP_NONE;
18948       } else {
18949          int d, r, c = *p - AAA;
18950
18951          if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18952              p++;
18953              board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18954              if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18955              d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18956              if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18957              board[LAST_TO] = 256*(r + d) + c;
18958              c = *p++ - AAA;
18959              if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18960                  if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18961                  board[LAST_TO] = 256*r + c;
18962                  if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18963              }
18964          }
18965       }
18966     }
18967
18968     while(*p == ' ') p++;
18969
18970     board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18971     if(sscanf(p, "%d+%d", &i, &j) == 2) {
18972         board[CHECK_COUNT] = i + 256*j;
18973         while(*p && *p != ' ') p++;
18974     }
18975
18976     c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18977     if(c > 0) {
18978         FENrulePlies = i; /* 50-move ply counter */
18979         /* (The move number is still ignored)    */
18980         if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18981     }
18982
18983     return TRUE;
18984 }
18985
18986 void
18987 EditPositionPasteFEN (char *fen)
18988 {
18989   if (fen != NULL) {
18990     Board initial_position;
18991
18992     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18993       DisplayError(_("Bad FEN position in clipboard"), 0);
18994       return ;
18995     } else {
18996       int savedBlackPlaysFirst = blackPlaysFirst;
18997       EditPositionEvent();
18998       blackPlaysFirst = savedBlackPlaysFirst;
18999       CopyBoard(boards[0], initial_position);
19000       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
19001       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
19002       DisplayBothClocks();
19003       DrawPosition(FALSE, boards[currentMove]);
19004     }
19005   }
19006 }
19007
19008 static char cseq[12] = "\\   ";
19009
19010 Boolean
19011 set_cont_sequence (char *new_seq)
19012 {
19013     int len;
19014     Boolean ret;
19015
19016     // handle bad attempts to set the sequence
19017         if (!new_seq)
19018                 return 0; // acceptable error - no debug
19019
19020     len = strlen(new_seq);
19021     ret = (len > 0) && (len < sizeof(cseq));
19022     if (ret)
19023       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
19024     else if (appData.debugMode)
19025       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
19026     return ret;
19027 }
19028
19029 /*
19030     reformat a source message so words don't cross the width boundary.  internal
19031     newlines are not removed.  returns the wrapped size (no null character unless
19032     included in source message).  If dest is NULL, only calculate the size required
19033     for the dest buffer.  lp argument indicats line position upon entry, and it's
19034     passed back upon exit.
19035 */
19036 int
19037 wrap (char *dest, char *src, int count, int width, int *lp)
19038 {
19039     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
19040
19041     cseq_len = strlen(cseq);
19042     old_line = line = *lp;
19043     ansi = len = clen = 0;
19044
19045     for (i=0; i < count; i++)
19046     {
19047         if (src[i] == '\033')
19048             ansi = 1;
19049
19050         // if we hit the width, back up
19051         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
19052         {
19053             // store i & len in case the word is too long
19054             old_i = i, old_len = len;
19055
19056             // find the end of the last word
19057             while (i && src[i] != ' ' && src[i] != '\n')
19058             {
19059                 i--;
19060                 len--;
19061             }
19062
19063             // word too long?  restore i & len before splitting it
19064             if ((old_i-i+clen) >= width)
19065             {
19066                 i = old_i;
19067                 len = old_len;
19068             }
19069
19070             // extra space?
19071             if (i && src[i-1] == ' ')
19072                 len--;
19073
19074             if (src[i] != ' ' && src[i] != '\n')
19075             {
19076                 i--;
19077                 if (len)
19078                     len--;
19079             }
19080
19081             // now append the newline and continuation sequence
19082             if (dest)
19083                 dest[len] = '\n';
19084             len++;
19085             if (dest)
19086                 strncpy(dest+len, cseq, cseq_len);
19087             len += cseq_len;
19088             line = cseq_len;
19089             clen = cseq_len;
19090             continue;
19091         }
19092
19093         if (dest)
19094             dest[len] = src[i];
19095         len++;
19096         if (!ansi)
19097             line++;
19098         if (src[i] == '\n')
19099             line = 0;
19100         if (src[i] == 'm')
19101             ansi = 0;
19102     }
19103     if (dest && appData.debugMode)
19104     {
19105         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
19106             count, width, line, len, *lp);
19107         show_bytes(debugFP, src, count);
19108         fprintf(debugFP, "\ndest: ");
19109         show_bytes(debugFP, dest, len);
19110         fprintf(debugFP, "\n");
19111     }
19112     *lp = dest ? line : old_line;
19113
19114     return len;
19115 }
19116
19117 // [HGM] vari: routines for shelving variations
19118 Boolean modeRestore = FALSE;
19119
19120 void
19121 PushInner (int firstMove, int lastMove)
19122 {
19123         int i, j, nrMoves = lastMove - firstMove;
19124
19125         // push current tail of game on stack
19126         savedResult[storedGames] = gameInfo.result;
19127         savedDetails[storedGames] = gameInfo.resultDetails;
19128         gameInfo.resultDetails = NULL;
19129         savedFirst[storedGames] = firstMove;
19130         savedLast [storedGames] = lastMove;
19131         savedFramePtr[storedGames] = framePtr;
19132         framePtr -= nrMoves; // reserve space for the boards
19133         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19134             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19135             for(j=0; j<MOVE_LEN; j++)
19136                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19137             for(j=0; j<2*MOVE_LEN; j++)
19138                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19139             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19140             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19141             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19142             pvInfoList[firstMove+i-1].depth = 0;
19143             commentList[framePtr+i] = commentList[firstMove+i];
19144             commentList[firstMove+i] = NULL;
19145         }
19146
19147         storedGames++;
19148         forwardMostMove = firstMove; // truncate game so we can start variation
19149 }
19150
19151 void
19152 PushTail (int firstMove, int lastMove)
19153 {
19154         if(appData.icsActive) { // only in local mode
19155                 forwardMostMove = currentMove; // mimic old ICS behavior
19156                 return;
19157         }
19158         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19159
19160         PushInner(firstMove, lastMove);
19161         if(storedGames == 1) GreyRevert(FALSE);
19162         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19163 }
19164
19165 void
19166 PopInner (Boolean annotate)
19167 {
19168         int i, j, nrMoves;
19169         char buf[8000], moveBuf[20];
19170
19171         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19172         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19173         nrMoves = savedLast[storedGames] - currentMove;
19174         if(annotate) {
19175                 int cnt = 10;
19176                 if(!WhiteOnMove(currentMove))
19177                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19178                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19179                 for(i=currentMove; i<forwardMostMove; i++) {
19180                         if(WhiteOnMove(i))
19181                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19182                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19183                         strcat(buf, moveBuf);
19184                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19185                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19186                 }
19187                 strcat(buf, ")");
19188         }
19189         for(i=1; i<=nrMoves; i++) { // copy last variation back
19190             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19191             for(j=0; j<MOVE_LEN; j++)
19192                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19193             for(j=0; j<2*MOVE_LEN; j++)
19194                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19195             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19196             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19197             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19198             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19199             commentList[currentMove+i] = commentList[framePtr+i];
19200             commentList[framePtr+i] = NULL;
19201         }
19202         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19203         framePtr = savedFramePtr[storedGames];
19204         gameInfo.result = savedResult[storedGames];
19205         if(gameInfo.resultDetails != NULL) {
19206             free(gameInfo.resultDetails);
19207       }
19208         gameInfo.resultDetails = savedDetails[storedGames];
19209         forwardMostMove = currentMove + nrMoves;
19210 }
19211
19212 Boolean
19213 PopTail (Boolean annotate)
19214 {
19215         if(appData.icsActive) return FALSE; // only in local mode
19216         if(!storedGames) return FALSE; // sanity
19217         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19218
19219         PopInner(annotate);
19220         if(currentMove < forwardMostMove) ForwardEvent(); else
19221         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19222
19223         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19224         return TRUE;
19225 }
19226
19227 void
19228 CleanupTail ()
19229 {       // remove all shelved variations
19230         int i;
19231         for(i=0; i<storedGames; i++) {
19232             if(savedDetails[i])
19233                 free(savedDetails[i]);
19234             savedDetails[i] = NULL;
19235         }
19236         for(i=framePtr; i<MAX_MOVES; i++) {
19237                 if(commentList[i]) free(commentList[i]);
19238                 commentList[i] = NULL;
19239         }
19240         framePtr = MAX_MOVES-1;
19241         storedGames = 0;
19242 }
19243
19244 void
19245 LoadVariation (int index, char *text)
19246 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19247         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19248         int level = 0, move;
19249
19250         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19251         // first find outermost bracketing variation
19252         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19253             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19254                 if(*p == '{') wait = '}'; else
19255                 if(*p == '[') wait = ']'; else
19256                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19257                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19258             }
19259             if(*p == wait) wait = NULLCHAR; // closing ]} found
19260             p++;
19261         }
19262         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19263         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19264         end[1] = NULLCHAR; // clip off comment beyond variation
19265         ToNrEvent(currentMove-1);
19266         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19267         // kludge: use ParsePV() to append variation to game
19268         move = currentMove;
19269         ParsePV(start, TRUE, TRUE);
19270         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19271         ClearPremoveHighlights();
19272         CommentPopDown();
19273         ToNrEvent(currentMove+1);
19274 }
19275
19276 int transparency[2];
19277
19278 void
19279 LoadTheme ()
19280 {
19281 #define BUF_SIZ (2*MSG_SIZ)
19282     char *p, *q, buf[BUF_SIZ];
19283     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19284         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19285         ParseArgsFromString(buf);
19286         ActivateTheme(TRUE); // also redo colors
19287         return;
19288     }
19289     p = nickName;
19290     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19291     {
19292         int len;
19293         q = appData.themeNames;
19294         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19295       if(appData.useBitmaps) {
19296         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19297                 Shorten(appData.liteBackTextureFile));
19298         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19299                 Shorten(appData.darkBackTextureFile),
19300                 appData.liteBackTextureMode,
19301                 appData.darkBackTextureMode );
19302       } else {
19303         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19304       }
19305       if(!appData.useBitmaps || transparency[0]) {
19306         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19307       }
19308       if(!appData.useBitmaps || transparency[1]) {
19309         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19310       }
19311       if(appData.useBorder) {
19312         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19313                 appData.border);
19314       } else {
19315         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19316       }
19317       if(appData.useFont) {
19318         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19319                 appData.renderPiecesWithFont,
19320                 appData.fontToPieceTable,
19321                 Col2Text(9),    // appData.fontBackColorWhite
19322                 Col2Text(10) ); // appData.fontForeColorBlack
19323       } else {
19324         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19325         if(appData.pieceDirectory[0]) {
19326           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19327           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19328             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19329         }
19330         if(!appData.pieceDirectory[0] || !appData.trueColors)
19331           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19332                 Col2Text(0),   // whitePieceColor
19333                 Col2Text(1) ); // blackPieceColor
19334       }
19335       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19336                 Col2Text(4),   // highlightSquareColor
19337                 Col2Text(5) ); // premoveHighlightColor
19338         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19339         if(insert != q) insert[-1] = NULLCHAR;
19340         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19341         if(q)   free(q);
19342     }
19343     ActivateTheme(FALSE);
19344 }