Allow pieces with dressed-letter ID as promotion choice
[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 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
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
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
447
448 ChessProgramState first, second, pairing;
449
450 /* premove variables */
451 int premoveToX = 0;
452 int premoveToY = 0;
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
456 int gotPremove = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
459
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
462
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
490
491 int have_sent_ICS_logon = 0;
492 int movesPerSession;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
504
505 /* animateTraining preserves the state of appData.animate
506  * when Training mode is activated. This allows the
507  * response to be animated when appData.animate == TRUE and
508  * appData.animateDragging == TRUE.
509  */
510 Boolean animateTraining;
511
512 GameInfo gameInfo;
513
514 AppData appData;
515
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int   initialRulePlies, FENrulePlies;
522 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 int loadFlag = 0;
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
526
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int storedGames = 0;
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
536
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
542
543 ChessSquare  FIDEArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547         BlackKing, BlackBishop, BlackKnight, BlackRook }
548 };
549
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554         BlackKing, BlackKing, BlackKnight, BlackRook }
555 };
556
557 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560     { BlackRook, BlackMan, BlackBishop, BlackQueen,
561         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
562 };
563
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
569 };
570
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
576 };
577
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackMan, BlackFerz,
589         BlackKing, BlackMan, BlackKnight, BlackRook }
590 };
591
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackMan, BlackFerz,
596         BlackKing, BlackMan, BlackKnight, BlackRook }
597 };
598
599 ChessSquare  lionArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackLion, BlackBishop, BlackQueen,
603         BlackKing, BlackBishop, BlackKnight, BlackRook }
604 };
605
606
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
613 };
614
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
620 };
621
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
627 };
628
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
634 };
635
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
641 };
642
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
648 };
649
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 };
656
657 #ifdef GOTHIC
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !GOTHIC
665 #define GothicArray CapablancaArray
666 #endif // !GOTHIC
667
668 #ifdef FALCON
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
674 };
675 #else // !FALCON
676 #define FalconArray CapablancaArray
677 #endif // !FALCON
678
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
685
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 };
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 };
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
711
712
713 Board initialPosition;
714
715
716 /* Convert str to a rating. Checks for special cases of "----",
717
718    "++++", etc. Also strips ()'s */
719 int
720 string_to_rating (char *str)
721 {
722   while(*str && !isdigit(*str)) ++str;
723   if (!*str)
724     return 0;   /* One of the special "no rating" cases */
725   else
726     return atoi(str);
727 }
728
729 void
730 ClearProgramStats ()
731 {
732     /* Init programStats */
733     programStats.movelist[0] = 0;
734     programStats.depth = 0;
735     programStats.nr_moves = 0;
736     programStats.moves_left = 0;
737     programStats.nodes = 0;
738     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
739     programStats.score = 0;
740     programStats.got_only_move = 0;
741     programStats.got_fail = 0;
742     programStats.line_is_book = 0;
743 }
744
745 void
746 CommonEngineInit ()
747 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755
756     first.other = &second;
757     second.other = &first;
758
759     { float norm = 1;
760         if(appData.timeOddsMode) {
761             norm = appData.timeOdds[0];
762             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763         }
764         first.timeOdds  = appData.timeOdds[0]/norm;
765         second.timeOdds = appData.timeOdds[1]/norm;
766     }
767
768     if(programVersion) free(programVersion);
769     if (appData.noChessProgram) {
770         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771         sprintf(programVersion, "%s", PACKAGE_STRING);
772     } else {
773       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
776     }
777 }
778
779 void
780 UnloadEngine (ChessProgramState *cps)
781 {
782         /* Kill off first chess program */
783         if (cps->isr != NULL)
784           RemoveInputSource(cps->isr);
785         cps->isr = NULL;
786
787         if (cps->pr != NoProc) {
788             ExitAnalyzeMode();
789             DoSleep( appData.delayBeforeQuit );
790             SendToProgram("quit\n", cps);
791             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
792         }
793         cps->pr = NoProc;
794         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 }
796
797 void
798 ClearOptions (ChessProgramState *cps)
799 {
800     int i;
801     cps->nrOptions = cps->comboCnt = 0;
802     for(i=0; i<MAX_OPTIONS; i++) {
803         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804         cps->option[i].textValue = 0;
805     }
806 }
807
808 char *engineNames[] = {
809   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("first"),
812   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
814 N_("second")
815 };
816
817 void
818 InitEngine (ChessProgramState *cps, int n)
819 {   // [HGM] all engine initialiation put in a function that does one engine
820
821     ClearOptions(cps);
822
823     cps->which = engineNames[n];
824     cps->maybeThinking = FALSE;
825     cps->pr = NoProc;
826     cps->isr = NULL;
827     cps->sendTime = 2;
828     cps->sendDrawOffers = 1;
829
830     cps->program = appData.chessProgram[n];
831     cps->host = appData.host[n];
832     cps->dir = appData.directory[n];
833     cps->initString = appData.engInitString[n];
834     cps->computerString = appData.computerString[n];
835     cps->useSigint  = TRUE;
836     cps->useSigterm = TRUE;
837     cps->reuse = appData.reuse[n];
838     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
839     cps->useSetboard = FALSE;
840     cps->useSAN = FALSE;
841     cps->usePing = FALSE;
842     cps->lastPing = 0;
843     cps->lastPong = 0;
844     cps->usePlayother = FALSE;
845     cps->useColors = TRUE;
846     cps->useUsermove = FALSE;
847     cps->sendICS = FALSE;
848     cps->sendName = appData.icsActive;
849     cps->sdKludge = FALSE;
850     cps->stKludge = FALSE;
851     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852     TidyProgramName(cps->program, cps->host, cps->tidy);
853     cps->matchWins = 0;
854     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855     cps->analysisSupport = 2; /* detect */
856     cps->analyzing = FALSE;
857     cps->initDone = FALSE;
858     cps->reload = FALSE;
859     cps->pseudo = appData.pseudo[n];
860
861     /* New features added by Tord: */
862     cps->useFEN960 = FALSE;
863     cps->useOOCastle = TRUE;
864     /* End of new features added by Tord. */
865     cps->fenOverride  = appData.fenOverride[n];
866
867     /* [HGM] time odds: set factor for each machine */
868     cps->timeOdds  = appData.timeOdds[n];
869
870     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871     cps->accumulateTC = appData.accumulateTC[n];
872     cps->maxNrOfSessions = 1;
873
874     /* [HGM] debug */
875     cps->debug = FALSE;
876
877     cps->drawDepth = appData.drawDepth[n];
878     cps->supportsNPS = UNKNOWN;
879     cps->memSize = FALSE;
880     cps->maxCores = FALSE;
881     ASSIGN(cps->egtFormats, "");
882
883     /* [HGM] options */
884     cps->optionSettings  = appData.engOptions[n];
885
886     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887     cps->isUCI = appData.isUCI[n]; /* [AS] */
888     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
889     cps->highlight = 0;
890
891     if (appData.protocolVersion[n] > PROTOVER
892         || appData.protocolVersion[n] < 1)
893       {
894         char buf[MSG_SIZ];
895         int len;
896
897         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898                        appData.protocolVersion[n]);
899         if( (len >= MSG_SIZ) && appData.debugMode )
900           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901
902         DisplayFatalError(buf, 0, 2);
903       }
904     else
905       {
906         cps->protocolVersion = appData.protocolVersion[n];
907       }
908
909     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
910     ParseFeatures(appData.featureDefaults, cps);
911 }
912
913 ChessProgramState *savCps;
914
915 GameMode oldMode;
916
917 void
918 LoadEngine ()
919 {
920     int i;
921     if(WaitForEngine(savCps, LoadEngine)) return;
922     CommonEngineInit(); // recalculate time odds
923     if(gameInfo.variant != StringToVariant(appData.variant)) {
924         // we changed variant when loading the engine; this forces us to reset
925         Reset(TRUE, savCps != &first);
926         oldMode = BeginningOfGame; // to prevent restoring old mode
927     }
928     InitChessProgram(savCps, FALSE);
929     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930     DisplayMessage("", "");
931     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
933     ThawUI();
934     SetGNUMode();
935     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 }
937
938 void
939 ReplaceEngine (ChessProgramState *cps, int n)
940 {
941     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942     keepInfo = 1;
943     if(oldMode != BeginningOfGame) EditGameEvent();
944     keepInfo = 0;
945     UnloadEngine(cps);
946     appData.noChessProgram = FALSE;
947     appData.clockMode = TRUE;
948     InitEngine(cps, n);
949     UpdateLogos(TRUE);
950     if(n) return; // only startup first engine immediately; second can wait
951     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952     LoadEngine();
953 }
954
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957
958 static char resetOptions[] =
959         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
963
964 void
965 FloatToFront(char **list, char *engineLine)
966 {
967     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968     int i=0;
969     if(appData.recentEngines <= 0) return;
970     TidyProgramName(engineLine, "localhost", tidy+1);
971     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972     strncpy(buf+1, *list, MSG_SIZ-50);
973     if(p = strstr(buf, tidy)) { // tidy name appears in list
974         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975         while(*p++ = *++q); // squeeze out
976     }
977     strcat(tidy, buf+1); // put list behind tidy name
978     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980     ASSIGN(*list, tidy+1);
981 }
982
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
984
985 void
986 Load (ChessProgramState *cps, int i)
987 {
988     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994         appData.firstProtocolVersion = PROTOVER;
995         ParseArgsFromString(buf);
996         SwapEngines(i);
997         ReplaceEngine(cps, i);
998         FloatToFront(&appData.recentEngineList, engineLine);
999         return;
1000     }
1001     p = engineName;
1002     while(q = strchr(p, SLASH)) p = q+1;
1003     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1004     if(engineDir[0] != NULLCHAR) {
1005         ASSIGN(appData.directory[i], engineDir); p = engineName;
1006     } else if(p != engineName) { // derive directory from engine path, when not given
1007         p[-1] = 0;
1008         ASSIGN(appData.directory[i], engineName);
1009         p[-1] = SLASH;
1010         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1011     } else { ASSIGN(appData.directory[i], "."); }
1012     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1013     if(params[0]) {
1014         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1015         snprintf(command, MSG_SIZ, "%s %s", p, params);
1016         p = command;
1017     }
1018     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1019     ASSIGN(appData.chessProgram[i], p);
1020     appData.isUCI[i] = isUCI;
1021     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1022     appData.hasOwnBookUCI[i] = hasBook;
1023     if(!nickName[0]) useNick = FALSE;
1024     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1025     if(addToList) {
1026         int len;
1027         char quote;
1028         q = firstChessProgramNames;
1029         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1030         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1031         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1032                         quote, p, quote, appData.directory[i],
1033                         useNick ? " -fn \"" : "",
1034                         useNick ? nickName : "",
1035                         useNick ? "\"" : "",
1036                         v1 ? " -firstProtocolVersion 1" : "",
1037                         hasBook ? "" : " -fNoOwnBookUCI",
1038                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1039                         storeVariant ? " -variant " : "",
1040                         storeVariant ? VariantName(gameInfo.variant) : "");
1041         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1042         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1043         if(insert != q) insert[-1] = NULLCHAR;
1044         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1045         if(q)   free(q);
1046         FloatToFront(&appData.recentEngineList, buf);
1047     }
1048     ReplaceEngine(cps, i);
1049 }
1050
1051 void
1052 InitTimeControls ()
1053 {
1054     int matched, min, sec;
1055     /*
1056      * Parse timeControl resource
1057      */
1058     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1059                           appData.movesPerSession)) {
1060         char buf[MSG_SIZ];
1061         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1062         DisplayFatalError(buf, 0, 2);
1063     }
1064
1065     /*
1066      * Parse searchTime resource
1067      */
1068     if (*appData.searchTime != NULLCHAR) {
1069         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1070         if (matched == 1) {
1071             searchTime = min * 60;
1072         } else if (matched == 2) {
1073             searchTime = min * 60 + sec;
1074         } else {
1075             char buf[MSG_SIZ];
1076             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1077             DisplayFatalError(buf, 0, 2);
1078         }
1079     }
1080 }
1081
1082 void
1083 InitBackEnd1 ()
1084 {
1085
1086     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1087     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1088
1089     GetTimeMark(&programStartTime);
1090     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1091     appData.seedBase = random() + (random()<<15);
1092     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1093
1094     ClearProgramStats();
1095     programStats.ok_to_send = 1;
1096     programStats.seen_stat = 0;
1097
1098     /*
1099      * Initialize game list
1100      */
1101     ListNew(&gameList);
1102
1103
1104     /*
1105      * Internet chess server status
1106      */
1107     if (appData.icsActive) {
1108         appData.matchMode = FALSE;
1109         appData.matchGames = 0;
1110 #if ZIPPY
1111         appData.noChessProgram = !appData.zippyPlay;
1112 #else
1113         appData.zippyPlay = FALSE;
1114         appData.zippyTalk = FALSE;
1115         appData.noChessProgram = TRUE;
1116 #endif
1117         if (*appData.icsHelper != NULLCHAR) {
1118             appData.useTelnet = TRUE;
1119             appData.telnetProgram = appData.icsHelper;
1120         }
1121     } else {
1122         appData.zippyTalk = appData.zippyPlay = FALSE;
1123     }
1124
1125     /* [AS] Initialize pv info list [HGM] and game state */
1126     {
1127         int i, j;
1128
1129         for( i=0; i<=framePtr; i++ ) {
1130             pvInfoList[i].depth = -1;
1131             boards[i][EP_STATUS] = EP_NONE;
1132             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1133         }
1134     }
1135
1136     InitTimeControls();
1137
1138     /* [AS] Adjudication threshold */
1139     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1140
1141     InitEngine(&first, 0);
1142     InitEngine(&second, 1);
1143     CommonEngineInit();
1144
1145     pairing.which = "pairing"; // pairing engine
1146     pairing.pr = NoProc;
1147     pairing.isr = NULL;
1148     pairing.program = appData.pairingEngine;
1149     pairing.host = "localhost";
1150     pairing.dir = ".";
1151
1152     if (appData.icsActive) {
1153         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1154     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1155         appData.clockMode = FALSE;
1156         first.sendTime = second.sendTime = 0;
1157     }
1158
1159 #if ZIPPY
1160     /* Override some settings from environment variables, for backward
1161        compatibility.  Unfortunately it's not feasible to have the env
1162        vars just set defaults, at least in xboard.  Ugh.
1163     */
1164     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1165       ZippyInit();
1166     }
1167 #endif
1168
1169     if (!appData.icsActive) {
1170       char buf[MSG_SIZ];
1171       int len;
1172
1173       /* Check for variants that are supported only in ICS mode,
1174          or not at all.  Some that are accepted here nevertheless
1175          have bugs; see comments below.
1176       */
1177       VariantClass variant = StringToVariant(appData.variant);
1178       switch (variant) {
1179       case VariantBughouse:     /* need four players and two boards */
1180       case VariantKriegspiel:   /* need to hide pieces and move details */
1181         /* case VariantFischeRandom: (Fabien: moved below) */
1182         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1183         if( (len >= MSG_SIZ) && appData.debugMode )
1184           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1185
1186         DisplayFatalError(buf, 0, 2);
1187         return;
1188
1189       case VariantUnknown:
1190       case VariantLoadable:
1191       case Variant29:
1192       case Variant30:
1193       case Variant31:
1194       case Variant32:
1195       case Variant33:
1196       case Variant34:
1197       case Variant35:
1198       case Variant36:
1199       default:
1200         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1201         if( (len >= MSG_SIZ) && appData.debugMode )
1202           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1203
1204         DisplayFatalError(buf, 0, 2);
1205         return;
1206
1207       case VariantNormal:     /* definitely works! */
1208         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1209           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1210           return;
1211         }
1212       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1213       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1214       case VariantGothic:     /* [HGM] should work */
1215       case VariantCapablanca: /* [HGM] should work */
1216       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1217       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1218       case VariantChu:        /* [HGM] experimental */
1219       case VariantKnightmate: /* [HGM] should work */
1220       case VariantCylinder:   /* [HGM] untested */
1221       case VariantFalcon:     /* [HGM] untested */
1222       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1223                                  offboard interposition not understood */
1224       case VariantWildCastle: /* pieces not automatically shuffled */
1225       case VariantNoCastle:   /* pieces not automatically shuffled */
1226       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1227       case VariantLosers:     /* should work except for win condition,
1228                                  and doesn't know captures are mandatory */
1229       case VariantSuicide:    /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantGiveaway:   /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantTwoKings:   /* should work */
1234       case VariantAtomic:     /* should work except for win condition */
1235       case Variant3Check:     /* should work except for win condition */
1236       case VariantShatranj:   /* should work except for all win conditions */
1237       case VariantMakruk:     /* should work except for draw countdown */
1238       case VariantASEAN :     /* should work except for draw countdown */
1239       case VariantBerolina:   /* might work if TestLegality is off */
1240       case VariantCapaRandom: /* should work */
1241       case VariantJanus:      /* should work */
1242       case VariantSuper:      /* experimental */
1243       case VariantGreat:      /* experimental, requires legality testing to be off */
1244       case VariantSChess:     /* S-Chess, should work */
1245       case VariantGrand:      /* should work */
1246       case VariantSpartan:    /* should work */
1247       case VariantLion:       /* should work */
1248       case VariantChuChess:   /* should work */
1249         break;
1250       }
1251     }
1252
1253 }
1254
1255 int
1256 NextIntegerFromString (char ** str, long * value)
1257 {
1258     int result = -1;
1259     char * s = *str;
1260
1261     while( *s == ' ' || *s == '\t' ) {
1262         s++;
1263     }
1264
1265     *value = 0;
1266
1267     if( *s >= '0' && *s <= '9' ) {
1268         while( *s >= '0' && *s <= '9' ) {
1269             *value = *value * 10 + (*s - '0');
1270             s++;
1271         }
1272
1273         result = 0;
1274     }
1275
1276     *str = s;
1277
1278     return result;
1279 }
1280
1281 int
1282 NextTimeControlFromString (char ** str, long * value)
1283 {
1284     long temp;
1285     int result = NextIntegerFromString( str, &temp );
1286
1287     if( result == 0 ) {
1288         *value = temp * 60; /* Minutes */
1289         if( **str == ':' ) {
1290             (*str)++;
1291             result = NextIntegerFromString( str, &temp );
1292             *value += temp; /* Seconds */
1293         }
1294     }
1295
1296     return result;
1297 }
1298
1299 int
1300 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1301 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1302     int result = -1, type = 0; long temp, temp2;
1303
1304     if(**str != ':') return -1; // old params remain in force!
1305     (*str)++;
1306     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1307     if( NextIntegerFromString( str, &temp ) ) return -1;
1308     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1309
1310     if(**str != '/') {
1311         /* time only: incremental or sudden-death time control */
1312         if(**str == '+') { /* increment follows; read it */
1313             (*str)++;
1314             if(**str == '!') type = *(*str)++; // Bronstein TC
1315             if(result = NextIntegerFromString( str, &temp2)) return -1;
1316             *inc = temp2 * 1000;
1317             if(**str == '.') { // read fraction of increment
1318                 char *start = ++(*str);
1319                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320                 temp2 *= 1000;
1321                 while(start++ < *str) temp2 /= 10;
1322                 *inc += temp2;
1323             }
1324         } else *inc = 0;
1325         *moves = 0; *tc = temp * 1000; *incType = type;
1326         return 0;
1327     }
1328
1329     (*str)++; /* classical time control */
1330     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1331
1332     if(result == 0) {
1333         *moves = temp;
1334         *tc    = temp2 * 1000;
1335         *inc   = 0;
1336         *incType = type;
1337     }
1338     return result;
1339 }
1340
1341 int
1342 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1343 {   /* [HGM] get time to add from the multi-session time-control string */
1344     int incType, moves=1; /* kludge to force reading of first session */
1345     long time, increment;
1346     char *s = tcString;
1347
1348     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1349     do {
1350         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1351         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1352         if(movenr == -1) return time;    /* last move before new session     */
1353         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1354         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1355         if(!moves) return increment;     /* current session is incremental   */
1356         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1357     } while(movenr >= -1);               /* try again for next session       */
1358
1359     return 0; // no new time quota on this move
1360 }
1361
1362 int
1363 ParseTimeControl (char *tc, float ti, int mps)
1364 {
1365   long tc1;
1366   long tc2;
1367   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1368   int min, sec=0;
1369
1370   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1371   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1372       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1373   if(ti > 0) {
1374
1375     if(mps)
1376       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1377     else
1378       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1379   } else {
1380     if(mps)
1381       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1382     else
1383       snprintf(buf, MSG_SIZ, ":%s", mytc);
1384   }
1385   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1386
1387   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1388     return FALSE;
1389   }
1390
1391   if( *tc == '/' ) {
1392     /* Parse second time control */
1393     tc++;
1394
1395     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1396       return FALSE;
1397     }
1398
1399     if( tc2 == 0 ) {
1400       return FALSE;
1401     }
1402
1403     timeControl_2 = tc2 * 1000;
1404   }
1405   else {
1406     timeControl_2 = 0;
1407   }
1408
1409   if( tc1 == 0 ) {
1410     return FALSE;
1411   }
1412
1413   timeControl = tc1 * 1000;
1414
1415   if (ti >= 0) {
1416     timeIncrement = ti * 1000;  /* convert to ms */
1417     movesPerSession = 0;
1418   } else {
1419     timeIncrement = 0;
1420     movesPerSession = mps;
1421   }
1422   return TRUE;
1423 }
1424
1425 void
1426 InitBackEnd2 ()
1427 {
1428     if (appData.debugMode) {
1429 #    ifdef __GIT_VERSION
1430       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1431 #    else
1432       fprintf(debugFP, "Version: %s\n", programVersion);
1433 #    endif
1434     }
1435     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1436
1437     set_cont_sequence(appData.wrapContSeq);
1438     if (appData.matchGames > 0) {
1439         appData.matchMode = TRUE;
1440     } else if (appData.matchMode) {
1441         appData.matchGames = 1;
1442     }
1443     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1444         appData.matchGames = appData.sameColorGames;
1445     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1446         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1447         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1448     }
1449     Reset(TRUE, FALSE);
1450     if (appData.noChessProgram || first.protocolVersion == 1) {
1451       InitBackEnd3();
1452     } else {
1453       /* kludge: allow timeout for initial "feature" commands */
1454       FreezeUI();
1455       DisplayMessage("", _("Starting chess program"));
1456       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1457     }
1458 }
1459
1460 int
1461 CalculateIndex (int index, int gameNr)
1462 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1463     int res;
1464     if(index > 0) return index; // fixed nmber
1465     if(index == 0) return 1;
1466     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1467     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1468     return res;
1469 }
1470
1471 int
1472 LoadGameOrPosition (int gameNr)
1473 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1474     if (*appData.loadGameFile != NULLCHAR) {
1475         if (!LoadGameFromFile(appData.loadGameFile,
1476                 CalculateIndex(appData.loadGameIndex, gameNr),
1477                               appData.loadGameFile, FALSE)) {
1478             DisplayFatalError(_("Bad game file"), 0, 1);
1479             return 0;
1480         }
1481     } else if (*appData.loadPositionFile != NULLCHAR) {
1482         if (!LoadPositionFromFile(appData.loadPositionFile,
1483                 CalculateIndex(appData.loadPositionIndex, gameNr),
1484                                   appData.loadPositionFile)) {
1485             DisplayFatalError(_("Bad position file"), 0, 1);
1486             return 0;
1487         }
1488     }
1489     return 1;
1490 }
1491
1492 void
1493 ReserveGame (int gameNr, char resChar)
1494 {
1495     FILE *tf = fopen(appData.tourneyFile, "r+");
1496     char *p, *q, c, buf[MSG_SIZ];
1497     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1498     safeStrCpy(buf, lastMsg, MSG_SIZ);
1499     DisplayMessage(_("Pick new game"), "");
1500     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1501     ParseArgsFromFile(tf);
1502     p = q = appData.results;
1503     if(appData.debugMode) {
1504       char *r = appData.participants;
1505       fprintf(debugFP, "results = '%s'\n", p);
1506       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1507       fprintf(debugFP, "\n");
1508     }
1509     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1510     nextGame = q - p;
1511     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1512     safeStrCpy(q, p, strlen(p) + 2);
1513     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1514     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1515     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1516         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1517         q[nextGame] = '*';
1518     }
1519     fseek(tf, -(strlen(p)+4), SEEK_END);
1520     c = fgetc(tf);
1521     if(c != '"') // depending on DOS or Unix line endings we can be one off
1522          fseek(tf, -(strlen(p)+2), SEEK_END);
1523     else fseek(tf, -(strlen(p)+3), SEEK_END);
1524     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1525     DisplayMessage(buf, "");
1526     free(p); appData.results = q;
1527     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1528        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1529       int round = appData.defaultMatchGames * appData.tourneyType;
1530       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1531          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1532         UnloadEngine(&first);  // next game belongs to other pairing;
1533         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1534     }
1535     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1536 }
1537
1538 void
1539 MatchEvent (int mode)
1540 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1541         int dummy;
1542         if(matchMode) { // already in match mode: switch it off
1543             abortMatch = TRUE;
1544             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1545             return;
1546         }
1547 //      if(gameMode != BeginningOfGame) {
1548 //          DisplayError(_("You can only start a match from the initial position."), 0);
1549 //          return;
1550 //      }
1551         abortMatch = FALSE;
1552         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1553         /* Set up machine vs. machine match */
1554         nextGame = 0;
1555         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1556         if(appData.tourneyFile[0]) {
1557             ReserveGame(-1, 0);
1558             if(nextGame > appData.matchGames) {
1559                 char buf[MSG_SIZ];
1560                 if(strchr(appData.results, '*') == NULL) {
1561                     FILE *f;
1562                     appData.tourneyCycles++;
1563                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1564                         fclose(f);
1565                         NextTourneyGame(-1, &dummy);
1566                         ReserveGame(-1, 0);
1567                         if(nextGame <= appData.matchGames) {
1568                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1569                             matchMode = mode;
1570                             ScheduleDelayedEvent(NextMatchGame, 10000);
1571                             return;
1572                         }
1573                     }
1574                 }
1575                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1576                 DisplayError(buf, 0);
1577                 appData.tourneyFile[0] = 0;
1578                 return;
1579             }
1580         } else
1581         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1582             DisplayFatalError(_("Can't have a match with no chess programs"),
1583                               0, 2);
1584             return;
1585         }
1586         matchMode = mode;
1587         matchGame = roundNr = 1;
1588         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1589         NextMatchGame();
1590 }
1591
1592 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1593
1594 void
1595 InitBackEnd3 P((void))
1596 {
1597     GameMode initialMode;
1598     char buf[MSG_SIZ];
1599     int err, len;
1600
1601     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1602        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1603         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1604        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1605        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1606         char c, *q = first.variants, *p = strchr(q, ',');
1607         if(p) *p = NULLCHAR;
1608         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1609             int w, h, s;
1610             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1611                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1612             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1613             Reset(TRUE, FALSE);         // and re-initialize
1614         }
1615         if(p) *p = ',';
1616     }
1617
1618     InitChessProgram(&first, startedFromSetupPosition);
1619
1620     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1621         free(programVersion);
1622         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1623         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1624         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1625     }
1626
1627     if (appData.icsActive) {
1628 #ifdef WIN32
1629         /* [DM] Make a console window if needed [HGM] merged ifs */
1630         ConsoleCreate();
1631 #endif
1632         err = establish();
1633         if (err != 0)
1634           {
1635             if (*appData.icsCommPort != NULLCHAR)
1636               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1637                              appData.icsCommPort);
1638             else
1639               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1640                         appData.icsHost, appData.icsPort);
1641
1642             if( (len >= MSG_SIZ) && appData.debugMode )
1643               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1644
1645             DisplayFatalError(buf, err, 1);
1646             return;
1647         }
1648         SetICSMode();
1649         telnetISR =
1650           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1651         fromUserISR =
1652           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1653         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1654             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1655     } else if (appData.noChessProgram) {
1656         SetNCPMode();
1657     } else {
1658         SetGNUMode();
1659     }
1660
1661     if (*appData.cmailGameName != NULLCHAR) {
1662         SetCmailMode();
1663         OpenLoopback(&cmailPR);
1664         cmailISR =
1665           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1666     }
1667
1668     ThawUI();
1669     DisplayMessage("", "");
1670     if (StrCaseCmp(appData.initialMode, "") == 0) {
1671       initialMode = BeginningOfGame;
1672       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1673         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1674         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1675         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1676         ModeHighlight();
1677       }
1678     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1679       initialMode = TwoMachinesPlay;
1680     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1681       initialMode = AnalyzeFile;
1682     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1683       initialMode = AnalyzeMode;
1684     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1685       initialMode = MachinePlaysWhite;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1687       initialMode = MachinePlaysBlack;
1688     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1689       initialMode = EditGame;
1690     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1691       initialMode = EditPosition;
1692     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1693       initialMode = Training;
1694     } else {
1695       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1696       if( (len >= MSG_SIZ) && appData.debugMode )
1697         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1698
1699       DisplayFatalError(buf, 0, 2);
1700       return;
1701     }
1702
1703     if (appData.matchMode) {
1704         if(appData.tourneyFile[0]) { // start tourney from command line
1705             FILE *f;
1706             if(f = fopen(appData.tourneyFile, "r")) {
1707                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1708                 fclose(f);
1709                 appData.clockMode = TRUE;
1710                 SetGNUMode();
1711             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1712         }
1713         MatchEvent(TRUE);
1714     } else if (*appData.cmailGameName != NULLCHAR) {
1715         /* Set up cmail mode */
1716         ReloadCmailMsgEvent(TRUE);
1717     } else {
1718         /* Set up other modes */
1719         if (initialMode == AnalyzeFile) {
1720           if (*appData.loadGameFile == NULLCHAR) {
1721             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1722             return;
1723           }
1724         }
1725         if (*appData.loadGameFile != NULLCHAR) {
1726             (void) LoadGameFromFile(appData.loadGameFile,
1727                                     appData.loadGameIndex,
1728                                     appData.loadGameFile, TRUE);
1729         } else if (*appData.loadPositionFile != NULLCHAR) {
1730             (void) LoadPositionFromFile(appData.loadPositionFile,
1731                                         appData.loadPositionIndex,
1732                                         appData.loadPositionFile);
1733             /* [HGM] try to make self-starting even after FEN load */
1734             /* to allow automatic setup of fairy variants with wtm */
1735             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1736                 gameMode = BeginningOfGame;
1737                 setboardSpoiledMachineBlack = 1;
1738             }
1739             /* [HGM] loadPos: make that every new game uses the setup */
1740             /* from file as long as we do not switch variant          */
1741             if(!blackPlaysFirst) {
1742                 startedFromPositionFile = TRUE;
1743                 CopyBoard(filePosition, boards[0]);
1744                 CopyBoard(initialPosition, boards[0]);
1745             }
1746         }
1747         if (initialMode == AnalyzeMode) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1750             return;
1751           }
1752           if (appData.icsActive) {
1753             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1754             return;
1755           }
1756           AnalyzeModeEvent();
1757         } else if (initialMode == AnalyzeFile) {
1758           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1759           ShowThinkingEvent();
1760           AnalyzeFileEvent();
1761           AnalysisPeriodicEvent(1);
1762         } else if (initialMode == MachinePlaysWhite) {
1763           if (appData.noChessProgram) {
1764             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1765                               0, 2);
1766             return;
1767           }
1768           if (appData.icsActive) {
1769             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1770                               0, 2);
1771             return;
1772           }
1773           MachineWhiteEvent();
1774         } else if (initialMode == MachinePlaysBlack) {
1775           if (appData.noChessProgram) {
1776             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1777                               0, 2);
1778             return;
1779           }
1780           if (appData.icsActive) {
1781             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1782                               0, 2);
1783             return;
1784           }
1785           MachineBlackEvent();
1786         } else if (initialMode == TwoMachinesPlay) {
1787           if (appData.noChessProgram) {
1788             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1789                               0, 2);
1790             return;
1791           }
1792           if (appData.icsActive) {
1793             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1794                               0, 2);
1795             return;
1796           }
1797           TwoMachinesEvent();
1798         } else if (initialMode == EditGame) {
1799           EditGameEvent();
1800         } else if (initialMode == EditPosition) {
1801           EditPositionEvent();
1802         } else if (initialMode == Training) {
1803           if (*appData.loadGameFile == NULLCHAR) {
1804             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1805             return;
1806           }
1807           TrainingEvent();
1808         }
1809     }
1810 }
1811
1812 void
1813 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1814 {
1815     DisplayBook(current+1);
1816
1817     MoveHistorySet( movelist, first, last, current, pvInfoList );
1818
1819     EvalGraphSet( first, last, current, pvInfoList );
1820
1821     MakeEngineOutputTitle();
1822 }
1823
1824 /*
1825  * Establish will establish a contact to a remote host.port.
1826  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1827  *  used to talk to the host.
1828  * Returns 0 if okay, error code if not.
1829  */
1830 int
1831 establish ()
1832 {
1833     char buf[MSG_SIZ];
1834
1835     if (*appData.icsCommPort != NULLCHAR) {
1836         /* Talk to the host through a serial comm port */
1837         return OpenCommPort(appData.icsCommPort, &icsPR);
1838
1839     } else if (*appData.gateway != NULLCHAR) {
1840         if (*appData.remoteShell == NULLCHAR) {
1841             /* Use the rcmd protocol to run telnet program on a gateway host */
1842             snprintf(buf, sizeof(buf), "%s %s %s",
1843                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1844             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1845
1846         } else {
1847             /* Use the rsh program to run telnet program on a gateway host */
1848             if (*appData.remoteUser == NULLCHAR) {
1849                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1850                         appData.gateway, appData.telnetProgram,
1851                         appData.icsHost, appData.icsPort);
1852             } else {
1853                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1854                         appData.remoteShell, appData.gateway,
1855                         appData.remoteUser, appData.telnetProgram,
1856                         appData.icsHost, appData.icsPort);
1857             }
1858             return StartChildProcess(buf, "", &icsPR);
1859
1860         }
1861     } else if (appData.useTelnet) {
1862         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1863
1864     } else {
1865         /* TCP socket interface differs somewhat between
1866            Unix and NT; handle details in the front end.
1867            */
1868         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1869     }
1870 }
1871
1872 void
1873 EscapeExpand (char *p, char *q)
1874 {       // [HGM] initstring: routine to shape up string arguments
1875         while(*p++ = *q++) if(p[-1] == '\\')
1876             switch(*q++) {
1877                 case 'n': p[-1] = '\n'; break;
1878                 case 'r': p[-1] = '\r'; break;
1879                 case 't': p[-1] = '\t'; break;
1880                 case '\\': p[-1] = '\\'; break;
1881                 case 0: *p = 0; return;
1882                 default: p[-1] = q[-1]; break;
1883             }
1884 }
1885
1886 void
1887 show_bytes (FILE *fp, char *buf, int count)
1888 {
1889     while (count--) {
1890         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1891             fprintf(fp, "\\%03o", *buf & 0xff);
1892         } else {
1893             putc(*buf, fp);
1894         }
1895         buf++;
1896     }
1897     fflush(fp);
1898 }
1899
1900 /* Returns an errno value */
1901 int
1902 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1903 {
1904     char buf[8192], *p, *q, *buflim;
1905     int left, newcount, outcount;
1906
1907     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1908         *appData.gateway != NULLCHAR) {
1909         if (appData.debugMode) {
1910             fprintf(debugFP, ">ICS: ");
1911             show_bytes(debugFP, message, count);
1912             fprintf(debugFP, "\n");
1913         }
1914         return OutputToProcess(pr, message, count, outError);
1915     }
1916
1917     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1918     p = message;
1919     q = buf;
1920     left = count;
1921     newcount = 0;
1922     while (left) {
1923         if (q >= buflim) {
1924             if (appData.debugMode) {
1925                 fprintf(debugFP, ">ICS: ");
1926                 show_bytes(debugFP, buf, newcount);
1927                 fprintf(debugFP, "\n");
1928             }
1929             outcount = OutputToProcess(pr, buf, newcount, outError);
1930             if (outcount < newcount) return -1; /* to be sure */
1931             q = buf;
1932             newcount = 0;
1933         }
1934         if (*p == '\n') {
1935             *q++ = '\r';
1936             newcount++;
1937         } else if (((unsigned char) *p) == TN_IAC) {
1938             *q++ = (char) TN_IAC;
1939             newcount ++;
1940         }
1941         *q++ = *p++;
1942         newcount++;
1943         left--;
1944     }
1945     if (appData.debugMode) {
1946         fprintf(debugFP, ">ICS: ");
1947         show_bytes(debugFP, buf, newcount);
1948         fprintf(debugFP, "\n");
1949     }
1950     outcount = OutputToProcess(pr, buf, newcount, outError);
1951     if (outcount < newcount) return -1; /* to be sure */
1952     return count;
1953 }
1954
1955 void
1956 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1957 {
1958     int outError, outCount;
1959     static int gotEof = 0;
1960     static FILE *ini;
1961
1962     /* Pass data read from player on to ICS */
1963     if (count > 0) {
1964         gotEof = 0;
1965         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1966         if (outCount < count) {
1967             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1968         }
1969         if(have_sent_ICS_logon == 2) {
1970           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1971             fprintf(ini, "%s", message);
1972             have_sent_ICS_logon = 3;
1973           } else
1974             have_sent_ICS_logon = 1;
1975         } else if(have_sent_ICS_logon == 3) {
1976             fprintf(ini, "%s", message);
1977             fclose(ini);
1978           have_sent_ICS_logon = 1;
1979         }
1980     } else if (count < 0) {
1981         RemoveInputSource(isr);
1982         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1983     } else if (gotEof++ > 0) {
1984         RemoveInputSource(isr);
1985         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1986     }
1987 }
1988
1989 void
1990 KeepAlive ()
1991 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1992     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1993     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1994     SendToICS("date\n");
1995     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1996 }
1997
1998 /* added routine for printf style output to ics */
1999 void
2000 ics_printf (char *format, ...)
2001 {
2002     char buffer[MSG_SIZ];
2003     va_list args;
2004
2005     va_start(args, format);
2006     vsnprintf(buffer, sizeof(buffer), format, args);
2007     buffer[sizeof(buffer)-1] = '\0';
2008     SendToICS(buffer);
2009     va_end(args);
2010 }
2011
2012 void
2013 SendToICS (char *s)
2014 {
2015     int count, outCount, outError;
2016
2017     if (icsPR == NoProc) return;
2018
2019     count = strlen(s);
2020     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2021     if (outCount < count) {
2022         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2023     }
2024 }
2025
2026 /* This is used for sending logon scripts to the ICS. Sending
2027    without a delay causes problems when using timestamp on ICC
2028    (at least on my machine). */
2029 void
2030 SendToICSDelayed (char *s, long msdelay)
2031 {
2032     int count, outCount, outError;
2033
2034     if (icsPR == NoProc) return;
2035
2036     count = strlen(s);
2037     if (appData.debugMode) {
2038         fprintf(debugFP, ">ICS: ");
2039         show_bytes(debugFP, s, count);
2040         fprintf(debugFP, "\n");
2041     }
2042     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2043                                       msdelay);
2044     if (outCount < count) {
2045         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2046     }
2047 }
2048
2049
2050 /* Remove all highlighting escape sequences in s
2051    Also deletes any suffix starting with '('
2052    */
2053 char *
2054 StripHighlightAndTitle (char *s)
2055 {
2056     static char retbuf[MSG_SIZ];
2057     char *p = retbuf;
2058
2059     while (*s != NULLCHAR) {
2060         while (*s == '\033') {
2061             while (*s != NULLCHAR && !isalpha(*s)) s++;
2062             if (*s != NULLCHAR) s++;
2063         }
2064         while (*s != NULLCHAR && *s != '\033') {
2065             if (*s == '(' || *s == '[') {
2066                 *p = NULLCHAR;
2067                 return retbuf;
2068             }
2069             *p++ = *s++;
2070         }
2071     }
2072     *p = NULLCHAR;
2073     return retbuf;
2074 }
2075
2076 /* Remove all highlighting escape sequences in s */
2077 char *
2078 StripHighlight (char *s)
2079 {
2080     static char retbuf[MSG_SIZ];
2081     char *p = retbuf;
2082
2083     while (*s != NULLCHAR) {
2084         while (*s == '\033') {
2085             while (*s != NULLCHAR && !isalpha(*s)) s++;
2086             if (*s != NULLCHAR) s++;
2087         }
2088         while (*s != NULLCHAR && *s != '\033') {
2089             *p++ = *s++;
2090         }
2091     }
2092     *p = NULLCHAR;
2093     return retbuf;
2094 }
2095
2096 char engineVariant[MSG_SIZ];
2097 char *variantNames[] = VARIANT_NAMES;
2098 char *
2099 VariantName (VariantClass v)
2100 {
2101     if(v == VariantUnknown || *engineVariant) return engineVariant;
2102     return variantNames[v];
2103 }
2104
2105
2106 /* Identify a variant from the strings the chess servers use or the
2107    PGN Variant tag names we use. */
2108 VariantClass
2109 StringToVariant (char *e)
2110 {
2111     char *p;
2112     int wnum = -1;
2113     VariantClass v = VariantNormal;
2114     int i, found = FALSE;
2115     char buf[MSG_SIZ], c;
2116     int len;
2117
2118     if (!e) return v;
2119
2120     /* [HGM] skip over optional board-size prefixes */
2121     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2122         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2123         while( *e++ != '_');
2124     }
2125
2126     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2127         v = VariantNormal;
2128         found = TRUE;
2129     } else
2130     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2131       if (p = StrCaseStr(e, variantNames[i])) {
2132         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2133         v = (VariantClass) i;
2134         found = TRUE;
2135         break;
2136       }
2137     }
2138
2139     if (!found) {
2140       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2141           || StrCaseStr(e, "wild/fr")
2142           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2143         v = VariantFischeRandom;
2144       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2145                  (i = 1, p = StrCaseStr(e, "w"))) {
2146         p += i;
2147         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2148         if (isdigit(*p)) {
2149           wnum = atoi(p);
2150         } else {
2151           wnum = -1;
2152         }
2153         switch (wnum) {
2154         case 0: /* FICS only, actually */
2155         case 1:
2156           /* Castling legal even if K starts on d-file */
2157           v = VariantWildCastle;
2158           break;
2159         case 2:
2160         case 3:
2161         case 4:
2162           /* Castling illegal even if K & R happen to start in
2163              normal positions. */
2164           v = VariantNoCastle;
2165           break;
2166         case 5:
2167         case 7:
2168         case 8:
2169         case 10:
2170         case 11:
2171         case 12:
2172         case 13:
2173         case 14:
2174         case 15:
2175         case 18:
2176         case 19:
2177           /* Castling legal iff K & R start in normal positions */
2178           v = VariantNormal;
2179           break;
2180         case 6:
2181         case 20:
2182         case 21:
2183           /* Special wilds for position setup; unclear what to do here */
2184           v = VariantLoadable;
2185           break;
2186         case 9:
2187           /* Bizarre ICC game */
2188           v = VariantTwoKings;
2189           break;
2190         case 16:
2191           v = VariantKriegspiel;
2192           break;
2193         case 17:
2194           v = VariantLosers;
2195           break;
2196         case 22:
2197           v = VariantFischeRandom;
2198           break;
2199         case 23:
2200           v = VariantCrazyhouse;
2201           break;
2202         case 24:
2203           v = VariantBughouse;
2204           break;
2205         case 25:
2206           v = Variant3Check;
2207           break;
2208         case 26:
2209           /* Not quite the same as FICS suicide! */
2210           v = VariantGiveaway;
2211           break;
2212         case 27:
2213           v = VariantAtomic;
2214           break;
2215         case 28:
2216           v = VariantShatranj;
2217           break;
2218
2219         /* Temporary names for future ICC types.  The name *will* change in
2220            the next xboard/WinBoard release after ICC defines it. */
2221         case 29:
2222           v = Variant29;
2223           break;
2224         case 30:
2225           v = Variant30;
2226           break;
2227         case 31:
2228           v = Variant31;
2229           break;
2230         case 32:
2231           v = Variant32;
2232           break;
2233         case 33:
2234           v = Variant33;
2235           break;
2236         case 34:
2237           v = Variant34;
2238           break;
2239         case 35:
2240           v = Variant35;
2241           break;
2242         case 36:
2243           v = Variant36;
2244           break;
2245         case 37:
2246           v = VariantShogi;
2247           break;
2248         case 38:
2249           v = VariantXiangqi;
2250           break;
2251         case 39:
2252           v = VariantCourier;
2253           break;
2254         case 40:
2255           v = VariantGothic;
2256           break;
2257         case 41:
2258           v = VariantCapablanca;
2259           break;
2260         case 42:
2261           v = VariantKnightmate;
2262           break;
2263         case 43:
2264           v = VariantFairy;
2265           break;
2266         case 44:
2267           v = VariantCylinder;
2268           break;
2269         case 45:
2270           v = VariantFalcon;
2271           break;
2272         case 46:
2273           v = VariantCapaRandom;
2274           break;
2275         case 47:
2276           v = VariantBerolina;
2277           break;
2278         case 48:
2279           v = VariantJanus;
2280           break;
2281         case 49:
2282           v = VariantSuper;
2283           break;
2284         case 50:
2285           v = VariantGreat;
2286           break;
2287         case -1:
2288           /* Found "wild" or "w" in the string but no number;
2289              must assume it's normal chess. */
2290           v = VariantNormal;
2291           break;
2292         default:
2293           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2294           if( (len >= MSG_SIZ) && appData.debugMode )
2295             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2296
2297           DisplayError(buf, 0);
2298           v = VariantUnknown;
2299           break;
2300         }
2301       }
2302     }
2303     if (appData.debugMode) {
2304       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2305               e, wnum, VariantName(v));
2306     }
2307     return v;
2308 }
2309
2310 static int leftover_start = 0, leftover_len = 0;
2311 char star_match[STAR_MATCH_N][MSG_SIZ];
2312
2313 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2314    advance *index beyond it, and set leftover_start to the new value of
2315    *index; else return FALSE.  If pattern contains the character '*', it
2316    matches any sequence of characters not containing '\r', '\n', or the
2317    character following the '*' (if any), and the matched sequence(s) are
2318    copied into star_match.
2319    */
2320 int
2321 looking_at ( char *buf, int *index, char *pattern)
2322 {
2323     char *bufp = &buf[*index], *patternp = pattern;
2324     int star_count = 0;
2325     char *matchp = star_match[0];
2326
2327     for (;;) {
2328         if (*patternp == NULLCHAR) {
2329             *index = leftover_start = bufp - buf;
2330             *matchp = NULLCHAR;
2331             return TRUE;
2332         }
2333         if (*bufp == NULLCHAR) return FALSE;
2334         if (*patternp == '*') {
2335             if (*bufp == *(patternp + 1)) {
2336                 *matchp = NULLCHAR;
2337                 matchp = star_match[++star_count];
2338                 patternp += 2;
2339                 bufp++;
2340                 continue;
2341             } else if (*bufp == '\n' || *bufp == '\r') {
2342                 patternp++;
2343                 if (*patternp == NULLCHAR)
2344                   continue;
2345                 else
2346                   return FALSE;
2347             } else {
2348                 *matchp++ = *bufp++;
2349                 continue;
2350             }
2351         }
2352         if (*patternp != *bufp) return FALSE;
2353         patternp++;
2354         bufp++;
2355     }
2356 }
2357
2358 void
2359 SendToPlayer (char *data, int length)
2360 {
2361     int error, outCount;
2362     outCount = OutputToProcess(NoProc, data, length, &error);
2363     if (outCount < length) {
2364         DisplayFatalError(_("Error writing to display"), error, 1);
2365     }
2366 }
2367
2368 void
2369 PackHolding (char packed[], char *holding)
2370 {
2371     char *p = holding;
2372     char *q = packed;
2373     int runlength = 0;
2374     int curr = 9999;
2375     do {
2376         if (*p == curr) {
2377             runlength++;
2378         } else {
2379             switch (runlength) {
2380               case 0:
2381                 break;
2382               case 1:
2383                 *q++ = curr;
2384                 break;
2385               case 2:
2386                 *q++ = curr;
2387                 *q++ = curr;
2388                 break;
2389               default:
2390                 sprintf(q, "%d", runlength);
2391                 while (*q) q++;
2392                 *q++ = curr;
2393                 break;
2394             }
2395             runlength = 1;
2396             curr = *p;
2397         }
2398     } while (*p++);
2399     *q = NULLCHAR;
2400 }
2401
2402 /* Telnet protocol requests from the front end */
2403 void
2404 TelnetRequest (unsigned char ddww, unsigned char option)
2405 {
2406     unsigned char msg[3];
2407     int outCount, outError;
2408
2409     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2410
2411     if (appData.debugMode) {
2412         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2413         switch (ddww) {
2414           case TN_DO:
2415             ddwwStr = "DO";
2416             break;
2417           case TN_DONT:
2418             ddwwStr = "DONT";
2419             break;
2420           case TN_WILL:
2421             ddwwStr = "WILL";
2422             break;
2423           case TN_WONT:
2424             ddwwStr = "WONT";
2425             break;
2426           default:
2427             ddwwStr = buf1;
2428             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2429             break;
2430         }
2431         switch (option) {
2432           case TN_ECHO:
2433             optionStr = "ECHO";
2434             break;
2435           default:
2436             optionStr = buf2;
2437             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2438             break;
2439         }
2440         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2441     }
2442     msg[0] = TN_IAC;
2443     msg[1] = ddww;
2444     msg[2] = option;
2445     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2446     if (outCount < 3) {
2447         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2448     }
2449 }
2450
2451 void
2452 DoEcho ()
2453 {
2454     if (!appData.icsActive) return;
2455     TelnetRequest(TN_DO, TN_ECHO);
2456 }
2457
2458 void
2459 DontEcho ()
2460 {
2461     if (!appData.icsActive) return;
2462     TelnetRequest(TN_DONT, TN_ECHO);
2463 }
2464
2465 void
2466 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2467 {
2468     /* put the holdings sent to us by the server on the board holdings area */
2469     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2470     char p;
2471     ChessSquare piece;
2472
2473     if(gameInfo.holdingsWidth < 2)  return;
2474     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2475         return; // prevent overwriting by pre-board holdings
2476
2477     if( (int)lowestPiece >= BlackPawn ) {
2478         holdingsColumn = 0;
2479         countsColumn = 1;
2480         holdingsStartRow = BOARD_HEIGHT-1;
2481         direction = -1;
2482     } else {
2483         holdingsColumn = BOARD_WIDTH-1;
2484         countsColumn = BOARD_WIDTH-2;
2485         holdingsStartRow = 0;
2486         direction = 1;
2487     }
2488
2489     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2490         board[i][holdingsColumn] = EmptySquare;
2491         board[i][countsColumn]   = (ChessSquare) 0;
2492     }
2493     while( (p=*holdings++) != NULLCHAR ) {
2494         piece = CharToPiece( ToUpper(p) );
2495         if(piece == EmptySquare) continue;
2496         /*j = (int) piece - (int) WhitePawn;*/
2497         j = PieceToNumber(piece);
2498         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2499         if(j < 0) continue;               /* should not happen */
2500         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2501         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2502         board[holdingsStartRow+j*direction][countsColumn]++;
2503     }
2504 }
2505
2506
2507 void
2508 VariantSwitch (Board board, VariantClass newVariant)
2509 {
2510    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2511    static Board oldBoard;
2512
2513    startedFromPositionFile = FALSE;
2514    if(gameInfo.variant == newVariant) return;
2515
2516    /* [HGM] This routine is called each time an assignment is made to
2517     * gameInfo.variant during a game, to make sure the board sizes
2518     * are set to match the new variant. If that means adding or deleting
2519     * holdings, we shift the playing board accordingly
2520     * This kludge is needed because in ICS observe mode, we get boards
2521     * of an ongoing game without knowing the variant, and learn about the
2522     * latter only later. This can be because of the move list we requested,
2523     * in which case the game history is refilled from the beginning anyway,
2524     * but also when receiving holdings of a crazyhouse game. In the latter
2525     * case we want to add those holdings to the already received position.
2526     */
2527
2528
2529    if (appData.debugMode) {
2530      fprintf(debugFP, "Switch board from %s to %s\n",
2531              VariantName(gameInfo.variant), VariantName(newVariant));
2532      setbuf(debugFP, NULL);
2533    }
2534    shuffleOpenings = 0;       /* [HGM] shuffle */
2535    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2536    switch(newVariant)
2537      {
2538      case VariantShogi:
2539        newWidth = 9;  newHeight = 9;
2540        gameInfo.holdingsSize = 7;
2541      case VariantBughouse:
2542      case VariantCrazyhouse:
2543        newHoldingsWidth = 2; break;
2544      case VariantGreat:
2545        newWidth = 10;
2546      case VariantSuper:
2547        newHoldingsWidth = 2;
2548        gameInfo.holdingsSize = 8;
2549        break;
2550      case VariantGothic:
2551      case VariantCapablanca:
2552      case VariantCapaRandom:
2553        newWidth = 10;
2554      default:
2555        newHoldingsWidth = gameInfo.holdingsSize = 0;
2556      };
2557
2558    if(newWidth  != gameInfo.boardWidth  ||
2559       newHeight != gameInfo.boardHeight ||
2560       newHoldingsWidth != gameInfo.holdingsWidth ) {
2561
2562      /* shift position to new playing area, if needed */
2563      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2564        for(i=0; i<BOARD_HEIGHT; i++)
2565          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2566            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2567              board[i][j];
2568        for(i=0; i<newHeight; i++) {
2569          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2570          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2571        }
2572      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2573        for(i=0; i<BOARD_HEIGHT; i++)
2574          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2575            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2576              board[i][j];
2577      }
2578      board[HOLDINGS_SET] = 0;
2579      gameInfo.boardWidth  = newWidth;
2580      gameInfo.boardHeight = newHeight;
2581      gameInfo.holdingsWidth = newHoldingsWidth;
2582      gameInfo.variant = newVariant;
2583      InitDrawingSizes(-2, 0);
2584    } else gameInfo.variant = newVariant;
2585    CopyBoard(oldBoard, board);   // remember correctly formatted board
2586      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2587    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2588 }
2589
2590 static int loggedOn = FALSE;
2591
2592 /*-- Game start info cache: --*/
2593 int gs_gamenum;
2594 char gs_kind[MSG_SIZ];
2595 static char player1Name[128] = "";
2596 static char player2Name[128] = "";
2597 static char cont_seq[] = "\n\\   ";
2598 static int player1Rating = -1;
2599 static int player2Rating = -1;
2600 /*----------------------------*/
2601
2602 ColorClass curColor = ColorNormal;
2603 int suppressKibitz = 0;
2604
2605 // [HGM] seekgraph
2606 Boolean soughtPending = FALSE;
2607 Boolean seekGraphUp;
2608 #define MAX_SEEK_ADS 200
2609 #define SQUARE 0x80
2610 char *seekAdList[MAX_SEEK_ADS];
2611 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2612 float tcList[MAX_SEEK_ADS];
2613 char colorList[MAX_SEEK_ADS];
2614 int nrOfSeekAds = 0;
2615 int minRating = 1010, maxRating = 2800;
2616 int hMargin = 10, vMargin = 20, h, w;
2617 extern int squareSize, lineGap;
2618
2619 void
2620 PlotSeekAd (int i)
2621 {
2622         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2623         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2624         if(r < minRating+100 && r >=0 ) r = minRating+100;
2625         if(r > maxRating) r = maxRating;
2626         if(tc < 1.f) tc = 1.f;
2627         if(tc > 95.f) tc = 95.f;
2628         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2629         y = ((double)r - minRating)/(maxRating - minRating)
2630             * (h-vMargin-squareSize/8-1) + vMargin;
2631         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2632         if(strstr(seekAdList[i], " u ")) color = 1;
2633         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2634            !strstr(seekAdList[i], "bullet") &&
2635            !strstr(seekAdList[i], "blitz") &&
2636            !strstr(seekAdList[i], "standard") ) color = 2;
2637         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2638         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2639 }
2640
2641 void
2642 PlotSingleSeekAd (int i)
2643 {
2644         PlotSeekAd(i);
2645 }
2646
2647 void
2648 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2649 {
2650         char buf[MSG_SIZ], *ext = "";
2651         VariantClass v = StringToVariant(type);
2652         if(strstr(type, "wild")) {
2653             ext = type + 4; // append wild number
2654             if(v == VariantFischeRandom) type = "chess960"; else
2655             if(v == VariantLoadable) type = "setup"; else
2656             type = VariantName(v);
2657         }
2658         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2659         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2660             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2661             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2662             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2663             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2664             seekNrList[nrOfSeekAds] = nr;
2665             zList[nrOfSeekAds] = 0;
2666             seekAdList[nrOfSeekAds++] = StrSave(buf);
2667             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2668         }
2669 }
2670
2671 void
2672 EraseSeekDot (int i)
2673 {
2674     int x = xList[i], y = yList[i], d=squareSize/4, k;
2675     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2676     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2677     // now replot every dot that overlapped
2678     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2679         int xx = xList[k], yy = yList[k];
2680         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2681             DrawSeekDot(xx, yy, colorList[k]);
2682     }
2683 }
2684
2685 void
2686 RemoveSeekAd (int nr)
2687 {
2688         int i;
2689         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2690             EraseSeekDot(i);
2691             if(seekAdList[i]) free(seekAdList[i]);
2692             seekAdList[i] = seekAdList[--nrOfSeekAds];
2693             seekNrList[i] = seekNrList[nrOfSeekAds];
2694             ratingList[i] = ratingList[nrOfSeekAds];
2695             colorList[i]  = colorList[nrOfSeekAds];
2696             tcList[i] = tcList[nrOfSeekAds];
2697             xList[i]  = xList[nrOfSeekAds];
2698             yList[i]  = yList[nrOfSeekAds];
2699             zList[i]  = zList[nrOfSeekAds];
2700             seekAdList[nrOfSeekAds] = NULL;
2701             break;
2702         }
2703 }
2704
2705 Boolean
2706 MatchSoughtLine (char *line)
2707 {
2708     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2709     int nr, base, inc, u=0; char dummy;
2710
2711     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2712        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2713        (u=1) &&
2714        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2715         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2716         // match: compact and save the line
2717         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2718         return TRUE;
2719     }
2720     return FALSE;
2721 }
2722
2723 int
2724 DrawSeekGraph ()
2725 {
2726     int i;
2727     if(!seekGraphUp) return FALSE;
2728     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2729     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2730
2731     DrawSeekBackground(0, 0, w, h);
2732     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2733     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2734     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2735         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2736         yy = h-1-yy;
2737         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2738         if(i%500 == 0) {
2739             char buf[MSG_SIZ];
2740             snprintf(buf, MSG_SIZ, "%d", i);
2741             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2742         }
2743     }
2744     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2745     for(i=1; i<100; i+=(i<10?1:5)) {
2746         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2747         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2748         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2749             char buf[MSG_SIZ];
2750             snprintf(buf, MSG_SIZ, "%d", i);
2751             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2752         }
2753     }
2754     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2755     return TRUE;
2756 }
2757
2758 int
2759 SeekGraphClick (ClickType click, int x, int y, int moving)
2760 {
2761     static int lastDown = 0, displayed = 0, lastSecond;
2762     if(y < 0) return FALSE;
2763     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2764         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2765         if(!seekGraphUp) return FALSE;
2766         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2767         DrawPosition(TRUE, NULL);
2768         return TRUE;
2769     }
2770     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2771         if(click == Release || moving) return FALSE;
2772         nrOfSeekAds = 0;
2773         soughtPending = TRUE;
2774         SendToICS(ics_prefix);
2775         SendToICS("sought\n"); // should this be "sought all"?
2776     } else { // issue challenge based on clicked ad
2777         int dist = 10000; int i, closest = 0, second = 0;
2778         for(i=0; i<nrOfSeekAds; i++) {
2779             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2780             if(d < dist) { dist = d; closest = i; }
2781             second += (d - zList[i] < 120); // count in-range ads
2782             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2783         }
2784         if(dist < 120) {
2785             char buf[MSG_SIZ];
2786             second = (second > 1);
2787             if(displayed != closest || second != lastSecond) {
2788                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2789                 lastSecond = second; displayed = closest;
2790             }
2791             if(click == Press) {
2792                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2793                 lastDown = closest;
2794                 return TRUE;
2795             } // on press 'hit', only show info
2796             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2797             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2798             SendToICS(ics_prefix);
2799             SendToICS(buf);
2800             return TRUE; // let incoming board of started game pop down the graph
2801         } else if(click == Release) { // release 'miss' is ignored
2802             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2803             if(moving == 2) { // right up-click
2804                 nrOfSeekAds = 0; // refresh graph
2805                 soughtPending = TRUE;
2806                 SendToICS(ics_prefix);
2807                 SendToICS("sought\n"); // should this be "sought all"?
2808             }
2809             return TRUE;
2810         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2811         // press miss or release hit 'pop down' seek graph
2812         seekGraphUp = FALSE;
2813         DrawPosition(TRUE, NULL);
2814     }
2815     return TRUE;
2816 }
2817
2818 void
2819 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2820 {
2821 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2822 #define STARTED_NONE 0
2823 #define STARTED_MOVES 1
2824 #define STARTED_BOARD 2
2825 #define STARTED_OBSERVE 3
2826 #define STARTED_HOLDINGS 4
2827 #define STARTED_CHATTER 5
2828 #define STARTED_COMMENT 6
2829 #define STARTED_MOVES_NOHIDE 7
2830
2831     static int started = STARTED_NONE;
2832     static char parse[20000];
2833     static int parse_pos = 0;
2834     static char buf[BUF_SIZE + 1];
2835     static int firstTime = TRUE, intfSet = FALSE;
2836     static ColorClass prevColor = ColorNormal;
2837     static int savingComment = FALSE;
2838     static int cmatch = 0; // continuation sequence match
2839     char *bp;
2840     char str[MSG_SIZ];
2841     int i, oldi;
2842     int buf_len;
2843     int next_out;
2844     int tkind;
2845     int backup;    /* [DM] For zippy color lines */
2846     char *p;
2847     char talker[MSG_SIZ]; // [HGM] chat
2848     int channel, collective=0;
2849
2850     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2851
2852     if (appData.debugMode) {
2853       if (!error) {
2854         fprintf(debugFP, "<ICS: ");
2855         show_bytes(debugFP, data, count);
2856         fprintf(debugFP, "\n");
2857       }
2858     }
2859
2860     if (appData.debugMode) { int f = forwardMostMove;
2861         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2862                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2863                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2864     }
2865     if (count > 0) {
2866         /* If last read ended with a partial line that we couldn't parse,
2867            prepend it to the new read and try again. */
2868         if (leftover_len > 0) {
2869             for (i=0; i<leftover_len; i++)
2870               buf[i] = buf[leftover_start + i];
2871         }
2872
2873     /* copy new characters into the buffer */
2874     bp = buf + leftover_len;
2875     buf_len=leftover_len;
2876     for (i=0; i<count; i++)
2877     {
2878         // ignore these
2879         if (data[i] == '\r')
2880             continue;
2881
2882         // join lines split by ICS?
2883         if (!appData.noJoin)
2884         {
2885             /*
2886                 Joining just consists of finding matches against the
2887                 continuation sequence, and discarding that sequence
2888                 if found instead of copying it.  So, until a match
2889                 fails, there's nothing to do since it might be the
2890                 complete sequence, and thus, something we don't want
2891                 copied.
2892             */
2893             if (data[i] == cont_seq[cmatch])
2894             {
2895                 cmatch++;
2896                 if (cmatch == strlen(cont_seq))
2897                 {
2898                     cmatch = 0; // complete match.  just reset the counter
2899
2900                     /*
2901                         it's possible for the ICS to not include the space
2902                         at the end of the last word, making our [correct]
2903                         join operation fuse two separate words.  the server
2904                         does this when the space occurs at the width setting.
2905                     */
2906                     if (!buf_len || buf[buf_len-1] != ' ')
2907                     {
2908                         *bp++ = ' ';
2909                         buf_len++;
2910                     }
2911                 }
2912                 continue;
2913             }
2914             else if (cmatch)
2915             {
2916                 /*
2917                     match failed, so we have to copy what matched before
2918                     falling through and copying this character.  In reality,
2919                     this will only ever be just the newline character, but
2920                     it doesn't hurt to be precise.
2921                 */
2922                 strncpy(bp, cont_seq, cmatch);
2923                 bp += cmatch;
2924                 buf_len += cmatch;
2925                 cmatch = 0;
2926             }
2927         }
2928
2929         // copy this char
2930         *bp++ = data[i];
2931         buf_len++;
2932     }
2933
2934         buf[buf_len] = NULLCHAR;
2935 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2936         next_out = 0;
2937         leftover_start = 0;
2938
2939         i = 0;
2940         while (i < buf_len) {
2941             /* Deal with part of the TELNET option negotiation
2942                protocol.  We refuse to do anything beyond the
2943                defaults, except that we allow the WILL ECHO option,
2944                which ICS uses to turn off password echoing when we are
2945                directly connected to it.  We reject this option
2946                if localLineEditing mode is on (always on in xboard)
2947                and we are talking to port 23, which might be a real
2948                telnet server that will try to keep WILL ECHO on permanently.
2949              */
2950             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2951                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2952                 unsigned char option;
2953                 oldi = i;
2954                 switch ((unsigned char) buf[++i]) {
2955                   case TN_WILL:
2956                     if (appData.debugMode)
2957                       fprintf(debugFP, "\n<WILL ");
2958                     switch (option = (unsigned char) buf[++i]) {
2959                       case TN_ECHO:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "ECHO ");
2962                         /* Reply only if this is a change, according
2963                            to the protocol rules. */
2964                         if (remoteEchoOption) break;
2965                         if (appData.localLineEditing &&
2966                             atoi(appData.icsPort) == TN_PORT) {
2967                             TelnetRequest(TN_DONT, TN_ECHO);
2968                         } else {
2969                             EchoOff();
2970                             TelnetRequest(TN_DO, TN_ECHO);
2971                             remoteEchoOption = TRUE;
2972                         }
2973                         break;
2974                       default:
2975                         if (appData.debugMode)
2976                           fprintf(debugFP, "%d ", option);
2977                         /* Whatever this is, we don't want it. */
2978                         TelnetRequest(TN_DONT, option);
2979                         break;
2980                     }
2981                     break;
2982                   case TN_WONT:
2983                     if (appData.debugMode)
2984                       fprintf(debugFP, "\n<WONT ");
2985                     switch (option = (unsigned char) buf[++i]) {
2986                       case TN_ECHO:
2987                         if (appData.debugMode)
2988                           fprintf(debugFP, "ECHO ");
2989                         /* Reply only if this is a change, according
2990                            to the protocol rules. */
2991                         if (!remoteEchoOption) break;
2992                         EchoOn();
2993                         TelnetRequest(TN_DONT, TN_ECHO);
2994                         remoteEchoOption = FALSE;
2995                         break;
2996                       default:
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", (unsigned char) option);
2999                         /* Whatever this is, it must already be turned
3000                            off, because we never agree to turn on
3001                            anything non-default, so according to the
3002                            protocol rules, we don't reply. */
3003                         break;
3004                     }
3005                     break;
3006                   case TN_DO:
3007                     if (appData.debugMode)
3008                       fprintf(debugFP, "\n<DO ");
3009                     switch (option = (unsigned char) buf[++i]) {
3010                       default:
3011                         /* Whatever this is, we refuse to do it. */
3012                         if (appData.debugMode)
3013                           fprintf(debugFP, "%d ", option);
3014                         TelnetRequest(TN_WONT, option);
3015                         break;
3016                     }
3017                     break;
3018                   case TN_DONT:
3019                     if (appData.debugMode)
3020                       fprintf(debugFP, "\n<DONT ");
3021                     switch (option = (unsigned char) buf[++i]) {
3022                       default:
3023                         if (appData.debugMode)
3024                           fprintf(debugFP, "%d ", option);
3025                         /* Whatever this is, we are already not doing
3026                            it, because we never agree to do anything
3027                            non-default, so according to the protocol
3028                            rules, we don't reply. */
3029                         break;
3030                     }
3031                     break;
3032                   case TN_IAC:
3033                     if (appData.debugMode)
3034                       fprintf(debugFP, "\n<IAC ");
3035                     /* Doubled IAC; pass it through */
3036                     i--;
3037                     break;
3038                   default:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3041                     /* Drop all other telnet commands on the floor */
3042                     break;
3043                 }
3044                 if (oldi > next_out)
3045                   SendToPlayer(&buf[next_out], oldi - next_out);
3046                 if (++i > next_out)
3047                   next_out = i;
3048                 continue;
3049             }
3050
3051             /* OK, this at least will *usually* work */
3052             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3053                 loggedOn = TRUE;
3054             }
3055
3056             if (loggedOn && !intfSet) {
3057                 if (ics_type == ICS_ICC) {
3058                   snprintf(str, MSG_SIZ,
3059                           "/set-quietly interface %s\n/set-quietly style 12\n",
3060                           programVersion);
3061                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3062                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3063                 } else if (ics_type == ICS_CHESSNET) {
3064                   snprintf(str, MSG_SIZ, "/style 12\n");
3065                 } else {
3066                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3067                   strcat(str, programVersion);
3068                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3069                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3071 #ifdef WIN32
3072                   strcat(str, "$iset nohighlight 1\n");
3073 #endif
3074                   strcat(str, "$iset lock 1\n$style 12\n");
3075                 }
3076                 SendToICS(str);
3077                 NotifyFrontendLogin();
3078                 intfSet = TRUE;
3079             }
3080
3081             if (started == STARTED_COMMENT) {
3082                 /* Accumulate characters in comment */
3083                 parse[parse_pos++] = buf[i];
3084                 if (buf[i] == '\n') {
3085                     parse[parse_pos] = NULLCHAR;
3086                     if(chattingPartner>=0) {
3087                         char mess[MSG_SIZ];
3088                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3089                         OutputChatMessage(chattingPartner, mess);
3090                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3091                             int p;
3092                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3093                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3094                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3095                                 OutputChatMessage(p, mess);
3096                                 break;
3097                             }
3098                         }
3099                         chattingPartner = -1;
3100                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3101                         collective = 0;
3102                     } else
3103                     if(!suppressKibitz) // [HGM] kibitz
3104                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3105                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3106                         int nrDigit = 0, nrAlph = 0, j;
3107                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3108                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3109                         parse[parse_pos] = NULLCHAR;
3110                         // try to be smart: if it does not look like search info, it should go to
3111                         // ICS interaction window after all, not to engine-output window.
3112                         for(j=0; j<parse_pos; j++) { // count letters and digits
3113                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3114                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3115                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3116                         }
3117                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3118                             int depth=0; float score;
3119                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3120                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3121                                 pvInfoList[forwardMostMove-1].depth = depth;
3122                                 pvInfoList[forwardMostMove-1].score = 100*score;
3123                             }
3124                             OutputKibitz(suppressKibitz, parse);
3125                         } else {
3126                             char tmp[MSG_SIZ];
3127                             if(gameMode == IcsObserving) // restore original ICS messages
3128                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3129                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3130                             else
3131                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3132                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3133                             SendToPlayer(tmp, strlen(tmp));
3134                         }
3135                         next_out = i+1; // [HGM] suppress printing in ICS window
3136                     }
3137                     started = STARTED_NONE;
3138                 } else {
3139                     /* Don't match patterns against characters in comment */
3140                     i++;
3141                     continue;
3142                 }
3143             }
3144             if (started == STARTED_CHATTER) {
3145                 if (buf[i] != '\n') {
3146                     /* Don't match patterns against characters in chatter */
3147                     i++;
3148                     continue;
3149                 }
3150                 started = STARTED_NONE;
3151                 if(suppressKibitz) next_out = i+1;
3152             }
3153
3154             /* Kludge to deal with rcmd protocol */
3155             if (firstTime && looking_at(buf, &i, "\001*")) {
3156                 DisplayFatalError(&buf[1], 0, 1);
3157                 continue;
3158             } else {
3159                 firstTime = FALSE;
3160             }
3161
3162             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3163                 ics_type = ICS_ICC;
3164                 ics_prefix = "/";
3165                 if (appData.debugMode)
3166                   fprintf(debugFP, "ics_type %d\n", ics_type);
3167                 continue;
3168             }
3169             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3170                 ics_type = ICS_FICS;
3171                 ics_prefix = "$";
3172                 if (appData.debugMode)
3173                   fprintf(debugFP, "ics_type %d\n", ics_type);
3174                 continue;
3175             }
3176             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3177                 ics_type = ICS_CHESSNET;
3178                 ics_prefix = "/";
3179                 if (appData.debugMode)
3180                   fprintf(debugFP, "ics_type %d\n", ics_type);
3181                 continue;
3182             }
3183
3184             if (!loggedOn &&
3185                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3186                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3187                  looking_at(buf, &i, "will be \"*\""))) {
3188               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3189               continue;
3190             }
3191
3192             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3193               char buf[MSG_SIZ];
3194               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3195               DisplayIcsInteractionTitle(buf);
3196               have_set_title = TRUE;
3197             }
3198
3199             /* skip finger notes */
3200             if (started == STARTED_NONE &&
3201                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3202                  (buf[i] == '1' && buf[i+1] == '0')) &&
3203                 buf[i+2] == ':' && buf[i+3] == ' ') {
3204               started = STARTED_CHATTER;
3205               i += 3;
3206               continue;
3207             }
3208
3209             oldi = i;
3210             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3211             if(appData.seekGraph) {
3212                 if(soughtPending && MatchSoughtLine(buf+i)) {
3213                     i = strstr(buf+i, "rated") - buf;
3214                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3215                     next_out = leftover_start = i;
3216                     started = STARTED_CHATTER;
3217                     suppressKibitz = TRUE;
3218                     continue;
3219                 }
3220                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3221                         && looking_at(buf, &i, "* ads displayed")) {
3222                     soughtPending = FALSE;
3223                     seekGraphUp = TRUE;
3224                     DrawSeekGraph();
3225                     continue;
3226                 }
3227                 if(appData.autoRefresh) {
3228                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3229                         int s = (ics_type == ICS_ICC); // ICC format differs
3230                         if(seekGraphUp)
3231                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3232                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3233                         looking_at(buf, &i, "*% "); // eat prompt
3234                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3235                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3236                         next_out = i; // suppress
3237                         continue;
3238                     }
3239                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3240                         char *p = star_match[0];
3241                         while(*p) {
3242                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3243                             while(*p && *p++ != ' '); // next
3244                         }
3245                         looking_at(buf, &i, "*% "); // eat prompt
3246                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247                         next_out = i;
3248                         continue;
3249                     }
3250                 }
3251             }
3252
3253             /* skip formula vars */
3254             if (started == STARTED_NONE &&
3255                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3256               started = STARTED_CHATTER;
3257               i += 3;
3258               continue;
3259             }
3260
3261             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3262             if (appData.autoKibitz && started == STARTED_NONE &&
3263                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3264                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3265                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3266                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3267                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3268                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3269                         suppressKibitz = TRUE;
3270                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3271                         next_out = i;
3272                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3273                                 && (gameMode == IcsPlayingWhite)) ||
3274                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3275                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3276                             started = STARTED_CHATTER; // own kibitz we simply discard
3277                         else {
3278                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3279                             parse_pos = 0; parse[0] = NULLCHAR;
3280                             savingComment = TRUE;
3281                             suppressKibitz = gameMode != IcsObserving ? 2 :
3282                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3283                         }
3284                         continue;
3285                 } else
3286                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3287                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3288                          && atoi(star_match[0])) {
3289                     // suppress the acknowledgements of our own autoKibitz
3290                     char *p;
3291                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3292                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3293                     SendToPlayer(star_match[0], strlen(star_match[0]));
3294                     if(looking_at(buf, &i, "*% ")) // eat prompt
3295                         suppressKibitz = FALSE;
3296                     next_out = i;
3297                     continue;
3298                 }
3299             } // [HGM] kibitz: end of patch
3300
3301             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3302
3303             // [HGM] chat: intercept tells by users for which we have an open chat window
3304             channel = -1;
3305             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3306                                            looking_at(buf, &i, "* whispers:") ||
3307                                            looking_at(buf, &i, "* kibitzes:") ||
3308                                            looking_at(buf, &i, "* shouts:") ||
3309                                            looking_at(buf, &i, "* c-shouts:") ||
3310                                            looking_at(buf, &i, "--> * ") ||
3311                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3314                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3315                 int p;
3316                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3317                 chattingPartner = -1; collective = 0;
3318
3319                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3320                 for(p=0; p<MAX_CHAT; p++) {
3321                     collective = 1;
3322                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3323                     talker[0] = '['; strcat(talker, "] ");
3324                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3325                     chattingPartner = p; break;
3326                     }
3327                 } else
3328                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3329                 for(p=0; p<MAX_CHAT; p++) {
3330                     collective = 1;
3331                     if(!strcmp("kibitzes", chatPartner[p])) {
3332                         talker[0] = '['; strcat(talker, "] ");
3333                         chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3337                 for(p=0; p<MAX_CHAT; p++) {
3338                     collective = 1;
3339                     if(!strcmp("whispers", chatPartner[p])) {
3340                         talker[0] = '['; strcat(talker, "] ");
3341                         chattingPartner = p; break;
3342                     }
3343                 } else
3344                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3345                   if(buf[i-8] == '-' && buf[i-3] == 't')
3346                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3347                     collective = 1;
3348                     if(!strcmp("c-shouts", chatPartner[p])) {
3349                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3350                         chattingPartner = p; break;
3351                     }
3352                   }
3353                   if(chattingPartner < 0)
3354                   for(p=0; p<MAX_CHAT; p++) {
3355                     collective = 1;
3356                     if(!strcmp("shouts", chatPartner[p])) {
3357                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3358                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3359                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3360                         chattingPartner = p; break;
3361                     }
3362                   }
3363                 }
3364                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3365                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3366                     talker[0] = 0;
3367                     Colorize(ColorTell, FALSE);
3368                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3369                     collective |= 2;
3370                     chattingPartner = p; break;
3371                 }
3372                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3373                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3374                     started = STARTED_COMMENT;
3375                     parse_pos = 0; parse[0] = NULLCHAR;
3376                     savingComment = 3 + chattingPartner; // counts as TRUE
3377                     if(collective == 3) i = oldi; else {
3378                         suppressKibitz = TRUE;
3379                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3380                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3381                         continue;
3382                     }
3383                 }
3384             } // [HGM] chat: end of patch
3385
3386           backup = i;
3387             if (appData.zippyTalk || appData.zippyPlay) {
3388                 /* [DM] Backup address for color zippy lines */
3389 #if ZIPPY
3390                if (loggedOn == TRUE)
3391                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3392                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3393 #endif
3394             } // [DM] 'else { ' deleted
3395                 if (
3396                     /* Regular tells and says */
3397                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3398                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3399                     looking_at(buf, &i, "* says: ") ||
3400                     /* Don't color "message" or "messages" output */
3401                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3402                     looking_at(buf, &i, "*. * at *:*: ") ||
3403                     looking_at(buf, &i, "--* (*:*): ") ||
3404                     /* Message notifications (same color as tells) */
3405                     looking_at(buf, &i, "* has left a message ") ||
3406                     looking_at(buf, &i, "* just sent you a message:\n") ||
3407                     /* Whispers and kibitzes */
3408                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3409                     looking_at(buf, &i, "* kibitzes: ") ||
3410                     /* Channel tells */
3411                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3412
3413                   if (tkind == 1 && strchr(star_match[0], ':')) {
3414                       /* Avoid "tells you:" spoofs in channels */
3415                      tkind = 3;
3416                   }
3417                   if (star_match[0][0] == NULLCHAR ||
3418                       strchr(star_match[0], ' ') ||
3419                       (tkind == 3 && strchr(star_match[1], ' '))) {
3420                     /* Reject bogus matches */
3421                     i = oldi;
3422                   } else {
3423                     if (appData.colorize) {
3424                       if (oldi > next_out) {
3425                         SendToPlayer(&buf[next_out], oldi - next_out);
3426                         next_out = oldi;
3427                       }
3428                       switch (tkind) {
3429                       case 1:
3430                         Colorize(ColorTell, FALSE);
3431                         curColor = ColorTell;
3432                         break;
3433                       case 2:
3434                         Colorize(ColorKibitz, FALSE);
3435                         curColor = ColorKibitz;
3436                         break;
3437                       case 3:
3438                         p = strrchr(star_match[1], '(');
3439                         if (p == NULL) {
3440                           p = star_match[1];
3441                         } else {
3442                           p++;
3443                         }
3444                         if (atoi(p) == 1) {
3445                           Colorize(ColorChannel1, FALSE);
3446                           curColor = ColorChannel1;
3447                         } else {
3448                           Colorize(ColorChannel, FALSE);
3449                           curColor = ColorChannel;
3450                         }
3451                         break;
3452                       case 5:
3453                         curColor = ColorNormal;
3454                         break;
3455                       }
3456                     }
3457                     if (started == STARTED_NONE && appData.autoComment &&
3458                         (gameMode == IcsObserving ||
3459                          gameMode == IcsPlayingWhite ||
3460                          gameMode == IcsPlayingBlack)) {
3461                       parse_pos = i - oldi;
3462                       memcpy(parse, &buf[oldi], parse_pos);
3463                       parse[parse_pos] = NULLCHAR;
3464                       started = STARTED_COMMENT;
3465                       savingComment = TRUE;
3466                     } else if(collective != 3) {
3467                       started = STARTED_CHATTER;
3468                       savingComment = FALSE;
3469                     }
3470                     loggedOn = TRUE;
3471                     continue;
3472                   }
3473                 }
3474
3475                 if (looking_at(buf, &i, "* s-shouts: ") ||
3476                     looking_at(buf, &i, "* c-shouts: ")) {
3477                     if (appData.colorize) {
3478                         if (oldi > next_out) {
3479                             SendToPlayer(&buf[next_out], oldi - next_out);
3480                             next_out = oldi;
3481                         }
3482                         Colorize(ColorSShout, FALSE);
3483                         curColor = ColorSShout;
3484                     }
3485                     loggedOn = TRUE;
3486                     started = STARTED_CHATTER;
3487                     continue;
3488                 }
3489
3490                 if (looking_at(buf, &i, "--->")) {
3491                     loggedOn = TRUE;
3492                     continue;
3493                 }
3494
3495                 if (looking_at(buf, &i, "* shouts: ") ||
3496                     looking_at(buf, &i, "--> ")) {
3497                     if (appData.colorize) {
3498                         if (oldi > next_out) {
3499                             SendToPlayer(&buf[next_out], oldi - next_out);
3500                             next_out = oldi;
3501                         }
3502                         Colorize(ColorShout, FALSE);
3503                         curColor = ColorShout;
3504                     }
3505                     loggedOn = TRUE;
3506                     started = STARTED_CHATTER;
3507                     continue;
3508                 }
3509
3510                 if (looking_at( buf, &i, "Challenge:")) {
3511                     if (appData.colorize) {
3512                         if (oldi > next_out) {
3513                             SendToPlayer(&buf[next_out], oldi - next_out);
3514                             next_out = oldi;
3515                         }
3516                         Colorize(ColorChallenge, FALSE);
3517                         curColor = ColorChallenge;
3518                     }
3519                     loggedOn = TRUE;
3520                     continue;
3521                 }
3522
3523                 if (looking_at(buf, &i, "* offers you") ||
3524                     looking_at(buf, &i, "* offers to be") ||
3525                     looking_at(buf, &i, "* would like to") ||
3526                     looking_at(buf, &i, "* requests to") ||
3527                     looking_at(buf, &i, "Your opponent offers") ||
3528                     looking_at(buf, &i, "Your opponent requests")) {
3529
3530                     if (appData.colorize) {
3531                         if (oldi > next_out) {
3532                             SendToPlayer(&buf[next_out], oldi - next_out);
3533                             next_out = oldi;
3534                         }
3535                         Colorize(ColorRequest, FALSE);
3536                         curColor = ColorRequest;
3537                     }
3538                     continue;
3539                 }
3540
3541                 if (looking_at(buf, &i, "* (*) seeking")) {
3542                     if (appData.colorize) {
3543                         if (oldi > next_out) {
3544                             SendToPlayer(&buf[next_out], oldi - next_out);
3545                             next_out = oldi;
3546                         }
3547                         Colorize(ColorSeek, FALSE);
3548                         curColor = ColorSeek;
3549                     }
3550                     continue;
3551             }
3552
3553           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3554
3555             if (looking_at(buf, &i, "\\   ")) {
3556                 if (prevColor != ColorNormal) {
3557                     if (oldi > next_out) {
3558                         SendToPlayer(&buf[next_out], oldi - next_out);
3559                         next_out = oldi;
3560                     }
3561                     Colorize(prevColor, TRUE);
3562                     curColor = prevColor;
3563                 }
3564                 if (savingComment) {
3565                     parse_pos = i - oldi;
3566                     memcpy(parse, &buf[oldi], parse_pos);
3567                     parse[parse_pos] = NULLCHAR;
3568                     started = STARTED_COMMENT;
3569                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3570                         chattingPartner = savingComment - 3; // kludge to remember the box
3571                 } else {
3572                     started = STARTED_CHATTER;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "Black Strength :") ||
3578                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3579                 looking_at(buf, &i, "<10>") ||
3580                 looking_at(buf, &i, "#@#")) {
3581                 /* Wrong board style */
3582                 loggedOn = TRUE;
3583                 SendToICS(ics_prefix);
3584                 SendToICS("set style 12\n");
3585                 SendToICS(ics_prefix);
3586                 SendToICS("refresh\n");
3587                 continue;
3588             }
3589
3590             if (looking_at(buf, &i, "login:")) {
3591               if (!have_sent_ICS_logon) {
3592                 if(ICSInitScript())
3593                   have_sent_ICS_logon = 1;
3594                 else // no init script was found
3595                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3596               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3597                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3598               }
3599                 continue;
3600             }
3601
3602             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3603                 (looking_at(buf, &i, "\n<12> ") ||
3604                  looking_at(buf, &i, "<12> "))) {
3605                 loggedOn = TRUE;
3606                 if (oldi > next_out) {
3607                     SendToPlayer(&buf[next_out], oldi - next_out);
3608                 }
3609                 next_out = i;
3610                 started = STARTED_BOARD;
3611                 parse_pos = 0;
3612                 continue;
3613             }
3614
3615             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3616                 looking_at(buf, &i, "<b1> ")) {
3617                 if (oldi > next_out) {
3618                     SendToPlayer(&buf[next_out], oldi - next_out);
3619                 }
3620                 next_out = i;
3621                 started = STARTED_HOLDINGS;
3622                 parse_pos = 0;
3623                 continue;
3624             }
3625
3626             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3627                 loggedOn = TRUE;
3628                 /* Header for a move list -- first line */
3629
3630                 switch (ics_getting_history) {
3631                   case H_FALSE:
3632                     switch (gameMode) {
3633                       case IcsIdle:
3634                       case BeginningOfGame:
3635                         /* User typed "moves" or "oldmoves" while we
3636                            were idle.  Pretend we asked for these
3637                            moves and soak them up so user can step
3638                            through them and/or save them.
3639                            */
3640                         Reset(FALSE, TRUE);
3641                         gameMode = IcsObserving;
3642                         ModeHighlight();
3643                         ics_gamenum = -1;
3644                         ics_getting_history = H_GOT_UNREQ_HEADER;
3645                         break;
3646                       case EditGame: /*?*/
3647                       case EditPosition: /*?*/
3648                         /* Should above feature work in these modes too? */
3649                         /* For now it doesn't */
3650                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3651                         break;
3652                       default:
3653                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3654                         break;
3655                     }
3656                     break;
3657                   case H_REQUESTED:
3658                     /* Is this the right one? */
3659                     if (gameInfo.white && gameInfo.black &&
3660                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3661                         strcmp(gameInfo.black, star_match[2]) == 0) {
3662                         /* All is well */
3663                         ics_getting_history = H_GOT_REQ_HEADER;
3664                     }
3665                     break;
3666                   case H_GOT_REQ_HEADER:
3667                   case H_GOT_UNREQ_HEADER:
3668                   case H_GOT_UNWANTED_HEADER:
3669                   case H_GETTING_MOVES:
3670                     /* Should not happen */
3671                     DisplayError(_("Error gathering move list: two headers"), 0);
3672                     ics_getting_history = H_FALSE;
3673                     break;
3674                 }
3675
3676                 /* Save player ratings into gameInfo if needed */
3677                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3678                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3679                     (gameInfo.whiteRating == -1 ||
3680                      gameInfo.blackRating == -1)) {
3681
3682                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3683                     gameInfo.blackRating = string_to_rating(star_match[3]);
3684                     if (appData.debugMode)
3685                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3686                               gameInfo.whiteRating, gameInfo.blackRating);
3687                 }
3688                 continue;
3689             }
3690
3691             if (looking_at(buf, &i,
3692               "* * match, initial time: * minute*, increment: * second")) {
3693                 /* Header for a move list -- second line */
3694                 /* Initial board will follow if this is a wild game */
3695                 if (gameInfo.event != NULL) free(gameInfo.event);
3696                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3697                 gameInfo.event = StrSave(str);
3698                 /* [HGM] we switched variant. Translate boards if needed. */
3699                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3700                 continue;
3701             }
3702
3703             if (looking_at(buf, &i, "Move  ")) {
3704                 /* Beginning of a move list */
3705                 switch (ics_getting_history) {
3706                   case H_FALSE:
3707                     /* Normally should not happen */
3708                     /* Maybe user hit reset while we were parsing */
3709                     break;
3710                   case H_REQUESTED:
3711                     /* Happens if we are ignoring a move list that is not
3712                      * the one we just requested.  Common if the user
3713                      * tries to observe two games without turning off
3714                      * getMoveList */
3715                     break;
3716                   case H_GETTING_MOVES:
3717                     /* Should not happen */
3718                     DisplayError(_("Error gathering move list: nested"), 0);
3719                     ics_getting_history = H_FALSE;
3720                     break;
3721                   case H_GOT_REQ_HEADER:
3722                     ics_getting_history = H_GETTING_MOVES;
3723                     started = STARTED_MOVES;
3724                     parse_pos = 0;
3725                     if (oldi > next_out) {
3726                         SendToPlayer(&buf[next_out], oldi - next_out);
3727                     }
3728                     break;
3729                   case H_GOT_UNREQ_HEADER:
3730                     ics_getting_history = H_GETTING_MOVES;
3731                     started = STARTED_MOVES_NOHIDE;
3732                     parse_pos = 0;
3733                     break;
3734                   case H_GOT_UNWANTED_HEADER:
3735                     ics_getting_history = H_FALSE;
3736                     break;
3737                 }
3738                 continue;
3739             }
3740
3741             if (looking_at(buf, &i, "% ") ||
3742                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3743                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3744                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3745                     soughtPending = FALSE;
3746                     seekGraphUp = TRUE;
3747                     DrawSeekGraph();
3748                 }
3749                 if(suppressKibitz) next_out = i;
3750                 savingComment = FALSE;
3751                 suppressKibitz = 0;
3752                 switch (started) {
3753                   case STARTED_MOVES:
3754                   case STARTED_MOVES_NOHIDE:
3755                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3756                     parse[parse_pos + i - oldi] = NULLCHAR;
3757                     ParseGameHistory(parse);
3758 #if ZIPPY
3759                     if (appData.zippyPlay && first.initDone) {
3760                         FeedMovesToProgram(&first, forwardMostMove);
3761                         if (gameMode == IcsPlayingWhite) {
3762                             if (WhiteOnMove(forwardMostMove)) {
3763                                 if (first.sendTime) {
3764                                   if (first.useColors) {
3765                                     SendToProgram("black\n", &first);
3766                                   }
3767                                   SendTimeRemaining(&first, TRUE);
3768                                 }
3769                                 if (first.useColors) {
3770                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3771                                 }
3772                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3773                                 first.maybeThinking = TRUE;
3774                             } else {
3775                                 if (first.usePlayother) {
3776                                   if (first.sendTime) {
3777                                     SendTimeRemaining(&first, TRUE);
3778                                   }
3779                                   SendToProgram("playother\n", &first);
3780                                   firstMove = FALSE;
3781                                 } else {
3782                                   firstMove = TRUE;
3783                                 }
3784                             }
3785                         } else if (gameMode == IcsPlayingBlack) {
3786                             if (!WhiteOnMove(forwardMostMove)) {
3787                                 if (first.sendTime) {
3788                                   if (first.useColors) {
3789                                     SendToProgram("white\n", &first);
3790                                   }
3791                                   SendTimeRemaining(&first, FALSE);
3792                                 }
3793                                 if (first.useColors) {
3794                                   SendToProgram("black\n", &first);
3795                                 }
3796                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3797                                 first.maybeThinking = TRUE;
3798                             } else {
3799                                 if (first.usePlayother) {
3800                                   if (first.sendTime) {
3801                                     SendTimeRemaining(&first, FALSE);
3802                                   }
3803                                   SendToProgram("playother\n", &first);
3804                                   firstMove = FALSE;
3805                                 } else {
3806                                   firstMove = TRUE;
3807                                 }
3808                             }
3809                         }
3810                     }
3811 #endif
3812                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3813                         /* Moves came from oldmoves or moves command
3814                            while we weren't doing anything else.
3815                            */
3816                         currentMove = forwardMostMove;
3817                         ClearHighlights();/*!!could figure this out*/
3818                         flipView = appData.flipView;
3819                         DrawPosition(TRUE, boards[currentMove]);
3820                         DisplayBothClocks();
3821                         snprintf(str, MSG_SIZ, "%s %s %s",
3822                                 gameInfo.white, _("vs."),  gameInfo.black);
3823                         DisplayTitle(str);
3824                         gameMode = IcsIdle;
3825                     } else {
3826                         /* Moves were history of an active game */
3827                         if (gameInfo.resultDetails != NULL) {
3828                             free(gameInfo.resultDetails);
3829                             gameInfo.resultDetails = NULL;
3830                         }
3831                     }
3832                     HistorySet(parseList, backwardMostMove,
3833                                forwardMostMove, currentMove-1);
3834                     DisplayMove(currentMove - 1);
3835                     if (started == STARTED_MOVES) next_out = i;
3836                     started = STARTED_NONE;
3837                     ics_getting_history = H_FALSE;
3838                     break;
3839
3840                   case STARTED_OBSERVE:
3841                     started = STARTED_NONE;
3842                     SendToICS(ics_prefix);
3843                     SendToICS("refresh\n");
3844                     break;
3845
3846                   default:
3847                     break;
3848                 }
3849                 if(bookHit) { // [HGM] book: simulate book reply
3850                     static char bookMove[MSG_SIZ]; // a bit generous?
3851
3852                     programStats.nodes = programStats.depth = programStats.time =
3853                     programStats.score = programStats.got_only_move = 0;
3854                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3855
3856                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3857                     strcat(bookMove, bookHit);
3858                     HandleMachineMove(bookMove, &first);
3859                 }
3860                 continue;
3861             }
3862
3863             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3864                  started == STARTED_HOLDINGS ||
3865                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3866                 /* Accumulate characters in move list or board */
3867                 parse[parse_pos++] = buf[i];
3868             }
3869
3870             /* Start of game messages.  Mostly we detect start of game
3871                when the first board image arrives.  On some versions
3872                of the ICS, though, we need to do a "refresh" after starting
3873                to observe in order to get the current board right away. */
3874             if (looking_at(buf, &i, "Adding game * to observation list")) {
3875                 started = STARTED_OBSERVE;
3876                 continue;
3877             }
3878
3879             /* Handle auto-observe */
3880             if (appData.autoObserve &&
3881                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3882                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3883                 char *player;
3884                 /* Choose the player that was highlighted, if any. */
3885                 if (star_match[0][0] == '\033' ||
3886                     star_match[1][0] != '\033') {
3887                     player = star_match[0];
3888                 } else {
3889                     player = star_match[2];
3890                 }
3891                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3892                         ics_prefix, StripHighlightAndTitle(player));
3893                 SendToICS(str);
3894
3895                 /* Save ratings from notify string */
3896                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3897                 player1Rating = string_to_rating(star_match[1]);
3898                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3899                 player2Rating = string_to_rating(star_match[3]);
3900
3901                 if (appData.debugMode)
3902                   fprintf(debugFP,
3903                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3904                           player1Name, player1Rating,
3905                           player2Name, player2Rating);
3906
3907                 continue;
3908             }
3909
3910             /* Deal with automatic examine mode after a game,
3911                and with IcsObserving -> IcsExamining transition */
3912             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3913                 looking_at(buf, &i, "has made you an examiner of game *")) {
3914
3915                 int gamenum = atoi(star_match[0]);
3916                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3917                     gamenum == ics_gamenum) {
3918                     /* We were already playing or observing this game;
3919                        no need to refetch history */
3920                     gameMode = IcsExamining;
3921                     if (pausing) {
3922                         pauseExamForwardMostMove = forwardMostMove;
3923                     } else if (currentMove < forwardMostMove) {
3924                         ForwardInner(forwardMostMove);
3925                     }
3926                 } else {
3927                     /* I don't think this case really can happen */
3928                     SendToICS(ics_prefix);
3929                     SendToICS("refresh\n");
3930                 }
3931                 continue;
3932             }
3933
3934             /* Error messages */
3935 //          if (ics_user_moved) {
3936             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3937                 if (looking_at(buf, &i, "Illegal move") ||
3938                     looking_at(buf, &i, "Not a legal move") ||
3939                     looking_at(buf, &i, "Your king is in check") ||
3940                     looking_at(buf, &i, "It isn't your turn") ||
3941                     looking_at(buf, &i, "It is not your move")) {
3942                     /* Illegal move */
3943                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3944                         currentMove = forwardMostMove-1;
3945                         DisplayMove(currentMove - 1); /* before DMError */
3946                         DrawPosition(FALSE, boards[currentMove]);
3947                         SwitchClocks(forwardMostMove-1); // [HGM] race
3948                         DisplayBothClocks();
3949                     }
3950                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3951                     ics_user_moved = 0;
3952                     continue;
3953                 }
3954             }
3955
3956             if (looking_at(buf, &i, "still have time") ||
3957                 looking_at(buf, &i, "not out of time") ||
3958                 looking_at(buf, &i, "either player is out of time") ||
3959                 looking_at(buf, &i, "has timeseal; checking")) {
3960                 /* We must have called his flag a little too soon */
3961                 whiteFlag = blackFlag = FALSE;
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "added * seconds to") ||
3966                 looking_at(buf, &i, "seconds were added to")) {
3967                 /* Update the clocks */
3968                 SendToICS(ics_prefix);
3969                 SendToICS("refresh\n");
3970                 continue;
3971             }
3972
3973             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3974                 ics_clock_paused = TRUE;
3975                 StopClocks();
3976                 continue;
3977             }
3978
3979             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3980                 ics_clock_paused = FALSE;
3981                 StartClocks();
3982                 continue;
3983             }
3984
3985             /* Grab player ratings from the Creating: message.
3986                Note we have to check for the special case when
3987                the ICS inserts things like [white] or [black]. */
3988             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3989                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3990                 /* star_matches:
3991                    0    player 1 name (not necessarily white)
3992                    1    player 1 rating
3993                    2    empty, white, or black (IGNORED)
3994                    3    player 2 name (not necessarily black)
3995                    4    player 2 rating
3996
3997                    The names/ratings are sorted out when the game
3998                    actually starts (below).
3999                 */
4000                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4001                 player1Rating = string_to_rating(star_match[1]);
4002                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4003                 player2Rating = string_to_rating(star_match[4]);
4004
4005                 if (appData.debugMode)
4006                   fprintf(debugFP,
4007                           "Ratings from 'Creating:' %s %d, %s %d\n",
4008                           player1Name, player1Rating,
4009                           player2Name, player2Rating);
4010
4011                 continue;
4012             }
4013
4014             /* Improved generic start/end-of-game messages */
4015             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4016                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4017                 /* If tkind == 0: */
4018                 /* star_match[0] is the game number */
4019                 /*           [1] is the white player's name */
4020                 /*           [2] is the black player's name */
4021                 /* For end-of-game: */
4022                 /*           [3] is the reason for the game end */
4023                 /*           [4] is a PGN end game-token, preceded by " " */
4024                 /* For start-of-game: */
4025                 /*           [3] begins with "Creating" or "Continuing" */
4026                 /*           [4] is " *" or empty (don't care). */
4027                 int gamenum = atoi(star_match[0]);
4028                 char *whitename, *blackname, *why, *endtoken;
4029                 ChessMove endtype = EndOfFile;
4030
4031                 if (tkind == 0) {
4032                   whitename = star_match[1];
4033                   blackname = star_match[2];
4034                   why = star_match[3];
4035                   endtoken = star_match[4];
4036                 } else {
4037                   whitename = star_match[1];
4038                   blackname = star_match[3];
4039                   why = star_match[5];
4040                   endtoken = star_match[6];
4041                 }
4042
4043                 /* Game start messages */
4044                 if (strncmp(why, "Creating ", 9) == 0 ||
4045                     strncmp(why, "Continuing ", 11) == 0) {
4046                     gs_gamenum = gamenum;
4047                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4048                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4049                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4050 #if ZIPPY
4051                     if (appData.zippyPlay) {
4052                         ZippyGameStart(whitename, blackname);
4053                     }
4054 #endif /*ZIPPY*/
4055                     partnerBoardValid = FALSE; // [HGM] bughouse
4056                     continue;
4057                 }
4058
4059                 /* Game end messages */
4060                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4061                     ics_gamenum != gamenum) {
4062                     continue;
4063                 }
4064                 while (endtoken[0] == ' ') endtoken++;
4065                 switch (endtoken[0]) {
4066                   case '*':
4067                   default:
4068                     endtype = GameUnfinished;
4069                     break;
4070                   case '0':
4071                     endtype = BlackWins;
4072                     break;
4073                   case '1':
4074                     if (endtoken[1] == '/')
4075                       endtype = GameIsDrawn;
4076                     else
4077                       endtype = WhiteWins;
4078                     break;
4079                 }
4080                 GameEnds(endtype, why, GE_ICS);
4081 #if ZIPPY
4082                 if (appData.zippyPlay && first.initDone) {
4083                     ZippyGameEnd(endtype, why);
4084                     if (first.pr == NoProc) {
4085                       /* Start the next process early so that we'll
4086                          be ready for the next challenge */
4087                       StartChessProgram(&first);
4088                     }
4089                     /* Send "new" early, in case this command takes
4090                        a long time to finish, so that we'll be ready
4091                        for the next challenge. */
4092                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4093                     Reset(TRUE, TRUE);
4094                 }
4095 #endif /*ZIPPY*/
4096                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4097                 continue;
4098             }
4099
4100             if (looking_at(buf, &i, "Removing game * from observation") ||
4101                 looking_at(buf, &i, "no longer observing game *") ||
4102                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4103                 if (gameMode == IcsObserving &&
4104                     atoi(star_match[0]) == ics_gamenum)
4105                   {
4106                       /* icsEngineAnalyze */
4107                       if (appData.icsEngineAnalyze) {
4108                             ExitAnalyzeMode();
4109                             ModeHighlight();
4110                       }
4111                       StopClocks();
4112                       gameMode = IcsIdle;
4113                       ics_gamenum = -1;
4114                       ics_user_moved = FALSE;
4115                   }
4116                 continue;
4117             }
4118
4119             if (looking_at(buf, &i, "no longer examining game *")) {
4120                 if (gameMode == IcsExamining &&
4121                     atoi(star_match[0]) == ics_gamenum)
4122                   {
4123                       gameMode = IcsIdle;
4124                       ics_gamenum = -1;
4125                       ics_user_moved = FALSE;
4126                   }
4127                 continue;
4128             }
4129
4130             /* Advance leftover_start past any newlines we find,
4131                so only partial lines can get reparsed */
4132             if (looking_at(buf, &i, "\n")) {
4133                 prevColor = curColor;
4134                 if (curColor != ColorNormal) {
4135                     if (oldi > next_out) {
4136                         SendToPlayer(&buf[next_out], oldi - next_out);
4137                         next_out = oldi;
4138                     }
4139                     Colorize(ColorNormal, FALSE);
4140                     curColor = ColorNormal;
4141                 }
4142                 if (started == STARTED_BOARD) {
4143                     started = STARTED_NONE;
4144                     parse[parse_pos] = NULLCHAR;
4145                     ParseBoard12(parse);
4146                     ics_user_moved = 0;
4147
4148                     /* Send premove here */
4149                     if (appData.premove) {
4150                       char str[MSG_SIZ];
4151                       if (currentMove == 0 &&
4152                           gameMode == IcsPlayingWhite &&
4153                           appData.premoveWhite) {
4154                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4155                         if (appData.debugMode)
4156                           fprintf(debugFP, "Sending premove:\n");
4157                         SendToICS(str);
4158                       } else if (currentMove == 1 &&
4159                                  gameMode == IcsPlayingBlack &&
4160                                  appData.premoveBlack) {
4161                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4162                         if (appData.debugMode)
4163                           fprintf(debugFP, "Sending premove:\n");
4164                         SendToICS(str);
4165                       } else if (gotPremove) {
4166                         gotPremove = 0;
4167                         ClearPremoveHighlights();
4168                         if (appData.debugMode)
4169                           fprintf(debugFP, "Sending premove:\n");
4170                           UserMoveEvent(premoveFromX, premoveFromY,
4171                                         premoveToX, premoveToY,
4172                                         premovePromoChar);
4173                       }
4174                     }
4175
4176                     /* Usually suppress following prompt */
4177                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4178                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4179                         if (looking_at(buf, &i, "*% ")) {
4180                             savingComment = FALSE;
4181                             suppressKibitz = 0;
4182                         }
4183                     }
4184                     next_out = i;
4185                 } else if (started == STARTED_HOLDINGS) {
4186                     int gamenum;
4187                     char new_piece[MSG_SIZ];
4188                     started = STARTED_NONE;
4189                     parse[parse_pos] = NULLCHAR;
4190                     if (appData.debugMode)
4191                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4192                                                         parse, currentMove);
4193                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4194                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4195                         if (gameInfo.variant == VariantNormal) {
4196                           /* [HGM] We seem to switch variant during a game!
4197                            * Presumably no holdings were displayed, so we have
4198                            * to move the position two files to the right to
4199                            * create room for them!
4200                            */
4201                           VariantClass newVariant;
4202                           switch(gameInfo.boardWidth) { // base guess on board width
4203                                 case 9:  newVariant = VariantShogi; break;
4204                                 case 10: newVariant = VariantGreat; break;
4205                                 default: newVariant = VariantCrazyhouse; break;
4206                           }
4207                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4208                           /* Get a move list just to see the header, which
4209                              will tell us whether this is really bug or zh */
4210                           if (ics_getting_history == H_FALSE) {
4211                             ics_getting_history = H_REQUESTED;
4212                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4213                             SendToICS(str);
4214                           }
4215                         }
4216                         new_piece[0] = NULLCHAR;
4217                         sscanf(parse, "game %d white [%s black [%s <- %s",
4218                                &gamenum, white_holding, black_holding,
4219                                new_piece);
4220                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4221                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4222                         /* [HGM] copy holdings to board holdings area */
4223                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4224                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4225                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4226 #if ZIPPY
4227                         if (appData.zippyPlay && first.initDone) {
4228                             ZippyHoldings(white_holding, black_holding,
4229                                           new_piece);
4230                         }
4231 #endif /*ZIPPY*/
4232                         if (tinyLayout || smallLayout) {
4233                             char wh[16], bh[16];
4234                             PackHolding(wh, white_holding);
4235                             PackHolding(bh, black_holding);
4236                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4237                                     gameInfo.white, gameInfo.black);
4238                         } else {
4239                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4240                                     gameInfo.white, white_holding, _("vs."),
4241                                     gameInfo.black, black_holding);
4242                         }
4243                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4244                         DrawPosition(FALSE, boards[currentMove]);
4245                         DisplayTitle(str);
4246                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4247                         sscanf(parse, "game %d white [%s black [%s <- %s",
4248                                &gamenum, white_holding, black_holding,
4249                                new_piece);
4250                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4251                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4252                         /* [HGM] copy holdings to partner-board holdings area */
4253                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4254                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4255                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4256                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4257                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4258                       }
4259                     }
4260                     /* Suppress following prompt */
4261                     if (looking_at(buf, &i, "*% ")) {
4262                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4263                         savingComment = FALSE;
4264                         suppressKibitz = 0;
4265                     }
4266                     next_out = i;
4267                 }
4268                 continue;
4269             }
4270
4271             i++;                /* skip unparsed character and loop back */
4272         }
4273
4274         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4275 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4276 //          SendToPlayer(&buf[next_out], i - next_out);
4277             started != STARTED_HOLDINGS && leftover_start > next_out) {
4278             SendToPlayer(&buf[next_out], leftover_start - next_out);
4279             next_out = i;
4280         }
4281
4282         leftover_len = buf_len - leftover_start;
4283         /* if buffer ends with something we couldn't parse,
4284            reparse it after appending the next read */
4285
4286     } else if (count == 0) {
4287         RemoveInputSource(isr);
4288         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4289     } else {
4290         DisplayFatalError(_("Error reading from ICS"), error, 1);
4291     }
4292 }
4293
4294
4295 /* Board style 12 looks like this:
4296
4297    <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
4298
4299  * The "<12> " is stripped before it gets to this routine.  The two
4300  * trailing 0's (flip state and clock ticking) are later addition, and
4301  * some chess servers may not have them, or may have only the first.
4302  * Additional trailing fields may be added in the future.
4303  */
4304
4305 #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"
4306
4307 #define RELATION_OBSERVING_PLAYED    0
4308 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4309 #define RELATION_PLAYING_MYMOVE      1
4310 #define RELATION_PLAYING_NOTMYMOVE  -1
4311 #define RELATION_EXAMINING           2
4312 #define RELATION_ISOLATED_BOARD     -3
4313 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4314
4315 void
4316 ParseBoard12 (char *string)
4317 {
4318 #if ZIPPY
4319     int i, takeback;
4320     char *bookHit = NULL; // [HGM] book
4321 #endif
4322     GameMode newGameMode;
4323     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4324     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4325     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4326     char to_play, board_chars[200];
4327     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4328     char black[32], white[32];
4329     Board board;
4330     int prevMove = currentMove;
4331     int ticking = 2;
4332     ChessMove moveType;
4333     int fromX, fromY, toX, toY;
4334     char promoChar;
4335     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4336     Boolean weird = FALSE, reqFlag = FALSE;
4337
4338     fromX = fromY = toX = toY = -1;
4339
4340     newGame = FALSE;
4341
4342     if (appData.debugMode)
4343       fprintf(debugFP, "Parsing board: %s\n", string);
4344
4345     move_str[0] = NULLCHAR;
4346     elapsed_time[0] = NULLCHAR;
4347     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4348         int  i = 0, j;
4349         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4350             if(string[i] == ' ') { ranks++; files = 0; }
4351             else files++;
4352             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4353             i++;
4354         }
4355         for(j = 0; j <i; j++) board_chars[j] = string[j];
4356         board_chars[i] = '\0';
4357         string += i + 1;
4358     }
4359     n = sscanf(string, PATTERN, &to_play, &double_push,
4360                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4361                &gamenum, white, black, &relation, &basetime, &increment,
4362                &white_stren, &black_stren, &white_time, &black_time,
4363                &moveNum, str, elapsed_time, move_str, &ics_flip,
4364                &ticking);
4365
4366     if (n < 21) {
4367         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4368         DisplayError(str, 0);
4369         return;
4370     }
4371
4372     /* Convert the move number to internal form */
4373     moveNum = (moveNum - 1) * 2;
4374     if (to_play == 'B') moveNum++;
4375     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4376       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4377                         0, 1);
4378       return;
4379     }
4380
4381     switch (relation) {
4382       case RELATION_OBSERVING_PLAYED:
4383       case RELATION_OBSERVING_STATIC:
4384         if (gamenum == -1) {
4385             /* Old ICC buglet */
4386             relation = RELATION_OBSERVING_STATIC;
4387         }
4388         newGameMode = IcsObserving;
4389         break;
4390       case RELATION_PLAYING_MYMOVE:
4391       case RELATION_PLAYING_NOTMYMOVE:
4392         newGameMode =
4393           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4394             IcsPlayingWhite : IcsPlayingBlack;
4395         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4396         break;
4397       case RELATION_EXAMINING:
4398         newGameMode = IcsExamining;
4399         break;
4400       case RELATION_ISOLATED_BOARD:
4401       default:
4402         /* Just display this board.  If user was doing something else,
4403            we will forget about it until the next board comes. */
4404         newGameMode = IcsIdle;
4405         break;
4406       case RELATION_STARTING_POSITION:
4407         newGameMode = gameMode;
4408         break;
4409     }
4410
4411     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4412         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4413          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4414       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4415       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4416       static int lastBgGame = -1;
4417       char *toSqr;
4418       for (k = 0; k < ranks; k++) {
4419         for (j = 0; j < files; j++)
4420           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4421         if(gameInfo.holdingsWidth > 1) {
4422              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4423              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4424         }
4425       }
4426       CopyBoard(partnerBoard, board);
4427       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4428         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4429         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4430       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4431       if(toSqr = strchr(str, '-')) {
4432         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4433         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4434       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4435       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4436       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4437       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4438       if(twoBoards) {
4439           DisplayWhiteClock(white_time*fac, to_play == 'W');
4440           DisplayBlackClock(black_time*fac, to_play != 'W');
4441           activePartner = to_play;
4442           if(gamenum != lastBgGame) {
4443               char buf[MSG_SIZ];
4444               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4445               DisplayTitle(buf);
4446           }
4447           lastBgGame = gamenum;
4448           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4449                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4450       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4451                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4452       if(!twoBoards) DisplayMessage(partnerStatus, "");
4453         partnerBoardValid = TRUE;
4454       return;
4455     }
4456
4457     if(appData.dualBoard && appData.bgObserve) {
4458         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4459             SendToICS(ics_prefix), SendToICS("pobserve\n");
4460         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4461             char buf[MSG_SIZ];
4462             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4463             SendToICS(buf);
4464         }
4465     }
4466
4467     /* Modify behavior for initial board display on move listing
4468        of wild games.
4469        */
4470     switch (ics_getting_history) {
4471       case H_FALSE:
4472       case H_REQUESTED:
4473         break;
4474       case H_GOT_REQ_HEADER:
4475       case H_GOT_UNREQ_HEADER:
4476         /* This is the initial position of the current game */
4477         gamenum = ics_gamenum;
4478         moveNum = 0;            /* old ICS bug workaround */
4479         if (to_play == 'B') {
4480           startedFromSetupPosition = TRUE;
4481           blackPlaysFirst = TRUE;
4482           moveNum = 1;
4483           if (forwardMostMove == 0) forwardMostMove = 1;
4484           if (backwardMostMove == 0) backwardMostMove = 1;
4485           if (currentMove == 0) currentMove = 1;
4486         }
4487         newGameMode = gameMode;
4488         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4489         break;
4490       case H_GOT_UNWANTED_HEADER:
4491         /* This is an initial board that we don't want */
4492         return;
4493       case H_GETTING_MOVES:
4494         /* Should not happen */
4495         DisplayError(_("Error gathering move list: extra board"), 0);
4496         ics_getting_history = H_FALSE;
4497         return;
4498     }
4499
4500    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4501                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4502                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4503      /* [HGM] We seem to have switched variant unexpectedly
4504       * Try to guess new variant from board size
4505       */
4506           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4507           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4508           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4509           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4510           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4511           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4512           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4513           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4514           /* Get a move list just to see the header, which
4515              will tell us whether this is really bug or zh */
4516           if (ics_getting_history == H_FALSE) {
4517             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4518             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4519             SendToICS(str);
4520           }
4521     }
4522
4523     /* Take action if this is the first board of a new game, or of a
4524        different game than is currently being displayed.  */
4525     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4526         relation == RELATION_ISOLATED_BOARD) {
4527
4528         /* Forget the old game and get the history (if any) of the new one */
4529         if (gameMode != BeginningOfGame) {
4530           Reset(TRUE, TRUE);
4531         }
4532         newGame = TRUE;
4533         if (appData.autoRaiseBoard) BoardToTop();
4534         prevMove = -3;
4535         if (gamenum == -1) {
4536             newGameMode = IcsIdle;
4537         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4538                    appData.getMoveList && !reqFlag) {
4539             /* Need to get game history */
4540             ics_getting_history = H_REQUESTED;
4541             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4542             SendToICS(str);
4543         }
4544
4545         /* Initially flip the board to have black on the bottom if playing
4546            black or if the ICS flip flag is set, but let the user change
4547            it with the Flip View button. */
4548         flipView = appData.autoFlipView ?
4549           (newGameMode == IcsPlayingBlack) || ics_flip :
4550           appData.flipView;
4551
4552         /* Done with values from previous mode; copy in new ones */
4553         gameMode = newGameMode;
4554         ModeHighlight();
4555         ics_gamenum = gamenum;
4556         if (gamenum == gs_gamenum) {
4557             int klen = strlen(gs_kind);
4558             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4559             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4560             gameInfo.event = StrSave(str);
4561         } else {
4562             gameInfo.event = StrSave("ICS game");
4563         }
4564         gameInfo.site = StrSave(appData.icsHost);
4565         gameInfo.date = PGNDate();
4566         gameInfo.round = StrSave("-");
4567         gameInfo.white = StrSave(white);
4568         gameInfo.black = StrSave(black);
4569         timeControl = basetime * 60 * 1000;
4570         timeControl_2 = 0;
4571         timeIncrement = increment * 1000;
4572         movesPerSession = 0;
4573         gameInfo.timeControl = TimeControlTagValue();
4574         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4575   if (appData.debugMode) {
4576     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4577     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4578     setbuf(debugFP, NULL);
4579   }
4580
4581         gameInfo.outOfBook = NULL;
4582
4583         /* Do we have the ratings? */
4584         if (strcmp(player1Name, white) == 0 &&
4585             strcmp(player2Name, black) == 0) {
4586             if (appData.debugMode)
4587               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4588                       player1Rating, player2Rating);
4589             gameInfo.whiteRating = player1Rating;
4590             gameInfo.blackRating = player2Rating;
4591         } else if (strcmp(player2Name, white) == 0 &&
4592                    strcmp(player1Name, black) == 0) {
4593             if (appData.debugMode)
4594               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4595                       player2Rating, player1Rating);
4596             gameInfo.whiteRating = player2Rating;
4597             gameInfo.blackRating = player1Rating;
4598         }
4599         player1Name[0] = player2Name[0] = NULLCHAR;
4600
4601         /* Silence shouts if requested */
4602         if (appData.quietPlay &&
4603             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4604             SendToICS(ics_prefix);
4605             SendToICS("set shout 0\n");
4606         }
4607     }
4608
4609     /* Deal with midgame name changes */
4610     if (!newGame) {
4611         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4612             if (gameInfo.white) free(gameInfo.white);
4613             gameInfo.white = StrSave(white);
4614         }
4615         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4616             if (gameInfo.black) free(gameInfo.black);
4617             gameInfo.black = StrSave(black);
4618         }
4619     }
4620
4621     /* Throw away game result if anything actually changes in examine mode */
4622     if (gameMode == IcsExamining && !newGame) {
4623         gameInfo.result = GameUnfinished;
4624         if (gameInfo.resultDetails != NULL) {
4625             free(gameInfo.resultDetails);
4626             gameInfo.resultDetails = NULL;
4627         }
4628     }
4629
4630     /* In pausing && IcsExamining mode, we ignore boards coming
4631        in if they are in a different variation than we are. */
4632     if (pauseExamInvalid) return;
4633     if (pausing && gameMode == IcsExamining) {
4634         if (moveNum <= pauseExamForwardMostMove) {
4635             pauseExamInvalid = TRUE;
4636             forwardMostMove = pauseExamForwardMostMove;
4637             return;
4638         }
4639     }
4640
4641   if (appData.debugMode) {
4642     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4643   }
4644     /* Parse the board */
4645     for (k = 0; k < ranks; k++) {
4646       for (j = 0; j < files; j++)
4647         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4648       if(gameInfo.holdingsWidth > 1) {
4649            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4650            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4651       }
4652     }
4653     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4654       board[5][BOARD_RGHT+1] = WhiteAngel;
4655       board[6][BOARD_RGHT+1] = WhiteMarshall;
4656       board[1][0] = BlackMarshall;
4657       board[2][0] = BlackAngel;
4658       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4659     }
4660     CopyBoard(boards[moveNum], board);
4661     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4662     if (moveNum == 0) {
4663         startedFromSetupPosition =
4664           !CompareBoards(board, initialPosition);
4665         if(startedFromSetupPosition)
4666             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4667     }
4668
4669     /* [HGM] Set castling rights. Take the outermost Rooks,
4670        to make it also work for FRC opening positions. Note that board12
4671        is really defective for later FRC positions, as it has no way to
4672        indicate which Rook can castle if they are on the same side of King.
4673        For the initial position we grant rights to the outermost Rooks,
4674        and remember thos rights, and we then copy them on positions
4675        later in an FRC game. This means WB might not recognize castlings with
4676        Rooks that have moved back to their original position as illegal,
4677        but in ICS mode that is not its job anyway.
4678     */
4679     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4680     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4681
4682         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4683             if(board[0][i] == WhiteRook) j = i;
4684         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4685         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4686             if(board[0][i] == WhiteRook) j = i;
4687         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4688         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4689             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4690         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4691         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4692             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4693         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4694
4695         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4696         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4697         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4698             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4699         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4700             if(board[BOARD_HEIGHT-1][k] == bKing)
4701                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4702         if(gameInfo.variant == VariantTwoKings) {
4703             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4704             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4705             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4706         }
4707     } else { int r;
4708         r = boards[moveNum][CASTLING][0] = initialRights[0];
4709         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4710         r = boards[moveNum][CASTLING][1] = initialRights[1];
4711         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4712         r = boards[moveNum][CASTLING][3] = initialRights[3];
4713         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4714         r = boards[moveNum][CASTLING][4] = initialRights[4];
4715         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4716         /* wildcastle kludge: always assume King has rights */
4717         r = boards[moveNum][CASTLING][2] = initialRights[2];
4718         r = boards[moveNum][CASTLING][5] = initialRights[5];
4719     }
4720     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4721     boards[moveNum][EP_STATUS] = EP_NONE;
4722     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4723     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4724     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4725
4726
4727     if (ics_getting_history == H_GOT_REQ_HEADER ||
4728         ics_getting_history == H_GOT_UNREQ_HEADER) {
4729         /* This was an initial position from a move list, not
4730            the current position */
4731         return;
4732     }
4733
4734     /* Update currentMove and known move number limits */
4735     newMove = newGame || moveNum > forwardMostMove;
4736
4737     if (newGame) {
4738         forwardMostMove = backwardMostMove = currentMove = moveNum;
4739         if (gameMode == IcsExamining && moveNum == 0) {
4740           /* Workaround for ICS limitation: we are not told the wild
4741              type when starting to examine a game.  But if we ask for
4742              the move list, the move list header will tell us */
4743             ics_getting_history = H_REQUESTED;
4744             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4745             SendToICS(str);
4746         }
4747     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4748                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4749 #if ZIPPY
4750         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4751         /* [HGM] applied this also to an engine that is silently watching        */
4752         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4753             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4754             gameInfo.variant == currentlyInitializedVariant) {
4755           takeback = forwardMostMove - moveNum;
4756           for (i = 0; i < takeback; i++) {
4757             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4758             SendToProgram("undo\n", &first);
4759           }
4760         }
4761 #endif
4762
4763         forwardMostMove = moveNum;
4764         if (!pausing || currentMove > forwardMostMove)
4765           currentMove = forwardMostMove;
4766     } else {
4767         /* New part of history that is not contiguous with old part */
4768         if (pausing && gameMode == IcsExamining) {
4769             pauseExamInvalid = TRUE;
4770             forwardMostMove = pauseExamForwardMostMove;
4771             return;
4772         }
4773         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4774 #if ZIPPY
4775             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4776                 // [HGM] when we will receive the move list we now request, it will be
4777                 // fed to the engine from the first move on. So if the engine is not
4778                 // in the initial position now, bring it there.
4779                 InitChessProgram(&first, 0);
4780             }
4781 #endif
4782             ics_getting_history = H_REQUESTED;
4783             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4784             SendToICS(str);
4785         }
4786         forwardMostMove = backwardMostMove = currentMove = moveNum;
4787     }
4788
4789     /* Update the clocks */
4790     if (strchr(elapsed_time, '.')) {
4791       /* Time is in ms */
4792       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4793       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4794     } else {
4795       /* Time is in seconds */
4796       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4797       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4798     }
4799
4800
4801 #if ZIPPY
4802     if (appData.zippyPlay && newGame &&
4803         gameMode != IcsObserving && gameMode != IcsIdle &&
4804         gameMode != IcsExamining)
4805       ZippyFirstBoard(moveNum, basetime, increment);
4806 #endif
4807
4808     /* Put the move on the move list, first converting
4809        to canonical algebraic form. */
4810     if (moveNum > 0) {
4811   if (appData.debugMode) {
4812     int f = forwardMostMove;
4813     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4814             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4815             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4816     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4817     fprintf(debugFP, "moveNum = %d\n", moveNum);
4818     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4819     setbuf(debugFP, NULL);
4820   }
4821         if (moveNum <= backwardMostMove) {
4822             /* We don't know what the board looked like before
4823                this move.  Punt. */
4824           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4825             strcat(parseList[moveNum - 1], " ");
4826             strcat(parseList[moveNum - 1], elapsed_time);
4827             moveList[moveNum - 1][0] = NULLCHAR;
4828         } else if (strcmp(move_str, "none") == 0) {
4829             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4830             /* Again, we don't know what the board looked like;
4831                this is really the start of the game. */
4832             parseList[moveNum - 1][0] = NULLCHAR;
4833             moveList[moveNum - 1][0] = NULLCHAR;
4834             backwardMostMove = moveNum;
4835             startedFromSetupPosition = TRUE;
4836             fromX = fromY = toX = toY = -1;
4837         } else {
4838           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4839           //                 So we parse the long-algebraic move string in stead of the SAN move
4840           int valid; char buf[MSG_SIZ], *prom;
4841
4842           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4843                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4844           // str looks something like "Q/a1-a2"; kill the slash
4845           if(str[1] == '/')
4846             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4847           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4848           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4849                 strcat(buf, prom); // long move lacks promo specification!
4850           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4851                 if(appData.debugMode)
4852                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4853                 safeStrCpy(move_str, buf, MSG_SIZ);
4854           }
4855           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4856                                 &fromX, &fromY, &toX, &toY, &promoChar)
4857                || ParseOneMove(buf, moveNum - 1, &moveType,
4858                                 &fromX, &fromY, &toX, &toY, &promoChar);
4859           // end of long SAN patch
4860           if (valid) {
4861             (void) CoordsToAlgebraic(boards[moveNum - 1],
4862                                      PosFlags(moveNum - 1),
4863                                      fromY, fromX, toY, toX, promoChar,
4864                                      parseList[moveNum-1]);
4865             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4866               case MT_NONE:
4867               case MT_STALEMATE:
4868               default:
4869                 break;
4870               case MT_CHECK:
4871                 if(!IS_SHOGI(gameInfo.variant))
4872                     strcat(parseList[moveNum - 1], "+");
4873                 break;
4874               case MT_CHECKMATE:
4875               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4876                 strcat(parseList[moveNum - 1], "#");
4877                 break;
4878             }
4879             strcat(parseList[moveNum - 1], " ");
4880             strcat(parseList[moveNum - 1], elapsed_time);
4881             /* currentMoveString is set as a side-effect of ParseOneMove */
4882             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4883             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4884             strcat(moveList[moveNum - 1], "\n");
4885
4886             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4887                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4888               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4889                 ChessSquare old, new = boards[moveNum][k][j];
4890                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4891                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4892                   if(old == new) continue;
4893                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4894                   else if(new == WhiteWazir || new == BlackWazir) {
4895                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4896                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4897                       else boards[moveNum][k][j] = old; // preserve type of Gold
4898                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4899                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4900               }
4901           } else {
4902             /* Move from ICS was illegal!?  Punt. */
4903             if (appData.debugMode) {
4904               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4905               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4906             }
4907             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4908             strcat(parseList[moveNum - 1], " ");
4909             strcat(parseList[moveNum - 1], elapsed_time);
4910             moveList[moveNum - 1][0] = NULLCHAR;
4911             fromX = fromY = toX = toY = -1;
4912           }
4913         }
4914   if (appData.debugMode) {
4915     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4916     setbuf(debugFP, NULL);
4917   }
4918
4919 #if ZIPPY
4920         /* Send move to chess program (BEFORE animating it). */
4921         if (appData.zippyPlay && !newGame && newMove &&
4922            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4923
4924             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4925                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4926                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4927                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4928                             move_str);
4929                     DisplayError(str, 0);
4930                 } else {
4931                     if (first.sendTime) {
4932                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4933                     }
4934                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4935                     if (firstMove && !bookHit) {
4936                         firstMove = FALSE;
4937                         if (first.useColors) {
4938                           SendToProgram(gameMode == IcsPlayingWhite ?
4939                                         "white\ngo\n" :
4940                                         "black\ngo\n", &first);
4941                         } else {
4942                           SendToProgram("go\n", &first);
4943                         }
4944                         first.maybeThinking = TRUE;
4945                     }
4946                 }
4947             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4948               if (moveList[moveNum - 1][0] == NULLCHAR) {
4949                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4950                 DisplayError(str, 0);
4951               } else {
4952                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4953                 SendMoveToProgram(moveNum - 1, &first);
4954               }
4955             }
4956         }
4957 #endif
4958     }
4959
4960     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4961         /* If move comes from a remote source, animate it.  If it
4962            isn't remote, it will have already been animated. */
4963         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4964             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4965         }
4966         if (!pausing && appData.highlightLastMove) {
4967             SetHighlights(fromX, fromY, toX, toY);
4968         }
4969     }
4970
4971     /* Start the clocks */
4972     whiteFlag = blackFlag = FALSE;
4973     appData.clockMode = !(basetime == 0 && increment == 0);
4974     if (ticking == 0) {
4975       ics_clock_paused = TRUE;
4976       StopClocks();
4977     } else if (ticking == 1) {
4978       ics_clock_paused = FALSE;
4979     }
4980     if (gameMode == IcsIdle ||
4981         relation == RELATION_OBSERVING_STATIC ||
4982         relation == RELATION_EXAMINING ||
4983         ics_clock_paused)
4984       DisplayBothClocks();
4985     else
4986       StartClocks();
4987
4988     /* Display opponents and material strengths */
4989     if (gameInfo.variant != VariantBughouse &&
4990         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4991         if (tinyLayout || smallLayout) {
4992             if(gameInfo.variant == VariantNormal)
4993               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4994                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4995                     basetime, increment);
4996             else
4997               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4998                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4999                     basetime, increment, (int) gameInfo.variant);
5000         } else {
5001             if(gameInfo.variant == VariantNormal)
5002               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5003                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5004                     basetime, increment);
5005             else
5006               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5007                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5008                     basetime, increment, VariantName(gameInfo.variant));
5009         }
5010         DisplayTitle(str);
5011   if (appData.debugMode) {
5012     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5013   }
5014     }
5015
5016
5017     /* Display the board */
5018     if (!pausing && !appData.noGUI) {
5019
5020       if (appData.premove)
5021           if (!gotPremove ||
5022              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5023              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5024               ClearPremoveHighlights();
5025
5026       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5027         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5028       DrawPosition(j, boards[currentMove]);
5029
5030       DisplayMove(moveNum - 1);
5031       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5032             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5033               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5034         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5035       }
5036     }
5037
5038     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5039 #if ZIPPY
5040     if(bookHit) { // [HGM] book: simulate book reply
5041         static char bookMove[MSG_SIZ]; // a bit generous?
5042
5043         programStats.nodes = programStats.depth = programStats.time =
5044         programStats.score = programStats.got_only_move = 0;
5045         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5046
5047         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5048         strcat(bookMove, bookHit);
5049         HandleMachineMove(bookMove, &first);
5050     }
5051 #endif
5052 }
5053
5054 void
5055 GetMoveListEvent ()
5056 {
5057     char buf[MSG_SIZ];
5058     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5059         ics_getting_history = H_REQUESTED;
5060         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5061         SendToICS(buf);
5062     }
5063 }
5064
5065 void
5066 SendToBoth (char *msg)
5067 {   // to make it easy to keep two engines in step in dual analysis
5068     SendToProgram(msg, &first);
5069     if(second.analyzing) SendToProgram(msg, &second);
5070 }
5071
5072 void
5073 AnalysisPeriodicEvent (int force)
5074 {
5075     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5076          && !force) || !appData.periodicUpdates)
5077       return;
5078
5079     /* Send . command to Crafty to collect stats */
5080     SendToBoth(".\n");
5081
5082     /* Don't send another until we get a response (this makes
5083        us stop sending to old Crafty's which don't understand
5084        the "." command (sending illegal cmds resets node count & time,
5085        which looks bad)) */
5086     programStats.ok_to_send = 0;
5087 }
5088
5089 void
5090 ics_update_width (int new_width)
5091 {
5092         ics_printf("set width %d\n", new_width);
5093 }
5094
5095 void
5096 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5097 {
5098     char buf[MSG_SIZ];
5099
5100     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5101         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5102             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5103             SendToProgram(buf, cps);
5104             return;
5105         }
5106         // null move in variant where engine does not understand it (for analysis purposes)
5107         SendBoard(cps, moveNum + 1); // send position after move in stead.
5108         return;
5109     }
5110     if (cps->useUsermove) {
5111       SendToProgram("usermove ", cps);
5112     }
5113     if (cps->useSAN) {
5114       char *space;
5115       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5116         int len = space - parseList[moveNum];
5117         memcpy(buf, parseList[moveNum], len);
5118         buf[len++] = '\n';
5119         buf[len] = NULLCHAR;
5120       } else {
5121         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5122       }
5123       SendToProgram(buf, cps);
5124     } else {
5125       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5126         AlphaRank(moveList[moveNum], 4);
5127         SendToProgram(moveList[moveNum], cps);
5128         AlphaRank(moveList[moveNum], 4); // and back
5129       } else
5130       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5131        * the engine. It would be nice to have a better way to identify castle
5132        * moves here. */
5133       if(appData.fischerCastling && cps->useOOCastle) {
5134         int fromX = moveList[moveNum][0] - AAA;
5135         int fromY = moveList[moveNum][1] - ONE;
5136         int toX = moveList[moveNum][2] - AAA;
5137         int toY = moveList[moveNum][3] - ONE;
5138         if((boards[moveNum][fromY][fromX] == WhiteKing
5139             && boards[moveNum][toY][toX] == WhiteRook)
5140            || (boards[moveNum][fromY][fromX] == BlackKing
5141                && boards[moveNum][toY][toX] == BlackRook)) {
5142           if(toX > fromX) SendToProgram("O-O\n", cps);
5143           else SendToProgram("O-O-O\n", cps);
5144         }
5145         else SendToProgram(moveList[moveNum], cps);
5146       } else
5147       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5148         char *m = moveList[moveNum];
5149         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
5150           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5151                                                m[2], m[3] - '0',
5152                                                m[5], m[6] - '0',
5153                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5154         else
5155           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5156                                                m[5], m[6] - '0',
5157                                                m[5], m[6] - '0',
5158                                                m[2], m[3] - '0');
5159           SendToProgram(buf, cps);
5160       } else
5161       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5162         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5163           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5164           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5165                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5166         } else
5167           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5168                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5169         SendToProgram(buf, cps);
5170       }
5171       else SendToProgram(moveList[moveNum], cps);
5172       /* End of additions by Tord */
5173     }
5174
5175     /* [HGM] setting up the opening has brought engine in force mode! */
5176     /*       Send 'go' if we are in a mode where machine should play. */
5177     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5178         (gameMode == TwoMachinesPlay   ||
5179 #if ZIPPY
5180          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5181 #endif
5182          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5183         SendToProgram("go\n", cps);
5184   if (appData.debugMode) {
5185     fprintf(debugFP, "(extra)\n");
5186   }
5187     }
5188     setboardSpoiledMachineBlack = 0;
5189 }
5190
5191 void
5192 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5193 {
5194     char user_move[MSG_SIZ];
5195     char suffix[4];
5196
5197     if(gameInfo.variant == VariantSChess && promoChar) {
5198         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5199         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5200     } else suffix[0] = NULLCHAR;
5201
5202     switch (moveType) {
5203       default:
5204         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5205                 (int)moveType, fromX, fromY, toX, toY);
5206         DisplayError(user_move + strlen("say "), 0);
5207         break;
5208       case WhiteKingSideCastle:
5209       case BlackKingSideCastle:
5210       case WhiteQueenSideCastleWild:
5211       case BlackQueenSideCastleWild:
5212       /* PUSH Fabien */
5213       case WhiteHSideCastleFR:
5214       case BlackHSideCastleFR:
5215       /* POP Fabien */
5216         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5217         break;
5218       case WhiteQueenSideCastle:
5219       case BlackQueenSideCastle:
5220       case WhiteKingSideCastleWild:
5221       case BlackKingSideCastleWild:
5222       /* PUSH Fabien */
5223       case WhiteASideCastleFR:
5224       case BlackASideCastleFR:
5225       /* POP Fabien */
5226         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5227         break;
5228       case WhiteNonPromotion:
5229       case BlackNonPromotion:
5230         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5231         break;
5232       case WhitePromotion:
5233       case BlackPromotion:
5234         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5235            gameInfo.variant == VariantMakruk)
5236           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5237                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5238                 PieceToChar(WhiteFerz));
5239         else if(gameInfo.variant == VariantGreat)
5240           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5241                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5242                 PieceToChar(WhiteMan));
5243         else
5244           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5245                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5246                 promoChar);
5247         break;
5248       case WhiteDrop:
5249       case BlackDrop:
5250       drop:
5251         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5252                  ToUpper(PieceToChar((ChessSquare) fromX)),
5253                  AAA + toX, ONE + toY);
5254         break;
5255       case IllegalMove:  /* could be a variant we don't quite understand */
5256         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5257       case NormalMove:
5258       case WhiteCapturesEnPassant:
5259       case BlackCapturesEnPassant:
5260         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5261                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5262         break;
5263     }
5264     SendToICS(user_move);
5265     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5266         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5267 }
5268
5269 void
5270 UploadGameEvent ()
5271 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5272     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5273     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5274     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5275       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5276       return;
5277     }
5278     if(gameMode != IcsExamining) { // is this ever not the case?
5279         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5280
5281         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5282           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5283         } else { // on FICS we must first go to general examine mode
5284           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5285         }
5286         if(gameInfo.variant != VariantNormal) {
5287             // try figure out wild number, as xboard names are not always valid on ICS
5288             for(i=1; i<=36; i++) {
5289               snprintf(buf, MSG_SIZ, "wild/%d", i);
5290                 if(StringToVariant(buf) == gameInfo.variant) break;
5291             }
5292             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5293             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5294             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5295         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5296         SendToICS(ics_prefix);
5297         SendToICS(buf);
5298         if(startedFromSetupPosition || backwardMostMove != 0) {
5299           fen = PositionToFEN(backwardMostMove, NULL, 1);
5300           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5301             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5302             SendToICS(buf);
5303           } else { // FICS: everything has to set by separate bsetup commands
5304             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5305             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5306             SendToICS(buf);
5307             if(!WhiteOnMove(backwardMostMove)) {
5308                 SendToICS("bsetup tomove black\n");
5309             }
5310             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5311             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5312             SendToICS(buf);
5313             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5314             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5315             SendToICS(buf);
5316             i = boards[backwardMostMove][EP_STATUS];
5317             if(i >= 0) { // set e.p.
5318               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5319                 SendToICS(buf);
5320             }
5321             bsetup++;
5322           }
5323         }
5324       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5325             SendToICS("bsetup done\n"); // switch to normal examining.
5326     }
5327     for(i = backwardMostMove; i<last; i++) {
5328         char buf[20];
5329         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5330         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5331             int len = strlen(moveList[i]);
5332             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5333             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5334         }
5335         SendToICS(buf);
5336     }
5337     SendToICS(ics_prefix);
5338     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5339 }
5340
5341 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5342 int legNr = 1;
5343
5344 void
5345 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5346 {
5347     if (rf == DROP_RANK) {
5348       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5349       sprintf(move, "%c@%c%c\n",
5350                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5351     } else {
5352         if (promoChar == 'x' || promoChar == NULLCHAR) {
5353           sprintf(move, "%c%c%c%c\n",
5354                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5355           if(killX >= 0 && killY >= 0) {
5356             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5357             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5358           }
5359         } else {
5360             sprintf(move, "%c%c%c%c%c\n",
5361                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5362         }
5363     }
5364 }
5365
5366 void
5367 ProcessICSInitScript (FILE *f)
5368 {
5369     char buf[MSG_SIZ];
5370
5371     while (fgets(buf, MSG_SIZ, f)) {
5372         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5373     }
5374
5375     fclose(f);
5376 }
5377
5378
5379 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5380 int dragging;
5381 static ClickType lastClickType;
5382
5383 int
5384 PieceInString (char *s, ChessSquare piece)
5385 {
5386   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5387   while((p = strchr(s, ID))) {
5388     if(!suffix || p[1] == suffix) return TRUE;
5389     s = p;
5390   }
5391   return FALSE;
5392 }
5393
5394 int
5395 Partner (ChessSquare *p)
5396 { // change piece into promotion partner if one shogi-promotes to the other
5397   ChessSquare partner = promoPartner[*p];
5398   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5399   *p = partner;
5400   return 1;
5401 }
5402
5403 void
5404 Sweep (int step)
5405 {
5406     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5407     static int toggleFlag;
5408     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5409     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5410     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5411     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5412     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5413     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5414     do {
5415         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5416         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5417         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5418         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5419         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5420         if(!step) step = -1;
5421     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5422             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5423             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5424             promoSweep == pawn ||
5425             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5426             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5427     if(toX >= 0) {
5428         int victim = boards[currentMove][toY][toX];
5429         boards[currentMove][toY][toX] = promoSweep;
5430         DrawPosition(FALSE, boards[currentMove]);
5431         boards[currentMove][toY][toX] = victim;
5432     } else
5433     ChangeDragPiece(promoSweep);
5434 }
5435
5436 int
5437 PromoScroll (int x, int y)
5438 {
5439   int step = 0;
5440
5441   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5442   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5443   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5444   if(!step) return FALSE;
5445   lastX = x; lastY = y;
5446   if((promoSweep < BlackPawn) == flipView) step = -step;
5447   if(step > 0) selectFlag = 1;
5448   if(!selectFlag) Sweep(step);
5449   return FALSE;
5450 }
5451
5452 void
5453 NextPiece (int step)
5454 {
5455     ChessSquare piece = boards[currentMove][toY][toX];
5456     do {
5457         pieceSweep -= step;
5458         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5459         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5460         if(!step) step = -1;
5461     } while(PieceToChar(pieceSweep) == '.');
5462     boards[currentMove][toY][toX] = pieceSweep;
5463     DrawPosition(FALSE, boards[currentMove]);
5464     boards[currentMove][toY][toX] = piece;
5465 }
5466 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5467 void
5468 AlphaRank (char *move, int n)
5469 {
5470 //    char *p = move, c; int x, y;
5471
5472     if (appData.debugMode) {
5473         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5474     }
5475
5476     if(move[1]=='*' &&
5477        move[2]>='0' && move[2]<='9' &&
5478        move[3]>='a' && move[3]<='x'    ) {
5479         move[1] = '@';
5480         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5481         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5482     } else
5483     if(move[0]>='0' && move[0]<='9' &&
5484        move[1]>='a' && move[1]<='x' &&
5485        move[2]>='0' && move[2]<='9' &&
5486        move[3]>='a' && move[3]<='x'    ) {
5487         /* input move, Shogi -> normal */
5488         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5489         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5490         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5491         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5492     } else
5493     if(move[1]=='@' &&
5494        move[3]>='0' && move[3]<='9' &&
5495        move[2]>='a' && move[2]<='x'    ) {
5496         move[1] = '*';
5497         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5498         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5499     } else
5500     if(
5501        move[0]>='a' && move[0]<='x' &&
5502        move[3]>='0' && move[3]<='9' &&
5503        move[2]>='a' && move[2]<='x'    ) {
5504          /* output move, normal -> Shogi */
5505         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5506         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5507         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5508         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5509         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5510     }
5511     if (appData.debugMode) {
5512         fprintf(debugFP, "   out = '%s'\n", move);
5513     }
5514 }
5515
5516 char yy_textstr[8000];
5517
5518 /* Parser for moves from gnuchess, ICS, or user typein box */
5519 Boolean
5520 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5521 {
5522     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5523
5524     switch (*moveType) {
5525       case WhitePromotion:
5526       case BlackPromotion:
5527       case WhiteNonPromotion:
5528       case BlackNonPromotion:
5529       case NormalMove:
5530       case FirstLeg:
5531       case WhiteCapturesEnPassant:
5532       case BlackCapturesEnPassant:
5533       case WhiteKingSideCastle:
5534       case WhiteQueenSideCastle:
5535       case BlackKingSideCastle:
5536       case BlackQueenSideCastle:
5537       case WhiteKingSideCastleWild:
5538       case WhiteQueenSideCastleWild:
5539       case BlackKingSideCastleWild:
5540       case BlackQueenSideCastleWild:
5541       /* Code added by Tord: */
5542       case WhiteHSideCastleFR:
5543       case WhiteASideCastleFR:
5544       case BlackHSideCastleFR:
5545       case BlackASideCastleFR:
5546       /* End of code added by Tord */
5547       case IllegalMove:         /* bug or odd chess variant */
5548         if(currentMoveString[1] == '@') { // illegal drop
5549           *fromX = WhiteOnMove(moveNum) ?
5550             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5551             (int) CharToPiece(ToLower(currentMoveString[0]));
5552           goto drop;
5553         }
5554         *fromX = currentMoveString[0] - AAA;
5555         *fromY = currentMoveString[1] - ONE;
5556         *toX = currentMoveString[2] - AAA;
5557         *toY = currentMoveString[3] - ONE;
5558         *promoChar = currentMoveString[4];
5559         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5560             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5561     if (appData.debugMode) {
5562         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5563     }
5564             *fromX = *fromY = *toX = *toY = 0;
5565             return FALSE;
5566         }
5567         if (appData.testLegality) {
5568           return (*moveType != IllegalMove);
5569         } else {
5570           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5571                          // [HGM] lion: if this is a double move we are less critical
5572                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5573         }
5574
5575       case WhiteDrop:
5576       case BlackDrop:
5577         *fromX = *moveType == WhiteDrop ?
5578           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5579           (int) CharToPiece(ToLower(currentMoveString[0]));
5580       drop:
5581         *fromY = DROP_RANK;
5582         *toX = currentMoveString[2] - AAA;
5583         *toY = currentMoveString[3] - ONE;
5584         *promoChar = NULLCHAR;
5585         return TRUE;
5586
5587       case AmbiguousMove:
5588       case ImpossibleMove:
5589       case EndOfFile:
5590       case ElapsedTime:
5591       case Comment:
5592       case PGNTag:
5593       case NAG:
5594       case WhiteWins:
5595       case BlackWins:
5596       case GameIsDrawn:
5597       default:
5598     if (appData.debugMode) {
5599         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5600     }
5601         /* bug? */
5602         *fromX = *fromY = *toX = *toY = 0;
5603         *promoChar = NULLCHAR;
5604         return FALSE;
5605     }
5606 }
5607
5608 Boolean pushed = FALSE;
5609 char *lastParseAttempt;
5610
5611 void
5612 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5613 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5614   int fromX, fromY, toX, toY; char promoChar;
5615   ChessMove moveType;
5616   Boolean valid;
5617   int nr = 0;
5618
5619   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5620   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5621     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5622     pushed = TRUE;
5623   }
5624   endPV = forwardMostMove;
5625   do {
5626     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5627     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5628     lastParseAttempt = pv;
5629     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5630     if(!valid && nr == 0 &&
5631        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5632         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5633         // Hande case where played move is different from leading PV move
5634         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5635         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5636         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5637         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5638           endPV += 2; // if position different, keep this
5639           moveList[endPV-1][0] = fromX + AAA;
5640           moveList[endPV-1][1] = fromY + ONE;
5641           moveList[endPV-1][2] = toX + AAA;
5642           moveList[endPV-1][3] = toY + ONE;
5643           parseList[endPV-1][0] = NULLCHAR;
5644           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5645         }
5646       }
5647     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5648     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5649     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5650     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5651         valid++; // allow comments in PV
5652         continue;
5653     }
5654     nr++;
5655     if(endPV+1 > framePtr) break; // no space, truncate
5656     if(!valid) break;
5657     endPV++;
5658     CopyBoard(boards[endPV], boards[endPV-1]);
5659     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5660     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5661     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5662     CoordsToAlgebraic(boards[endPV - 1],
5663                              PosFlags(endPV - 1),
5664                              fromY, fromX, toY, toX, promoChar,
5665                              parseList[endPV - 1]);
5666   } while(valid);
5667   if(atEnd == 2) return; // used hidden, for PV conversion
5668   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5669   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5670   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5671                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5672   DrawPosition(TRUE, boards[currentMove]);
5673 }
5674
5675 int
5676 MultiPV (ChessProgramState *cps, int kind)
5677 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5678         int i;
5679         for(i=0; i<cps->nrOptions; i++) {
5680             char *s = cps->option[i].name;
5681             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5682             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5683                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5684         }
5685         return -1;
5686 }
5687
5688 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5689 static int multi, pv_margin;
5690 static ChessProgramState *activeCps;
5691
5692 Boolean
5693 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5694 {
5695         int startPV, lineStart, origIndex = index;
5696         char *p, buf2[MSG_SIZ];
5697         ChessProgramState *cps = (pane ? &second : &first);
5698
5699         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5700         lastX = x; lastY = y;
5701         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5702         lineStart = startPV = index;
5703         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5704         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5705         index = startPV;
5706         do{ while(buf[index] && buf[index] != '\n') index++;
5707         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5708         buf[index] = 0;
5709         if(lineStart == 0 && gameMode == AnalyzeMode) {
5710             int n = 0;
5711             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5712             if(n == 0) { // click not on "fewer" or "more"
5713                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5714                     pv_margin = cps->option[multi].value;
5715                     activeCps = cps; // non-null signals margin adjustment
5716                 }
5717             } else if((multi = MultiPV(cps, 1)) >= 0) {
5718                 n += cps->option[multi].value; if(n < 1) n = 1;
5719                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5720                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5721                 cps->option[multi].value = n;
5722                 *start = *end = 0;
5723                 return FALSE;
5724             }
5725         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5726                 ExcludeClick(origIndex - lineStart);
5727                 return FALSE;
5728         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5729                 Collapse(origIndex - lineStart);
5730                 return FALSE;
5731         }
5732         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5733         *start = startPV; *end = index-1;
5734         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5735         return TRUE;
5736 }
5737
5738 char *
5739 PvToSAN (char *pv)
5740 {
5741         static char buf[10*MSG_SIZ];
5742         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5743         *buf = NULLCHAR;
5744         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5745         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5746         for(i = forwardMostMove; i<endPV; i++){
5747             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5748             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5749             k += strlen(buf+k);
5750         }
5751         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5752         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5753         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5754         endPV = savedEnd;
5755         return buf;
5756 }
5757
5758 Boolean
5759 LoadPV (int x, int y)
5760 { // called on right mouse click to load PV
5761   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5762   lastX = x; lastY = y;
5763   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5764   extendGame = FALSE;
5765   return TRUE;
5766 }
5767
5768 void
5769 UnLoadPV ()
5770 {
5771   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5772   if(activeCps) {
5773     if(pv_margin != activeCps->option[multi].value) {
5774       char buf[MSG_SIZ];
5775       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5776       SendToProgram(buf, activeCps);
5777       activeCps->option[multi].value = pv_margin;
5778     }
5779     activeCps = NULL;
5780     return;
5781   }
5782   if(endPV < 0) return;
5783   if(appData.autoCopyPV) CopyFENToClipboard();
5784   endPV = -1;
5785   if(extendGame && currentMove > forwardMostMove) {
5786         Boolean saveAnimate = appData.animate;
5787         if(pushed) {
5788             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5789                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5790             } else storedGames--; // abandon shelved tail of original game
5791         }
5792         pushed = FALSE;
5793         forwardMostMove = currentMove;
5794         currentMove = oldFMM;
5795         appData.animate = FALSE;
5796         ToNrEvent(forwardMostMove);
5797         appData.animate = saveAnimate;
5798   }
5799   currentMove = forwardMostMove;
5800   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5801   ClearPremoveHighlights();
5802   DrawPosition(TRUE, boards[currentMove]);
5803 }
5804
5805 void
5806 MovePV (int x, int y, int h)
5807 { // step through PV based on mouse coordinates (called on mouse move)
5808   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5809
5810   if(activeCps) { // adjusting engine's multi-pv margin
5811     if(x > lastX) pv_margin++; else
5812     if(x < lastX) pv_margin -= (pv_margin > 0);
5813     if(x != lastX) {
5814       char buf[MSG_SIZ];
5815       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5816       DisplayMessage(buf, "");
5817     }
5818     lastX = x;
5819     return;
5820   }
5821   // we must somehow check if right button is still down (might be released off board!)
5822   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5823   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5824   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5825   if(!step) return;
5826   lastX = x; lastY = y;
5827
5828   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5829   if(endPV < 0) return;
5830   if(y < margin) step = 1; else
5831   if(y > h - margin) step = -1;
5832   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5833   currentMove += step;
5834   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5835   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5836                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5837   DrawPosition(FALSE, boards[currentMove]);
5838 }
5839
5840
5841 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5842 // All positions will have equal probability, but the current method will not provide a unique
5843 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5844 #define DARK 1
5845 #define LITE 2
5846 #define ANY 3
5847
5848 int squaresLeft[4];
5849 int piecesLeft[(int)BlackPawn];
5850 int seed, nrOfShuffles;
5851
5852 void
5853 GetPositionNumber ()
5854 {       // sets global variable seed
5855         int i;
5856
5857         seed = appData.defaultFrcPosition;
5858         if(seed < 0) { // randomize based on time for negative FRC position numbers
5859                 for(i=0; i<50; i++) seed += random();
5860                 seed = random() ^ random() >> 8 ^ random() << 8;
5861                 if(seed<0) seed = -seed;
5862         }
5863 }
5864
5865 int
5866 put (Board board, int pieceType, int rank, int n, int shade)
5867 // put the piece on the (n-1)-th empty squares of the given shade
5868 {
5869         int i;
5870
5871         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5872                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5873                         board[rank][i] = (ChessSquare) pieceType;
5874                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5875                         squaresLeft[ANY]--;
5876                         piecesLeft[pieceType]--;
5877                         return i;
5878                 }
5879         }
5880         return -1;
5881 }
5882
5883
5884 void
5885 AddOnePiece (Board board, int pieceType, int rank, int shade)
5886 // calculate where the next piece goes, (any empty square), and put it there
5887 {
5888         int i;
5889
5890         i = seed % squaresLeft[shade];
5891         nrOfShuffles *= squaresLeft[shade];
5892         seed /= squaresLeft[shade];
5893         put(board, pieceType, rank, i, shade);
5894 }
5895
5896 void
5897 AddTwoPieces (Board board, int pieceType, int rank)
5898 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5899 {
5900         int i, n=squaresLeft[ANY], j=n-1, k;
5901
5902         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5903         i = seed % k;  // pick one
5904         nrOfShuffles *= k;
5905         seed /= k;
5906         while(i >= j) i -= j--;
5907         j = n - 1 - j; i += j;
5908         put(board, pieceType, rank, j, ANY);
5909         put(board, pieceType, rank, i, ANY);
5910 }
5911
5912 void
5913 SetUpShuffle (Board board, int number)
5914 {
5915         int i, p, first=1;
5916
5917         GetPositionNumber(); nrOfShuffles = 1;
5918
5919         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5920         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5921         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5922
5923         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5924
5925         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5926             p = (int) board[0][i];
5927             if(p < (int) BlackPawn) piecesLeft[p] ++;
5928             board[0][i] = EmptySquare;
5929         }
5930
5931         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5932             // shuffles restricted to allow normal castling put KRR first
5933             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5934                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5935             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5936                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5937             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5938                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5939             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5940                 put(board, WhiteRook, 0, 0, ANY);
5941             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5942         }
5943
5944         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5945             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5946             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5947                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5948                 while(piecesLeft[p] >= 2) {
5949                     AddOnePiece(board, p, 0, LITE);
5950                     AddOnePiece(board, p, 0, DARK);
5951                 }
5952                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5953             }
5954
5955         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5956             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5957             // but we leave King and Rooks for last, to possibly obey FRC restriction
5958             if(p == (int)WhiteRook) continue;
5959             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5960             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5961         }
5962
5963         // now everything is placed, except perhaps King (Unicorn) and Rooks
5964
5965         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5966             // Last King gets castling rights
5967             while(piecesLeft[(int)WhiteUnicorn]) {
5968                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5969                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5970             }
5971
5972             while(piecesLeft[(int)WhiteKing]) {
5973                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5974                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5975             }
5976
5977
5978         } else {
5979             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5980             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5981         }
5982
5983         // Only Rooks can be left; simply place them all
5984         while(piecesLeft[(int)WhiteRook]) {
5985                 i = put(board, WhiteRook, 0, 0, ANY);
5986                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5987                         if(first) {
5988                                 first=0;
5989                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5990                         }
5991                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5992                 }
5993         }
5994         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5995             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5996         }
5997
5998         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5999 }
6000
6001 int
6002 ptclen (const char *s, char *escapes)
6003 {
6004     int n = 0;
6005     if(!*escapes) return strlen(s);
6006     while(*s) n += (*s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)), s++;
6007     return n;
6008 }
6009
6010 static int pieceOrder[] = {
6011   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, // P N B R Q F E A C W M
6012  11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, // O H I J G D V L S U Lion
6013  45, 23, 24, 25, 26, 27, 28, 29, 46, 31, 32, // Sword Zebra Camel Tower Wolf Dragon Duck Axe Leopard Gnu Cub
6014  44, 51, 56, 57, 58, 59, 60, 61, 62, 63, 34, // Whale Pegasus Wizard Copper Iron Viking Flag Amazon Wheel Shield Claw
6015  33, 55, 53, 42, 37, 48, 39, 40, 41, 22, 30, // +P +N =B =R +L +S +E +Ph +Kn Butterfly Hat
6016  38, 43, 35, 36, 49, 47, 52, 50, 54, 64, 65 // +V +M =H =D Princess HSword +GB HCrown Wheer Shierd King
6017 };
6018
6019 int
6020 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6021 /* [HGM] moved here from winboard.c because of its general usefulness */
6022 /*       Basically a safe strcpy that uses the last character as King */
6023 {
6024     int result = FALSE; int NrPieces;
6025     unsigned char partner[EmptySquare];
6026
6027     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6028                     && NrPieces >= 12 && !(NrPieces&1)) {
6029         int i, ii, j = 0; /* [HGM] Accept even length from 12 to 88 */
6030
6031         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6032         for( ii=0; ii<NrPieces/2-1; ii++ ) {
6033             char *p, c=0;
6034             i = pieceOrder[ii];
6035             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6036             table[i] = map[j++];
6037             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
6038             if(c) partner[i] = table[i], table[i] = c;
6039         }
6040         table[(int) WhiteKing]  = map[j++];
6041         for( ii=0; ii<NrPieces/2-1; ii++ ) {
6042             char *p, c=0;
6043             i = WHITE_TO_BLACK pieceOrder[ii];
6044             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6045             table[i] = map[j++];
6046             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
6047             if(c) partner[i] = table[i], table[i] = c;
6048         }
6049         table[(int) BlackKing]  = map[j++];
6050
6051
6052         if(*escapes) { // set up promotion pairing
6053             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6054             // pieceToChar entirely filled, so we can look up specified partners
6055             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6056                 int c = table[i];
6057                 if(c == '^' || c == '-') { // has specified partner
6058                     int p;
6059                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6060                     if(c == '^') table[i] = '+';
6061                     if(p < EmptySquare) promoPartner[p] = i, promoPartner[i] = p; // marry them
6062                 } else if(c == '*') promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6063             }
6064         }
6065
6066         result = TRUE;
6067     }
6068
6069     return result;
6070 }
6071
6072 int
6073 SetCharTable (unsigned char *table, const char * map)
6074 {
6075     return SetCharTableEsc(table, map, "");
6076 }
6077
6078 void
6079 Prelude (Board board)
6080 {       // [HGM] superchess: random selection of exo-pieces
6081         int i, j, k; ChessSquare p;
6082         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6083
6084         GetPositionNumber(); // use FRC position number
6085
6086         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6087             SetCharTable(pieceToChar, appData.pieceToCharTable);
6088             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6089                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6090         }
6091
6092         j = seed%4;                 seed /= 4;
6093         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6094         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6095         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6096         j = seed%3 + (seed%3 >= j); seed /= 3;
6097         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6098         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6099         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6100         j = seed%3;                 seed /= 3;
6101         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6102         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6103         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6104         j = seed%2 + (seed%2 >= j); seed /= 2;
6105         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6106         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6107         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6108         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6109         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6110         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6111         put(board, exoPieces[0],    0, 0, ANY);
6112         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6113 }
6114
6115 void
6116 InitPosition (int redraw)
6117 {
6118     ChessSquare (* pieces)[BOARD_FILES];
6119     int i, j, pawnRow=1, pieceRows=1, overrule,
6120     oldx = gameInfo.boardWidth,
6121     oldy = gameInfo.boardHeight,
6122     oldh = gameInfo.holdingsWidth;
6123     static int oldv;
6124
6125     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6126
6127     /* [AS] Initialize pv info list [HGM] and game status */
6128     {
6129         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6130             pvInfoList[i].depth = 0;
6131             boards[i][EP_STATUS] = EP_NONE;
6132             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6133         }
6134
6135         initialRulePlies = 0; /* 50-move counter start */
6136
6137         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6138         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6139     }
6140
6141
6142     /* [HGM] logic here is completely changed. In stead of full positions */
6143     /* the initialized data only consist of the two backranks. The switch */
6144     /* selects which one we will use, which is than copied to the Board   */
6145     /* initialPosition, which for the rest is initialized by Pawns and    */
6146     /* empty squares. This initial position is then copied to boards[0],  */
6147     /* possibly after shuffling, so that it remains available.            */
6148
6149     gameInfo.holdingsWidth = 0; /* default board sizes */
6150     gameInfo.boardWidth    = 8;
6151     gameInfo.boardHeight   = 8;
6152     gameInfo.holdingsSize  = 0;
6153     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6154     for(i=0; i<BOARD_FILES-6; i++)
6155       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6156     initialPosition[EP_STATUS] = EP_NONE;
6157     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6158     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6159     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6160          SetCharTable(pieceNickName, appData.pieceNickNames);
6161     else SetCharTable(pieceNickName, "............");
6162     pieces = FIDEArray;
6163
6164     switch (gameInfo.variant) {
6165     case VariantFischeRandom:
6166       shuffleOpenings = TRUE;
6167       appData.fischerCastling = TRUE;
6168     default:
6169       break;
6170     case VariantShatranj:
6171       pieces = ShatranjArray;
6172       nrCastlingRights = 0;
6173       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6174       break;
6175     case VariantMakruk:
6176       pieces = makrukArray;
6177       nrCastlingRights = 0;
6178       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6179       break;
6180     case VariantASEAN:
6181       pieces = aseanArray;
6182       nrCastlingRights = 0;
6183       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6184       break;
6185     case VariantTwoKings:
6186       pieces = twoKingsArray;
6187       break;
6188     case VariantGrand:
6189       pieces = GrandArray;
6190       nrCastlingRights = 0;
6191       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6192       gameInfo.boardWidth = 10;
6193       gameInfo.boardHeight = 10;
6194       gameInfo.holdingsSize = 7;
6195       break;
6196     case VariantCapaRandom:
6197       shuffleOpenings = TRUE;
6198       appData.fischerCastling = TRUE;
6199     case VariantCapablanca:
6200       pieces = CapablancaArray;
6201       gameInfo.boardWidth = 10;
6202       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6203       break;
6204     case VariantGothic:
6205       pieces = GothicArray;
6206       gameInfo.boardWidth = 10;
6207       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6208       break;
6209     case VariantSChess:
6210       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6211       gameInfo.holdingsSize = 7;
6212       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6213       break;
6214     case VariantJanus:
6215       pieces = JanusArray;
6216       gameInfo.boardWidth = 10;
6217       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6218       nrCastlingRights = 6;
6219         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6220         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6221         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6222         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6223         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6224         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6225       break;
6226     case VariantFalcon:
6227       pieces = FalconArray;
6228       gameInfo.boardWidth = 10;
6229       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6230       break;
6231     case VariantXiangqi:
6232       pieces = XiangqiArray;
6233       gameInfo.boardWidth  = 9;
6234       gameInfo.boardHeight = 10;
6235       nrCastlingRights = 0;
6236       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6237       break;
6238     case VariantShogi:
6239       pieces = ShogiArray;
6240       gameInfo.boardWidth  = 9;
6241       gameInfo.boardHeight = 9;
6242       gameInfo.holdingsSize = 7;
6243       nrCastlingRights = 0;
6244       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6245       break;
6246     case VariantChu:
6247       pieces = ChuArray; pieceRows = 3;
6248       gameInfo.boardWidth  = 12;
6249       gameInfo.boardHeight = 12;
6250       nrCastlingRights = 0;
6251       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/^P.^B^R.^S^E^X^O^G^C^A^T^H^D.^V^M^L^I^FK"
6252                                    "p.brqsexogcathd.vmlifn/^p.^b^r.^s^e^x^o^g^c^a^t^h^d.^v^m^l^i^fk", SUFFIXES);
6253       break;
6254     case VariantCourier:
6255       pieces = CourierArray;
6256       gameInfo.boardWidth  = 12;
6257       nrCastlingRights = 0;
6258       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6259       break;
6260     case VariantKnightmate:
6261       pieces = KnightmateArray;
6262       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6263       break;
6264     case VariantSpartan:
6265       pieces = SpartanArray;
6266       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6267       break;
6268     case VariantLion:
6269       pieces = lionArray;
6270       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6271       break;
6272     case VariantChuChess:
6273       pieces = ChuChessArray;
6274       gameInfo.boardWidth = 10;
6275       gameInfo.boardHeight = 10;
6276       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6277       break;
6278     case VariantFairy:
6279       pieces = fairyArray;
6280       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6281       break;
6282     case VariantGreat:
6283       pieces = GreatArray;
6284       gameInfo.boardWidth = 10;
6285       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6286       gameInfo.holdingsSize = 8;
6287       break;
6288     case VariantSuper:
6289       pieces = FIDEArray;
6290       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6291       gameInfo.holdingsSize = 8;
6292       startedFromSetupPosition = TRUE;
6293       break;
6294     case VariantCrazyhouse:
6295     case VariantBughouse:
6296       pieces = FIDEArray;
6297       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6298       gameInfo.holdingsSize = 5;
6299       break;
6300     case VariantWildCastle:
6301       pieces = FIDEArray;
6302       /* !!?shuffle with kings guaranteed to be on d or e file */
6303       shuffleOpenings = 1;
6304       break;
6305     case VariantNoCastle:
6306       pieces = FIDEArray;
6307       nrCastlingRights = 0;
6308       /* !!?unconstrained back-rank shuffle */
6309       shuffleOpenings = 1;
6310       break;
6311     }
6312
6313     overrule = 0;
6314     if(appData.NrFiles >= 0) {
6315         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6316         gameInfo.boardWidth = appData.NrFiles;
6317     }
6318     if(appData.NrRanks >= 0) {
6319         gameInfo.boardHeight = appData.NrRanks;
6320     }
6321     if(appData.holdingsSize >= 0) {
6322         i = appData.holdingsSize;
6323         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6324         gameInfo.holdingsSize = i;
6325     }
6326     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6327     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6328         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6329
6330     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6331     if(pawnRow < 1) pawnRow = 1;
6332     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6333        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6334     if(gameInfo.variant == VariantChu) pawnRow = 3;
6335
6336     /* User pieceToChar list overrules defaults */
6337     if(appData.pieceToCharTable != NULL)
6338         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6339
6340     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6341
6342         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6343             s = (ChessSquare) 0; /* account holding counts in guard band */
6344         for( i=0; i<BOARD_HEIGHT; i++ )
6345             initialPosition[i][j] = s;
6346
6347         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6348         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6349         initialPosition[pawnRow][j] = WhitePawn;
6350         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6351         if(gameInfo.variant == VariantXiangqi) {
6352             if(j&1) {
6353                 initialPosition[pawnRow][j] =
6354                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6355                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6356                    initialPosition[2][j] = WhiteCannon;
6357                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6358                 }
6359             }
6360         }
6361         if(gameInfo.variant == VariantChu) {
6362              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6363                initialPosition[pawnRow+1][j] = WhiteCobra,
6364                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6365              for(i=1; i<pieceRows; i++) {
6366                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6367                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6368              }
6369         }
6370         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6371             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6372                initialPosition[0][j] = WhiteRook;
6373                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6374             }
6375         }
6376         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6377     }
6378     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6379     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6380
6381             j=BOARD_LEFT+1;
6382             initialPosition[1][j] = WhiteBishop;
6383             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6384             j=BOARD_RGHT-2;
6385             initialPosition[1][j] = WhiteRook;
6386             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6387     }
6388
6389     if( nrCastlingRights == -1) {
6390         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6391         /*       This sets default castling rights from none to normal corners   */
6392         /* Variants with other castling rights must set them themselves above    */
6393         nrCastlingRights = 6;
6394
6395         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6396         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6397         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6398         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6399         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6400         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6401      }
6402
6403      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6404      if(gameInfo.variant == VariantGreat) { // promotion commoners
6405         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6406         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6407         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6408         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6409      }
6410      if( gameInfo.variant == VariantSChess ) {
6411       initialPosition[1][0] = BlackMarshall;
6412       initialPosition[2][0] = BlackAngel;
6413       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6414       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6415       initialPosition[1][1] = initialPosition[2][1] =
6416       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6417      }
6418   if (appData.debugMode) {
6419     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6420   }
6421     if(shuffleOpenings) {
6422         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6423         startedFromSetupPosition = TRUE;
6424     }
6425     if(startedFromPositionFile) {
6426       /* [HGM] loadPos: use PositionFile for every new game */
6427       CopyBoard(initialPosition, filePosition);
6428       for(i=0; i<nrCastlingRights; i++)
6429           initialRights[i] = filePosition[CASTLING][i];
6430       startedFromSetupPosition = TRUE;
6431     }
6432
6433     CopyBoard(boards[0], initialPosition);
6434
6435     if(oldx != gameInfo.boardWidth ||
6436        oldy != gameInfo.boardHeight ||
6437        oldv != gameInfo.variant ||
6438        oldh != gameInfo.holdingsWidth
6439                                          )
6440             InitDrawingSizes(-2 ,0);
6441
6442     oldv = gameInfo.variant;
6443     if (redraw)
6444       DrawPosition(TRUE, boards[currentMove]);
6445 }
6446
6447 void
6448 SendBoard (ChessProgramState *cps, int moveNum)
6449 {
6450     char message[MSG_SIZ];
6451
6452     if (cps->useSetboard) {
6453       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6454       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6455       SendToProgram(message, cps);
6456       free(fen);
6457
6458     } else {
6459       ChessSquare *bp;
6460       int i, j, left=0, right=BOARD_WIDTH;
6461       /* Kludge to set black to move, avoiding the troublesome and now
6462        * deprecated "black" command.
6463        */
6464       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6465         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6466
6467       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6468
6469       SendToProgram("edit\n", cps);
6470       SendToProgram("#\n", cps);
6471       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6472         bp = &boards[moveNum][i][left];
6473         for (j = left; j < right; j++, bp++) {
6474           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6475           if ((int) *bp < (int) BlackPawn) {
6476             if(j == BOARD_RGHT+1)
6477                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6478             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6479             if(message[0] == '+' || message[0] == '~') {
6480               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6481                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6482                         AAA + j, ONE + i - '0');
6483             }
6484             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6485                 message[1] = BOARD_RGHT   - 1 - j + '1';
6486                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6487             }
6488             SendToProgram(message, cps);
6489           }
6490         }
6491       }
6492
6493       SendToProgram("c\n", cps);
6494       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6495         bp = &boards[moveNum][i][left];
6496         for (j = left; j < right; j++, bp++) {
6497           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6498           if (((int) *bp != (int) EmptySquare)
6499               && ((int) *bp >= (int) BlackPawn)) {
6500             if(j == BOARD_LEFT-2)
6501                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6502             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6503                     AAA + j, ONE + i - '0');
6504             if(message[0] == '+' || message[0] == '~') {
6505               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6506                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6507                         AAA + j, ONE + i - '0');
6508             }
6509             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6510                 message[1] = BOARD_RGHT   - 1 - j + '1';
6511                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6512             }
6513             SendToProgram(message, cps);
6514           }
6515         }
6516       }
6517
6518       SendToProgram(".\n", cps);
6519     }
6520     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6521 }
6522
6523 char exclusionHeader[MSG_SIZ];
6524 int exCnt, excludePtr;
6525 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6526 static Exclusion excluTab[200];
6527 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6528
6529 static void
6530 WriteMap (int s)
6531 {
6532     int j;
6533     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6534     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6535 }
6536
6537 static void
6538 ClearMap ()
6539 {
6540     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6541     excludePtr = 24; exCnt = 0;
6542     WriteMap(0);
6543 }
6544
6545 static void
6546 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6547 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6548     char buf[2*MOVE_LEN], *p;
6549     Exclusion *e = excluTab;
6550     int i;
6551     for(i=0; i<exCnt; i++)
6552         if(e[i].ff == fromX && e[i].fr == fromY &&
6553            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6554     if(i == exCnt) { // was not in exclude list; add it
6555         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6556         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6557             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6558             return; // abort
6559         }
6560         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6561         excludePtr++; e[i].mark = excludePtr++;
6562         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6563         exCnt++;
6564     }
6565     exclusionHeader[e[i].mark] = state;
6566 }
6567
6568 static int
6569 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6570 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6571     char buf[MSG_SIZ];
6572     int j, k;
6573     ChessMove moveType;
6574     if((signed char)promoChar == -1) { // kludge to indicate best move
6575         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6576             return 1; // if unparsable, abort
6577     }
6578     // update exclusion map (resolving toggle by consulting existing state)
6579     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6580     j = k%8; k >>= 3;
6581     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6582     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6583          excludeMap[k] |=   1<<j;
6584     else excludeMap[k] &= ~(1<<j);
6585     // update header
6586     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6587     // inform engine
6588     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6589     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6590     SendToBoth(buf);
6591     return (state == '+');
6592 }
6593
6594 static void
6595 ExcludeClick (int index)
6596 {
6597     int i, j;
6598     Exclusion *e = excluTab;
6599     if(index < 25) { // none, best or tail clicked
6600         if(index < 13) { // none: include all
6601             WriteMap(0); // clear map
6602             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6603             SendToBoth("include all\n"); // and inform engine
6604         } else if(index > 18) { // tail
6605             if(exclusionHeader[19] == '-') { // tail was excluded
6606                 SendToBoth("include all\n");
6607                 WriteMap(0); // clear map completely
6608                 // now re-exclude selected moves
6609                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6610                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6611             } else { // tail was included or in mixed state
6612                 SendToBoth("exclude all\n");
6613                 WriteMap(0xFF); // fill map completely
6614                 // now re-include selected moves
6615                 j = 0; // count them
6616                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6617                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6618                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6619             }
6620         } else { // best
6621             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6622         }
6623     } else {
6624         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6625             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6626             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6627             break;
6628         }
6629     }
6630 }
6631
6632 ChessSquare
6633 DefaultPromoChoice (int white)
6634 {
6635     ChessSquare result;
6636     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6637        gameInfo.variant == VariantMakruk)
6638         result = WhiteFerz; // no choice
6639     else if(gameInfo.variant == VariantASEAN)
6640         result = WhiteRook; // no choice
6641     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6642         result= WhiteKing; // in Suicide Q is the last thing we want
6643     else if(gameInfo.variant == VariantSpartan)
6644         result = white ? WhiteQueen : WhiteAngel;
6645     else result = WhiteQueen;
6646     if(!white) result = WHITE_TO_BLACK result;
6647     return result;
6648 }
6649
6650 static int autoQueen; // [HGM] oneclick
6651
6652 int
6653 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6654 {
6655     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6656     /* [HGM] add Shogi promotions */
6657     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6658     ChessSquare piece, partner;
6659     ChessMove moveType;
6660     Boolean premove;
6661
6662     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6663     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6664
6665     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6666       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6667         return FALSE;
6668
6669     piece = boards[currentMove][fromY][fromX];
6670     if(gameInfo.variant == VariantChu) {
6671         promotionZoneSize = BOARD_HEIGHT/3;
6672         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6673     } else if(gameInfo.variant == VariantShogi) {
6674         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6675         highestPromotingPiece = (int)WhiteAlfil;
6676     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6677         promotionZoneSize = 3;
6678     }
6679
6680     // Treat Lance as Pawn when it is not representing Amazon or Lance
6681     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6682         if(piece == WhiteLance) piece = WhitePawn; else
6683         if(piece == BlackLance) piece = BlackPawn;
6684     }
6685
6686     // next weed out all moves that do not touch the promotion zone at all
6687     if((int)piece >= BlackPawn) {
6688         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6689              return FALSE;
6690         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6691         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6692     } else {
6693         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6694            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6695         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6696              return FALSE;
6697     }
6698
6699     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6700
6701     // weed out mandatory Shogi promotions
6702     if(gameInfo.variant == VariantShogi) {
6703         if(piece >= BlackPawn) {
6704             if(toY == 0 && piece == BlackPawn ||
6705                toY == 0 && piece == BlackQueen ||
6706                toY <= 1 && piece == BlackKnight) {
6707                 *promoChoice = '+';
6708                 return FALSE;
6709             }
6710         } else {
6711             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6712                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6713                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6714                 *promoChoice = '+';
6715                 return FALSE;
6716             }
6717         }
6718     }
6719
6720     // weed out obviously illegal Pawn moves
6721     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6722         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6723         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6724         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6725         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6726         // note we are not allowed to test for valid (non-)capture, due to premove
6727     }
6728
6729     // we either have a choice what to promote to, or (in Shogi) whether to promote
6730     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6731        gameInfo.variant == VariantMakruk) {
6732         ChessSquare p=BlackFerz;  // no choice
6733         while(p < EmptySquare) {  //but make sure we use piece that exists
6734             *promoChoice = PieceToChar(p++);
6735             if(*promoChoice != '.') break;
6736         }
6737         return FALSE;
6738     }
6739     // no sense asking what we must promote to if it is going to explode...
6740     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6741         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6742         return FALSE;
6743     }
6744     // give caller the default choice even if we will not make it
6745     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6746     partner = piece; // pieces can promote if the pieceToCharTable says so
6747     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6748     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6749     if(        sweepSelect && gameInfo.variant != VariantGreat
6750                            && gameInfo.variant != VariantGrand
6751                            && gameInfo.variant != VariantSuper) return FALSE;
6752     if(autoQueen) return FALSE; // predetermined
6753
6754     // suppress promotion popup on illegal moves that are not premoves
6755     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6756               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6757     if(appData.testLegality && !premove) {
6758         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6759                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6760         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6761         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6762             return FALSE;
6763     }
6764
6765     return TRUE;
6766 }
6767
6768 int
6769 InPalace (int row, int column)
6770 {   /* [HGM] for Xiangqi */
6771     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6772          column < (BOARD_WIDTH + 4)/2 &&
6773          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6774     return FALSE;
6775 }
6776
6777 int
6778 PieceForSquare (int x, int y)
6779 {
6780   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6781      return -1;
6782   else
6783      return boards[currentMove][y][x];
6784 }
6785
6786 int
6787 OKToStartUserMove (int x, int y)
6788 {
6789     ChessSquare from_piece;
6790     int white_piece;
6791
6792     if (matchMode) return FALSE;
6793     if (gameMode == EditPosition) return TRUE;
6794
6795     if (x >= 0 && y >= 0)
6796       from_piece = boards[currentMove][y][x];
6797     else
6798       from_piece = EmptySquare;
6799
6800     if (from_piece == EmptySquare) return FALSE;
6801
6802     white_piece = (int)from_piece >= (int)WhitePawn &&
6803       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6804
6805     switch (gameMode) {
6806       case AnalyzeFile:
6807       case TwoMachinesPlay:
6808       case EndOfGame:
6809         return FALSE;
6810
6811       case IcsObserving:
6812       case IcsIdle:
6813         return FALSE;
6814
6815       case MachinePlaysWhite:
6816       case IcsPlayingBlack:
6817         if (appData.zippyPlay) return FALSE;
6818         if (white_piece) {
6819             DisplayMoveError(_("You are playing Black"));
6820             return FALSE;
6821         }
6822         break;
6823
6824       case MachinePlaysBlack:
6825       case IcsPlayingWhite:
6826         if (appData.zippyPlay) return FALSE;
6827         if (!white_piece) {
6828             DisplayMoveError(_("You are playing White"));
6829             return FALSE;
6830         }
6831         break;
6832
6833       case PlayFromGameFile:
6834             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6835       case EditGame:
6836         if (!white_piece && WhiteOnMove(currentMove)) {
6837             DisplayMoveError(_("It is White's turn"));
6838             return FALSE;
6839         }
6840         if (white_piece && !WhiteOnMove(currentMove)) {
6841             DisplayMoveError(_("It is Black's turn"));
6842             return FALSE;
6843         }
6844         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6845             /* Editing correspondence game history */
6846             /* Could disallow this or prompt for confirmation */
6847             cmailOldMove = -1;
6848         }
6849         break;
6850
6851       case BeginningOfGame:
6852         if (appData.icsActive) return FALSE;
6853         if (!appData.noChessProgram) {
6854             if (!white_piece) {
6855                 DisplayMoveError(_("You are playing White"));
6856                 return FALSE;
6857             }
6858         }
6859         break;
6860
6861       case Training:
6862         if (!white_piece && WhiteOnMove(currentMove)) {
6863             DisplayMoveError(_("It is White's turn"));
6864             return FALSE;
6865         }
6866         if (white_piece && !WhiteOnMove(currentMove)) {
6867             DisplayMoveError(_("It is Black's turn"));
6868             return FALSE;
6869         }
6870         break;
6871
6872       default:
6873       case IcsExamining:
6874         break;
6875     }
6876     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6877         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6878         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6879         && gameMode != AnalyzeFile && gameMode != Training) {
6880         DisplayMoveError(_("Displayed position is not current"));
6881         return FALSE;
6882     }
6883     return TRUE;
6884 }
6885
6886 Boolean
6887 OnlyMove (int *x, int *y, Boolean captures)
6888 {
6889     DisambiguateClosure cl;
6890     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6891     switch(gameMode) {
6892       case MachinePlaysBlack:
6893       case IcsPlayingWhite:
6894       case BeginningOfGame:
6895         if(!WhiteOnMove(currentMove)) return FALSE;
6896         break;
6897       case MachinePlaysWhite:
6898       case IcsPlayingBlack:
6899         if(WhiteOnMove(currentMove)) return FALSE;
6900         break;
6901       case EditGame:
6902         break;
6903       default:
6904         return FALSE;
6905     }
6906     cl.pieceIn = EmptySquare;
6907     cl.rfIn = *y;
6908     cl.ffIn = *x;
6909     cl.rtIn = -1;
6910     cl.ftIn = -1;
6911     cl.promoCharIn = NULLCHAR;
6912     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6913     if( cl.kind == NormalMove ||
6914         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6915         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6916         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6917       fromX = cl.ff;
6918       fromY = cl.rf;
6919       *x = cl.ft;
6920       *y = cl.rt;
6921       return TRUE;
6922     }
6923     if(cl.kind != ImpossibleMove) return FALSE;
6924     cl.pieceIn = EmptySquare;
6925     cl.rfIn = -1;
6926     cl.ffIn = -1;
6927     cl.rtIn = *y;
6928     cl.ftIn = *x;
6929     cl.promoCharIn = NULLCHAR;
6930     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6931     if( cl.kind == NormalMove ||
6932         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6933         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6934         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6935       fromX = cl.ff;
6936       fromY = cl.rf;
6937       *x = cl.ft;
6938       *y = cl.rt;
6939       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6940       return TRUE;
6941     }
6942     return FALSE;
6943 }
6944
6945 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6946 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6947 int lastLoadGameUseList = FALSE;
6948 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6949 ChessMove lastLoadGameStart = EndOfFile;
6950 int doubleClick;
6951 Boolean addToBookFlag;
6952
6953 void
6954 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6955 {
6956     ChessMove moveType;
6957     ChessSquare pup;
6958     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6959
6960     /* Check if the user is playing in turn.  This is complicated because we
6961        let the user "pick up" a piece before it is his turn.  So the piece he
6962        tried to pick up may have been captured by the time he puts it down!
6963        Therefore we use the color the user is supposed to be playing in this
6964        test, not the color of the piece that is currently on the starting
6965        square---except in EditGame mode, where the user is playing both
6966        sides; fortunately there the capture race can't happen.  (It can
6967        now happen in IcsExamining mode, but that's just too bad.  The user
6968        will get a somewhat confusing message in that case.)
6969        */
6970
6971     switch (gameMode) {
6972       case AnalyzeFile:
6973       case TwoMachinesPlay:
6974       case EndOfGame:
6975       case IcsObserving:
6976       case IcsIdle:
6977         /* We switched into a game mode where moves are not accepted,
6978            perhaps while the mouse button was down. */
6979         return;
6980
6981       case MachinePlaysWhite:
6982         /* User is moving for Black */
6983         if (WhiteOnMove(currentMove)) {
6984             DisplayMoveError(_("It is White's turn"));
6985             return;
6986         }
6987         break;
6988
6989       case MachinePlaysBlack:
6990         /* User is moving for White */
6991         if (!WhiteOnMove(currentMove)) {
6992             DisplayMoveError(_("It is Black's turn"));
6993             return;
6994         }
6995         break;
6996
6997       case PlayFromGameFile:
6998             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6999       case EditGame:
7000       case IcsExamining:
7001       case BeginningOfGame:
7002       case AnalyzeMode:
7003       case Training:
7004         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7005         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7006             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7007             /* User is moving for Black */
7008             if (WhiteOnMove(currentMove)) {
7009                 DisplayMoveError(_("It is White's turn"));
7010                 return;
7011             }
7012         } else {
7013             /* User is moving for White */
7014             if (!WhiteOnMove(currentMove)) {
7015                 DisplayMoveError(_("It is Black's turn"));
7016                 return;
7017             }
7018         }
7019         break;
7020
7021       case IcsPlayingBlack:
7022         /* User is moving for Black */
7023         if (WhiteOnMove(currentMove)) {
7024             if (!appData.premove) {
7025                 DisplayMoveError(_("It is White's turn"));
7026             } else if (toX >= 0 && toY >= 0) {
7027                 premoveToX = toX;
7028                 premoveToY = toY;
7029                 premoveFromX = fromX;
7030                 premoveFromY = fromY;
7031                 premovePromoChar = promoChar;
7032                 gotPremove = 1;
7033                 if (appData.debugMode)
7034                     fprintf(debugFP, "Got premove: fromX %d,"
7035                             "fromY %d, toX %d, toY %d\n",
7036                             fromX, fromY, toX, toY);
7037             }
7038             return;
7039         }
7040         break;
7041
7042       case IcsPlayingWhite:
7043         /* User is moving for White */
7044         if (!WhiteOnMove(currentMove)) {
7045             if (!appData.premove) {
7046                 DisplayMoveError(_("It is Black's turn"));
7047             } else if (toX >= 0 && toY >= 0) {
7048                 premoveToX = toX;
7049                 premoveToY = toY;
7050                 premoveFromX = fromX;
7051                 premoveFromY = fromY;
7052                 premovePromoChar = promoChar;
7053                 gotPremove = 1;
7054                 if (appData.debugMode)
7055                     fprintf(debugFP, "Got premove: fromX %d,"
7056                             "fromY %d, toX %d, toY %d\n",
7057                             fromX, fromY, toX, toY);
7058             }
7059             return;
7060         }
7061         break;
7062
7063       default:
7064         break;
7065
7066       case EditPosition:
7067         /* EditPosition, empty square, or different color piece;
7068            click-click move is possible */
7069         if (toX == -2 || toY == -2) {
7070             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7071             DrawPosition(FALSE, boards[currentMove]);
7072             return;
7073         } else if (toX >= 0 && toY >= 0) {
7074             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7075                 ChessSquare q, p = boards[0][rf][ff];
7076                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7077                 if(CHUPROMOTED(p) < BlackPawn) p = q = CHUPROMOTED(boards[0][rf][ff]);
7078                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7079                 if(PieceToChar(q) == '+') gatingPiece = p;
7080             }
7081             boards[0][toY][toX] = boards[0][fromY][fromX];
7082             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7083                 if(boards[0][fromY][0] != EmptySquare) {
7084                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7085                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7086                 }
7087             } else
7088             if(fromX == BOARD_RGHT+1) {
7089                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7090                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7091                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7092                 }
7093             } else
7094             boards[0][fromY][fromX] = gatingPiece;
7095             DrawPosition(FALSE, boards[currentMove]);
7096             return;
7097         }
7098         return;
7099     }
7100
7101     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7102     pup = boards[currentMove][toY][toX];
7103
7104     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7105     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7106          if( pup != EmptySquare ) return;
7107          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7108            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7109                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7110            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7111            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7112            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7113            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7114          fromY = DROP_RANK;
7115     }
7116
7117     /* [HGM] always test for legality, to get promotion info */
7118     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7119                                          fromY, fromX, toY, toX, promoChar);
7120
7121     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7122
7123     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7124
7125     /* [HGM] but possibly ignore an IllegalMove result */
7126     if (appData.testLegality) {
7127         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7128             DisplayMoveError(_("Illegal move"));
7129             return;
7130         }
7131     }
7132
7133     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7134         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7135              ClearPremoveHighlights(); // was included
7136         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7137         return;
7138     }
7139
7140     if(addToBookFlag) { // adding moves to book
7141         char buf[MSG_SIZ], move[MSG_SIZ];
7142         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7143         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7144         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7145         AddBookMove(buf);
7146         addToBookFlag = FALSE;
7147         ClearHighlights();
7148         return;
7149     }
7150
7151     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7152 }
7153
7154 /* Common tail of UserMoveEvent and DropMenuEvent */
7155 int
7156 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7157 {
7158     char *bookHit = 0;
7159
7160     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7161         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7162         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7163         if(WhiteOnMove(currentMove)) {
7164             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7165         } else {
7166             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7167         }
7168     }
7169
7170     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7171        move type in caller when we know the move is a legal promotion */
7172     if(moveType == NormalMove && promoChar)
7173         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7174
7175     /* [HGM] <popupFix> The following if has been moved here from
7176        UserMoveEvent(). Because it seemed to belong here (why not allow
7177        piece drops in training games?), and because it can only be
7178        performed after it is known to what we promote. */
7179     if (gameMode == Training) {
7180       /* compare the move played on the board to the next move in the
7181        * game. If they match, display the move and the opponent's response.
7182        * If they don't match, display an error message.
7183        */
7184       int saveAnimate;
7185       Board testBoard;
7186       CopyBoard(testBoard, boards[currentMove]);
7187       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7188
7189       if (CompareBoards(testBoard, boards[currentMove+1])) {
7190         ForwardInner(currentMove+1);
7191
7192         /* Autoplay the opponent's response.
7193          * if appData.animate was TRUE when Training mode was entered,
7194          * the response will be animated.
7195          */
7196         saveAnimate = appData.animate;
7197         appData.animate = animateTraining;
7198         ForwardInner(currentMove+1);
7199         appData.animate = saveAnimate;
7200
7201         /* check for the end of the game */
7202         if (currentMove >= forwardMostMove) {
7203           gameMode = PlayFromGameFile;
7204           ModeHighlight();
7205           SetTrainingModeOff();
7206           DisplayInformation(_("End of game"));
7207         }
7208       } else {
7209         DisplayError(_("Incorrect move"), 0);
7210       }
7211       return 1;
7212     }
7213
7214   /* Ok, now we know that the move is good, so we can kill
7215      the previous line in Analysis Mode */
7216   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7217                                 && currentMove < forwardMostMove) {
7218     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7219     else forwardMostMove = currentMove;
7220   }
7221
7222   ClearMap();
7223
7224   /* If we need the chess program but it's dead, restart it */
7225   ResurrectChessProgram();
7226
7227   /* A user move restarts a paused game*/
7228   if (pausing)
7229     PauseEvent();
7230
7231   thinkOutput[0] = NULLCHAR;
7232
7233   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7234
7235   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7236     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7237     return 1;
7238   }
7239
7240   if (gameMode == BeginningOfGame) {
7241     if (appData.noChessProgram) {
7242       gameMode = EditGame;
7243       SetGameInfo();
7244     } else {
7245       char buf[MSG_SIZ];
7246       gameMode = MachinePlaysBlack;
7247       StartClocks();
7248       SetGameInfo();
7249       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7250       DisplayTitle(buf);
7251       if (first.sendName) {
7252         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7253         SendToProgram(buf, &first);
7254       }
7255       StartClocks();
7256     }
7257     ModeHighlight();
7258   }
7259
7260   /* Relay move to ICS or chess engine */
7261   if (appData.icsActive) {
7262     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7263         gameMode == IcsExamining) {
7264       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7265         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7266         SendToICS("draw ");
7267         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7268       }
7269       // also send plain move, in case ICS does not understand atomic claims
7270       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7271       ics_user_moved = 1;
7272     }
7273   } else {
7274     if (first.sendTime && (gameMode == BeginningOfGame ||
7275                            gameMode == MachinePlaysWhite ||
7276                            gameMode == MachinePlaysBlack)) {
7277       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7278     }
7279     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7280          // [HGM] book: if program might be playing, let it use book
7281         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7282         first.maybeThinking = TRUE;
7283     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7284         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7285         SendBoard(&first, currentMove+1);
7286         if(second.analyzing) {
7287             if(!second.useSetboard) SendToProgram("undo\n", &second);
7288             SendBoard(&second, currentMove+1);
7289         }
7290     } else {
7291         SendMoveToProgram(forwardMostMove-1, &first);
7292         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7293     }
7294     if (currentMove == cmailOldMove + 1) {
7295       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7296     }
7297   }
7298
7299   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7300
7301   switch (gameMode) {
7302   case EditGame:
7303     if(appData.testLegality)
7304     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7305     case MT_NONE:
7306     case MT_CHECK:
7307       break;
7308     case MT_CHECKMATE:
7309     case MT_STAINMATE:
7310       if (WhiteOnMove(currentMove)) {
7311         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7312       } else {
7313         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7314       }
7315       break;
7316     case MT_STALEMATE:
7317       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7318       break;
7319     }
7320     break;
7321
7322   case MachinePlaysBlack:
7323   case MachinePlaysWhite:
7324     /* disable certain menu options while machine is thinking */
7325     SetMachineThinkingEnables();
7326     break;
7327
7328   default:
7329     break;
7330   }
7331
7332   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7333   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7334
7335   if(bookHit) { // [HGM] book: simulate book reply
7336         static char bookMove[MSG_SIZ]; // a bit generous?
7337
7338         programStats.nodes = programStats.depth = programStats.time =
7339         programStats.score = programStats.got_only_move = 0;
7340         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7341
7342         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7343         strcat(bookMove, bookHit);
7344         HandleMachineMove(bookMove, &first);
7345   }
7346   return 1;
7347 }
7348
7349 void
7350 MarkByFEN(char *fen)
7351 {
7352         int r, f;
7353         if(!appData.markers || !appData.highlightDragging) return;
7354         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7355         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7356         while(*fen) {
7357             int s = 0;
7358             marker[r][f] = 0;
7359             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7360             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7361             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7362             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7363             if(*fen == 'T') marker[r][f++] = 0; else
7364             if(*fen == 'Y') marker[r][f++] = 1; else
7365             if(*fen == 'G') marker[r][f++] = 3; else
7366             if(*fen == 'B') marker[r][f++] = 4; else
7367             if(*fen == 'C') marker[r][f++] = 5; else
7368             if(*fen == 'M') marker[r][f++] = 6; else
7369             if(*fen == 'W') marker[r][f++] = 7; else
7370             if(*fen == 'D') marker[r][f++] = 8; else
7371             if(*fen == 'R') marker[r][f++] = 2; else {
7372                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7373               f += s; fen -= s>0;
7374             }
7375             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7376             if(r < 0) break;
7377             fen++;
7378         }
7379         DrawPosition(TRUE, NULL);
7380 }
7381
7382 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7383
7384 void
7385 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7386 {
7387     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7388     Markers *m = (Markers *) closure;
7389     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7390         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7391                          || kind == WhiteCapturesEnPassant
7392                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7393     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7394 }
7395
7396 static int hoverSavedValid;
7397
7398 void
7399 MarkTargetSquares (int clear)
7400 {
7401   int x, y, sum=0;
7402   if(clear) { // no reason to ever suppress clearing
7403     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7404     hoverSavedValid = 0;
7405     if(!sum) return; // nothing was cleared,no redraw needed
7406   } else {
7407     int capt = 0;
7408     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7409        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7410     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7411     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7412       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7413       if(capt)
7414       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7415     }
7416   }
7417   DrawPosition(FALSE, NULL);
7418 }
7419
7420 int
7421 Explode (Board board, int fromX, int fromY, int toX, int toY)
7422 {
7423     if(gameInfo.variant == VariantAtomic &&
7424        (board[toY][toX] != EmptySquare ||                     // capture?
7425         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7426                          board[fromY][fromX] == BlackPawn   )
7427       )) {
7428         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7429         return TRUE;
7430     }
7431     return FALSE;
7432 }
7433
7434 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7435
7436 int
7437 CanPromote (ChessSquare piece, int y)
7438 {
7439         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7440         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7441         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7442         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7443            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7444            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7445          gameInfo.variant == VariantMakruk) return FALSE;
7446         return (piece == BlackPawn && y <= zone ||
7447                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7448                 piece == BlackLance && y <= zone ||
7449                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7450 }
7451
7452 void
7453 HoverEvent (int xPix, int yPix, int x, int y)
7454 {
7455         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7456         int r, f;
7457         if(!first.highlight) return;
7458         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7459         if(x == oldX && y == oldY) return; // only do something if we enter new square
7460         oldFromX = fromX; oldFromY = fromY;
7461         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7462           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7463             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7464           hoverSavedValid = 1;
7465         } else if(oldX != x || oldY != y) {
7466           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7467           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7468           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7469             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7470           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7471             char buf[MSG_SIZ];
7472             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7473             SendToProgram(buf, &first);
7474           }
7475           oldX = x; oldY = y;
7476 //        SetHighlights(fromX, fromY, x, y);
7477         }
7478 }
7479
7480 void ReportClick(char *action, int x, int y)
7481 {
7482         char buf[MSG_SIZ]; // Inform engine of what user does
7483         int r, f;
7484         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7485           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7486             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7487         if(!first.highlight || gameMode == EditPosition) return;
7488         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7489         SendToProgram(buf, &first);
7490 }
7491
7492 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7493
7494 void
7495 LeftClick (ClickType clickType, int xPix, int yPix)
7496 {
7497     int x, y;
7498     Boolean saveAnimate;
7499     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7500     char promoChoice = NULLCHAR;
7501     ChessSquare piece;
7502     static TimeMark lastClickTime, prevClickTime;
7503
7504     x = EventToSquare(xPix, BOARD_WIDTH);
7505     y = EventToSquare(yPix, BOARD_HEIGHT);
7506     if (!flipView && y >= 0) {
7507         y = BOARD_HEIGHT - 1 - y;
7508     }
7509     if (flipView && x >= 0) {
7510         x = BOARD_WIDTH - 1 - x;
7511     }
7512
7513     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7514         static int dummy;
7515         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7516         right = TRUE;
7517         return;
7518     }
7519
7520     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7521
7522     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7523
7524     if (clickType == Press) ErrorPopDown();
7525     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7526
7527     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7528         defaultPromoChoice = promoSweep;
7529         promoSweep = EmptySquare;   // terminate sweep
7530         promoDefaultAltered = TRUE;
7531         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7532     }
7533
7534     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7535         if(clickType == Release) return; // ignore upclick of click-click destination
7536         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7537         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7538         if(gameInfo.holdingsWidth &&
7539                 (WhiteOnMove(currentMove)
7540                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7541                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7542             // click in right holdings, for determining promotion piece
7543             ChessSquare p = boards[currentMove][y][x];
7544             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7545             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7546             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7547                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7548                 fromX = fromY = -1;
7549                 return;
7550             }
7551         }
7552         DrawPosition(FALSE, boards[currentMove]);
7553         return;
7554     }
7555
7556     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7557     if(clickType == Press
7558             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7559               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7560               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7561         return;
7562
7563     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7564         // could be static click on premove from-square: abort premove
7565         gotPremove = 0;
7566         ClearPremoveHighlights();
7567     }
7568
7569     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7570         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7571
7572     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7573         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7574                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7575         defaultPromoChoice = DefaultPromoChoice(side);
7576     }
7577
7578     autoQueen = appData.alwaysPromoteToQueen;
7579
7580     if (fromX == -1) {
7581       int originalY = y;
7582       gatingPiece = EmptySquare;
7583       if (clickType != Press) {
7584         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7585             DragPieceEnd(xPix, yPix); dragging = 0;
7586             DrawPosition(FALSE, NULL);
7587         }
7588         return;
7589       }
7590       doubleClick = FALSE;
7591       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7592         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7593       }
7594       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7595       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7596          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7597          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7598             /* First square */
7599             if (OKToStartUserMove(fromX, fromY)) {
7600                 second = 0;
7601                 ReportClick("lift", x, y);
7602                 MarkTargetSquares(0);
7603                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7604                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7605                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7606                     promoSweep = defaultPromoChoice;
7607                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7608                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7609                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7610                 }
7611                 if (appData.highlightDragging) {
7612                     SetHighlights(fromX, fromY, -1, -1);
7613                 } else {
7614                     ClearHighlights();
7615                 }
7616             } else fromX = fromY = -1;
7617             return;
7618         }
7619     }
7620
7621     /* fromX != -1 */
7622     if (clickType == Press && gameMode != EditPosition) {
7623         ChessSquare fromP;
7624         ChessSquare toP;
7625         int frc;
7626
7627         // ignore off-board to clicks
7628         if(y < 0 || x < 0) return;
7629
7630         /* Check if clicking again on the same color piece */
7631         fromP = boards[currentMove][fromY][fromX];
7632         toP = boards[currentMove][y][x];
7633         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7634         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7635             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7636            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7637              WhitePawn <= toP && toP <= WhiteKing &&
7638              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7639              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7640             (BlackPawn <= fromP && fromP <= BlackKing &&
7641              BlackPawn <= toP && toP <= BlackKing &&
7642              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7643              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7644             /* Clicked again on same color piece -- changed his mind */
7645             second = (x == fromX && y == fromY);
7646             killX = killY = -1;
7647             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7648                 second = FALSE; // first double-click rather than scond click
7649                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7650             }
7651             promoDefaultAltered = FALSE;
7652             MarkTargetSquares(1);
7653            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7654             if (appData.highlightDragging) {
7655                 SetHighlights(x, y, -1, -1);
7656             } else {
7657                 ClearHighlights();
7658             }
7659             if (OKToStartUserMove(x, y)) {
7660                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7661                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7662                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7663                  gatingPiece = boards[currentMove][fromY][fromX];
7664                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7665                 fromX = x;
7666                 fromY = y; dragging = 1;
7667                 if(!second) ReportClick("lift", x, y);
7668                 MarkTargetSquares(0);
7669                 DragPieceBegin(xPix, yPix, FALSE);
7670                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7671                     promoSweep = defaultPromoChoice;
7672                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7673                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7674                 }
7675             }
7676            }
7677            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7678            second = FALSE;
7679         }
7680         // ignore clicks on holdings
7681         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7682     }
7683
7684     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7685         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7686         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7687         return;
7688     }
7689
7690     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7691         DragPieceEnd(xPix, yPix); dragging = 0;
7692         if(clearFlag) {
7693             // a deferred attempt to click-click move an empty square on top of a piece
7694             boards[currentMove][y][x] = EmptySquare;
7695             ClearHighlights();
7696             DrawPosition(FALSE, boards[currentMove]);
7697             fromX = fromY = -1; clearFlag = 0;
7698             return;
7699         }
7700         if (appData.animateDragging) {
7701             /* Undo animation damage if any */
7702             DrawPosition(FALSE, NULL);
7703         }
7704         if (second) {
7705             /* Second up/down in same square; just abort move */
7706             second = 0;
7707             fromX = fromY = -1;
7708             gatingPiece = EmptySquare;
7709             MarkTargetSquares(1);
7710             ClearHighlights();
7711             gotPremove = 0;
7712             ClearPremoveHighlights();
7713         } else {
7714             /* First upclick in same square; start click-click mode */
7715             SetHighlights(x, y, -1, -1);
7716         }
7717         return;
7718     }
7719
7720     clearFlag = 0;
7721
7722     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7723        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7724         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7725         DisplayMessage(_("only marked squares are legal"),"");
7726         DrawPosition(TRUE, NULL);
7727         return; // ignore to-click
7728     }
7729
7730     /* we now have a different from- and (possibly off-board) to-square */
7731     /* Completed move */
7732     if(!sweepSelecting) {
7733         toX = x;
7734         toY = y;
7735     }
7736
7737     piece = boards[currentMove][fromY][fromX];
7738
7739     saveAnimate = appData.animate;
7740     if (clickType == Press) {
7741         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7742         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7743             // must be Edit Position mode with empty-square selected
7744             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7745             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7746             return;
7747         }
7748         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7749             return;
7750         }
7751         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7752             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7753         } else
7754         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7755         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7756           if(appData.sweepSelect) {
7757             promoSweep = defaultPromoChoice;
7758             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7759             selectFlag = 0; lastX = xPix; lastY = yPix;
7760             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7761             Sweep(0); // Pawn that is going to promote: preview promotion piece
7762             sweepSelecting = 1;
7763             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7764             MarkTargetSquares(1);
7765           }
7766           return; // promo popup appears on up-click
7767         }
7768         /* Finish clickclick move */
7769         if (appData.animate || appData.highlightLastMove) {
7770             SetHighlights(fromX, fromY, toX, toY);
7771         } else {
7772             ClearHighlights();
7773         }
7774     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7775         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7776         *promoRestrict = 0;
7777         if (appData.animate || appData.highlightLastMove) {
7778             SetHighlights(fromX, fromY, toX, toY);
7779         } else {
7780             ClearHighlights();
7781         }
7782     } else {
7783 #if 0
7784 // [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
7785         /* Finish drag move */
7786         if (appData.highlightLastMove) {
7787             SetHighlights(fromX, fromY, toX, toY);
7788         } else {
7789             ClearHighlights();
7790         }
7791 #endif
7792         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7793         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7794           dragging *= 2;            // flag button-less dragging if we are dragging
7795           MarkTargetSquares(1);
7796           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7797           else {
7798             kill2X = killX; kill2Y = killY;
7799             killX = x; killY = y;     //remeber this square as intermediate
7800             ReportClick("put", x, y); // and inform engine
7801             ReportClick("lift", x, y);
7802             MarkTargetSquares(0);
7803             return;
7804           }
7805         }
7806         DragPieceEnd(xPix, yPix); dragging = 0;
7807         /* Don't animate move and drag both */
7808         appData.animate = FALSE;
7809     }
7810
7811     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7812     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7813         ChessSquare piece = boards[currentMove][fromY][fromX];
7814         if(gameMode == EditPosition && piece != EmptySquare &&
7815            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7816             int n;
7817
7818             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7819                 n = PieceToNumber(piece - (int)BlackPawn);
7820                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7821                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7822                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7823             } else
7824             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7825                 n = PieceToNumber(piece);
7826                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7827                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7828                 boards[currentMove][n][BOARD_WIDTH-2]++;
7829             }
7830             boards[currentMove][fromY][fromX] = EmptySquare;
7831         }
7832         ClearHighlights();
7833         fromX = fromY = -1;
7834         MarkTargetSquares(1);
7835         DrawPosition(TRUE, boards[currentMove]);
7836         return;
7837     }
7838
7839     // off-board moves should not be highlighted
7840     if(x < 0 || y < 0) ClearHighlights();
7841     else ReportClick("put", x, y);
7842
7843     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7844
7845     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7846
7847     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7848         SetHighlights(fromX, fromY, toX, toY);
7849         MarkTargetSquares(1);
7850         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7851             // [HGM] super: promotion to captured piece selected from holdings
7852             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7853             promotionChoice = TRUE;
7854             // kludge follows to temporarily execute move on display, without promoting yet
7855             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7856             boards[currentMove][toY][toX] = p;
7857             DrawPosition(FALSE, boards[currentMove]);
7858             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7859             boards[currentMove][toY][toX] = q;
7860             DisplayMessage("Click in holdings to choose piece", "");
7861             return;
7862         }
7863         PromotionPopUp(promoChoice);
7864     } else {
7865         int oldMove = currentMove;
7866         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7867         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7868         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7869         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7870            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7871             DrawPosition(TRUE, boards[currentMove]);
7872         MarkTargetSquares(1);
7873         fromX = fromY = -1;
7874     }
7875     appData.animate = saveAnimate;
7876     if (appData.animate || appData.animateDragging) {
7877         /* Undo animation damage if needed */
7878         DrawPosition(FALSE, NULL);
7879     }
7880 }
7881
7882 int
7883 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7884 {   // front-end-free part taken out of PieceMenuPopup
7885     int whichMenu; int xSqr, ySqr;
7886
7887     if(seekGraphUp) { // [HGM] seekgraph
7888         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7889         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7890         return -2;
7891     }
7892
7893     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7894          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7895         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7896         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7897         if(action == Press)   {
7898             originalFlip = flipView;
7899             flipView = !flipView; // temporarily flip board to see game from partners perspective
7900             DrawPosition(TRUE, partnerBoard);
7901             DisplayMessage(partnerStatus, "");
7902             partnerUp = TRUE;
7903         } else if(action == Release) {
7904             flipView = originalFlip;
7905             DrawPosition(TRUE, boards[currentMove]);
7906             partnerUp = FALSE;
7907         }
7908         return -2;
7909     }
7910
7911     xSqr = EventToSquare(x, BOARD_WIDTH);
7912     ySqr = EventToSquare(y, BOARD_HEIGHT);
7913     if (action == Release) {
7914         if(pieceSweep != EmptySquare) {
7915             EditPositionMenuEvent(pieceSweep, toX, toY);
7916             pieceSweep = EmptySquare;
7917         } else UnLoadPV(); // [HGM] pv
7918     }
7919     if (action != Press) return -2; // return code to be ignored
7920     switch (gameMode) {
7921       case IcsExamining:
7922         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7923       case EditPosition:
7924         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7925         if (xSqr < 0 || ySqr < 0) return -1;
7926         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7927         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7928         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7929         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7930         NextPiece(0);
7931         return 2; // grab
7932       case IcsObserving:
7933         if(!appData.icsEngineAnalyze) return -1;
7934       case IcsPlayingWhite:
7935       case IcsPlayingBlack:
7936         if(!appData.zippyPlay) goto noZip;
7937       case AnalyzeMode:
7938       case AnalyzeFile:
7939       case MachinePlaysWhite:
7940       case MachinePlaysBlack:
7941       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7942         if (!appData.dropMenu) {
7943           LoadPV(x, y);
7944           return 2; // flag front-end to grab mouse events
7945         }
7946         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7947            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7948       case EditGame:
7949       noZip:
7950         if (xSqr < 0 || ySqr < 0) return -1;
7951         if (!appData.dropMenu || appData.testLegality &&
7952             gameInfo.variant != VariantBughouse &&
7953             gameInfo.variant != VariantCrazyhouse) return -1;
7954         whichMenu = 1; // drop menu
7955         break;
7956       default:
7957         return -1;
7958     }
7959
7960     if (((*fromX = xSqr) < 0) ||
7961         ((*fromY = ySqr) < 0)) {
7962         *fromX = *fromY = -1;
7963         return -1;
7964     }
7965     if (flipView)
7966       *fromX = BOARD_WIDTH - 1 - *fromX;
7967     else
7968       *fromY = BOARD_HEIGHT - 1 - *fromY;
7969
7970     return whichMenu;
7971 }
7972
7973 void
7974 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7975 {
7976 //    char * hint = lastHint;
7977     FrontEndProgramStats stats;
7978
7979     stats.which = cps == &first ? 0 : 1;
7980     stats.depth = cpstats->depth;
7981     stats.nodes = cpstats->nodes;
7982     stats.score = cpstats->score;
7983     stats.time = cpstats->time;
7984     stats.pv = cpstats->movelist;
7985     stats.hint = lastHint;
7986     stats.an_move_index = 0;
7987     stats.an_move_count = 0;
7988
7989     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7990         stats.hint = cpstats->move_name;
7991         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7992         stats.an_move_count = cpstats->nr_moves;
7993     }
7994
7995     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
7996
7997     SetProgramStats( &stats );
7998 }
7999
8000 void
8001 ClearEngineOutputPane (int which)
8002 {
8003     static FrontEndProgramStats dummyStats;
8004     dummyStats.which = which;
8005     dummyStats.pv = "#";
8006     SetProgramStats( &dummyStats );
8007 }
8008
8009 #define MAXPLAYERS 500
8010
8011 char *
8012 TourneyStandings (int display)
8013 {
8014     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8015     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8016     char result, *p, *names[MAXPLAYERS];
8017
8018     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8019         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8020     names[0] = p = strdup(appData.participants);
8021     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8022
8023     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8024
8025     while(result = appData.results[nr]) {
8026         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8027         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8028         wScore = bScore = 0;
8029         switch(result) {
8030           case '+': wScore = 2; break;
8031           case '-': bScore = 2; break;
8032           case '=': wScore = bScore = 1; break;
8033           case ' ':
8034           case '*': return strdup("busy"); // tourney not finished
8035         }
8036         score[w] += wScore;
8037         score[b] += bScore;
8038         games[w]++;
8039         games[b]++;
8040         nr++;
8041     }
8042     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8043     for(w=0; w<nPlayers; w++) {
8044         bScore = -1;
8045         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8046         ranking[w] = b; points[w] = bScore; score[b] = -2;
8047     }
8048     p = malloc(nPlayers*34+1);
8049     for(w=0; w<nPlayers && w<display; w++)
8050         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8051     free(names[0]);
8052     return p;
8053 }
8054
8055 void
8056 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8057 {       // count all piece types
8058         int p, f, r;
8059         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8060         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8061         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8062                 p = board[r][f];
8063                 pCnt[p]++;
8064                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8065                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8066                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8067                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8068                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8069                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8070         }
8071 }
8072
8073 int
8074 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8075 {
8076         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8077         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8078
8079         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8080         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8081         if(myPawns == 2 && nMine == 3) // KPP
8082             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8083         if(myPawns == 1 && nMine == 2) // KP
8084             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8085         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8086             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8087         if(myPawns) return FALSE;
8088         if(pCnt[WhiteRook+side])
8089             return pCnt[BlackRook-side] ||
8090                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8091                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8092                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8093         if(pCnt[WhiteCannon+side]) {
8094             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8095             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8096         }
8097         if(pCnt[WhiteKnight+side])
8098             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8099         return FALSE;
8100 }
8101
8102 int
8103 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8104 {
8105         VariantClass v = gameInfo.variant;
8106
8107         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8108         if(v == VariantShatranj) return TRUE; // always winnable through baring
8109         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8110         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8111
8112         if(v == VariantXiangqi) {
8113                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8114
8115                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8116                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8117                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8118                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8119                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8120                 if(stale) // we have at least one last-rank P plus perhaps C
8121                     return majors // KPKX
8122                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8123                 else // KCA*E*
8124                     return pCnt[WhiteFerz+side] // KCAK
8125                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8126                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8127                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8128
8129         } else if(v == VariantKnightmate) {
8130                 if(nMine == 1) return FALSE;
8131                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8132         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8133                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8134
8135                 if(nMine == 1) return FALSE; // bare King
8136                 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
8137                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8138                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8139                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8140                 if(pCnt[WhiteKnight+side])
8141                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8142                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8143                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8144                 if(nBishops)
8145                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8146                 if(pCnt[WhiteAlfil+side])
8147                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8148                 if(pCnt[WhiteWazir+side])
8149                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8150         }
8151
8152         return TRUE;
8153 }
8154
8155 int
8156 CompareWithRights (Board b1, Board b2)
8157 {
8158     int rights = 0;
8159     if(!CompareBoards(b1, b2)) return FALSE;
8160     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8161     /* compare castling rights */
8162     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8163            rights++; /* King lost rights, while rook still had them */
8164     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8165         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8166            rights++; /* but at least one rook lost them */
8167     }
8168     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8169            rights++;
8170     if( b1[CASTLING][5] != NoRights ) {
8171         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8172            rights++;
8173     }
8174     return rights == 0;
8175 }
8176
8177 int
8178 Adjudicate (ChessProgramState *cps)
8179 {       // [HGM] some adjudications useful with buggy engines
8180         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8181         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8182         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8183         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8184         int k, drop, count = 0; static int bare = 1;
8185         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8186         Boolean canAdjudicate = !appData.icsActive;
8187
8188         // most tests only when we understand the game, i.e. legality-checking on
8189             if( appData.testLegality )
8190             {   /* [HGM] Some more adjudications for obstinate engines */
8191                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8192                 static int moveCount = 6;
8193                 ChessMove result;
8194                 char *reason = NULL;
8195
8196                 /* Count what is on board. */
8197                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8198
8199                 /* Some material-based adjudications that have to be made before stalemate test */
8200                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8201                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8202                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8203                      if(canAdjudicate && appData.checkMates) {
8204                          if(engineOpponent)
8205                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8206                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8207                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8208                          return 1;
8209                      }
8210                 }
8211
8212                 /* Bare King in Shatranj (loses) or Losers (wins) */
8213                 if( nrW == 1 || nrB == 1) {
8214                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8215                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8216                      if(canAdjudicate && appData.checkMates) {
8217                          if(engineOpponent)
8218                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8219                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8220                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8221                          return 1;
8222                      }
8223                   } else
8224                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8225                   {    /* bare King */
8226                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8227                         if(canAdjudicate && appData.checkMates) {
8228                             /* but only adjudicate if adjudication enabled */
8229                             if(engineOpponent)
8230                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8231                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8232                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8233                             return 1;
8234                         }
8235                   }
8236                 } else bare = 1;
8237
8238
8239             // don't wait for engine to announce game end if we can judge ourselves
8240             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8241               case MT_CHECK:
8242                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8243                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8244                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8245                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8246                             checkCnt++;
8247                         if(checkCnt >= 2) {
8248                             reason = "Xboard adjudication: 3rd check";
8249                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8250                             break;
8251                         }
8252                     }
8253                 }
8254               case MT_NONE:
8255               default:
8256                 break;
8257               case MT_STEALMATE:
8258               case MT_STALEMATE:
8259               case MT_STAINMATE:
8260                 reason = "Xboard adjudication: Stalemate";
8261                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8262                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8263                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8264                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8265                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8266                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8267                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8268                                                                         EP_CHECKMATE : EP_WINS);
8269                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8270                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8271                 }
8272                 break;
8273               case MT_CHECKMATE:
8274                 reason = "Xboard adjudication: Checkmate";
8275                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8276                 if(gameInfo.variant == VariantShogi) {
8277                     if(forwardMostMove > backwardMostMove
8278                        && moveList[forwardMostMove-1][1] == '@'
8279                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8280                         reason = "XBoard adjudication: pawn-drop mate";
8281                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8282                     }
8283                 }
8284                 break;
8285             }
8286
8287                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8288                     case EP_STALEMATE:
8289                         result = GameIsDrawn; break;
8290                     case EP_CHECKMATE:
8291                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8292                     case EP_WINS:
8293                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8294                     default:
8295                         result = EndOfFile;
8296                 }
8297                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8298                     if(engineOpponent)
8299                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8300                     GameEnds( result, reason, GE_XBOARD );
8301                     return 1;
8302                 }
8303
8304                 /* Next absolutely insufficient mating material. */
8305                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8306                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8307                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8308
8309                      /* always flag draws, for judging claims */
8310                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8311
8312                      if(canAdjudicate && appData.materialDraws) {
8313                          /* but only adjudicate them if adjudication enabled */
8314                          if(engineOpponent) {
8315                            SendToProgram("force\n", engineOpponent); // suppress reply
8316                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8317                          }
8318                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8319                          return 1;
8320                      }
8321                 }
8322
8323                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8324                 if(gameInfo.variant == VariantXiangqi ?
8325                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8326                  : nrW + nrB == 4 &&
8327                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8328                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8329                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8330                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8331                    ) ) {
8332                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8333                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8334                           if(engineOpponent) {
8335                             SendToProgram("force\n", engineOpponent); // suppress reply
8336                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8337                           }
8338                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8339                           return 1;
8340                      }
8341                 } else moveCount = 6;
8342             }
8343
8344         // Repetition draws and 50-move rule can be applied independently of legality testing
8345
8346                 /* Check for rep-draws */
8347                 count = 0;
8348                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8349                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8350                 for(k = forwardMostMove-2;
8351                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8352                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8353                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8354                     k-=2)
8355                 {   int rights=0;
8356                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8357                         /* compare castling rights */
8358                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8359                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8360                                 rights++; /* King lost rights, while rook still had them */
8361                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8362                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8363                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8364                                    rights++; /* but at least one rook lost them */
8365                         }
8366                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8367                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8368                                 rights++;
8369                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8370                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8371                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8372                                    rights++;
8373                         }
8374                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8375                             && appData.drawRepeats > 1) {
8376                              /* adjudicate after user-specified nr of repeats */
8377                              int result = GameIsDrawn;
8378                              char *details = "XBoard adjudication: repetition draw";
8379                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8380                                 // [HGM] xiangqi: check for forbidden perpetuals
8381                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8382                                 for(m=forwardMostMove; m>k; m-=2) {
8383                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8384                                         ourPerpetual = 0; // the current mover did not always check
8385                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8386                                         hisPerpetual = 0; // the opponent did not always check
8387                                 }
8388                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8389                                                                         ourPerpetual, hisPerpetual);
8390                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8391                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8392                                     details = "Xboard adjudication: perpetual checking";
8393                                 } else
8394                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8395                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8396                                 } else
8397                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8398                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8399                                         result = BlackWins;
8400                                         details = "Xboard adjudication: repetition";
8401                                     }
8402                                 } else // it must be XQ
8403                                 // Now check for perpetual chases
8404                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8405                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8406                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8407                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8408                                         static char resdet[MSG_SIZ];
8409                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8410                                         details = resdet;
8411                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8412                                     } else
8413                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8414                                         break; // Abort repetition-checking loop.
8415                                 }
8416                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8417                              }
8418                              if(engineOpponent) {
8419                                SendToProgram("force\n", engineOpponent); // suppress reply
8420                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8421                              }
8422                              GameEnds( result, details, GE_XBOARD );
8423                              return 1;
8424                         }
8425                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8426                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8427                     }
8428                 }
8429
8430                 /* Now we test for 50-move draws. Determine ply count */
8431                 count = forwardMostMove;
8432                 /* look for last irreversble move */
8433                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8434                     count--;
8435                 /* if we hit starting position, add initial plies */
8436                 if( count == backwardMostMove )
8437                     count -= initialRulePlies;
8438                 count = forwardMostMove - count;
8439                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8440                         // adjust reversible move counter for checks in Xiangqi
8441                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8442                         if(i < backwardMostMove) i = backwardMostMove;
8443                         while(i <= forwardMostMove) {
8444                                 lastCheck = inCheck; // check evasion does not count
8445                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8446                                 if(inCheck || lastCheck) count--; // check does not count
8447                                 i++;
8448                         }
8449                 }
8450                 if( count >= 100)
8451                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8452                          /* this is used to judge if draw claims are legal */
8453                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8454                          if(engineOpponent) {
8455                            SendToProgram("force\n", engineOpponent); // suppress reply
8456                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8457                          }
8458                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8459                          return 1;
8460                 }
8461
8462                 /* if draw offer is pending, treat it as a draw claim
8463                  * when draw condition present, to allow engines a way to
8464                  * claim draws before making their move to avoid a race
8465                  * condition occurring after their move
8466                  */
8467                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8468                          char *p = NULL;
8469                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8470                              p = "Draw claim: 50-move rule";
8471                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8472                              p = "Draw claim: 3-fold repetition";
8473                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8474                              p = "Draw claim: insufficient mating material";
8475                          if( p != NULL && canAdjudicate) {
8476                              if(engineOpponent) {
8477                                SendToProgram("force\n", engineOpponent); // suppress reply
8478                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8479                              }
8480                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8481                              return 1;
8482                          }
8483                 }
8484
8485                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8486                     if(engineOpponent) {
8487                       SendToProgram("force\n", engineOpponent); // suppress reply
8488                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8489                     }
8490                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8491                     return 1;
8492                 }
8493         return 0;
8494 }
8495
8496 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8497 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8498 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8499
8500 static int
8501 BitbaseProbe ()
8502 {
8503     int pieces[10], squares[10], cnt=0, r, f, res;
8504     static int loaded;
8505     static PPROBE_EGBB probeBB;
8506     if(!appData.testLegality) return 10;
8507     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8508     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8509     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8510     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8511         ChessSquare piece = boards[forwardMostMove][r][f];
8512         int black = (piece >= BlackPawn);
8513         int type = piece - black*BlackPawn;
8514         if(piece == EmptySquare) continue;
8515         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8516         if(type == WhiteKing) type = WhiteQueen + 1;
8517         type = egbbCode[type];
8518         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8519         pieces[cnt] = type + black*6;
8520         if(++cnt > 5) return 11;
8521     }
8522     pieces[cnt] = squares[cnt] = 0;
8523     // probe EGBB
8524     if(loaded == 2) return 13; // loading failed before
8525     if(loaded == 0) {
8526         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8527         HMODULE lib;
8528         PLOAD_EGBB loadBB;
8529         loaded = 2; // prepare for failure
8530         if(!path) return 13; // no egbb installed
8531         strncpy(buf, path + 8, MSG_SIZ);
8532         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8533         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8534         lib = LoadLibrary(buf);
8535         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8536         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8537         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8538         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8539         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8540         loaded = 1; // success!
8541     }
8542     res = probeBB(forwardMostMove & 1, pieces, squares);
8543     return res > 0 ? 1 : res < 0 ? -1 : 0;
8544 }
8545
8546 char *
8547 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8548 {   // [HGM] book: this routine intercepts moves to simulate book replies
8549     char *bookHit = NULL;
8550
8551     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8552         char buf[MSG_SIZ];
8553         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8554         SendToProgram(buf, cps);
8555     }
8556     //first determine if the incoming move brings opponent into his book
8557     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8558         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8559     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8560     if(bookHit != NULL && !cps->bookSuspend) {
8561         // make sure opponent is not going to reply after receiving move to book position
8562         SendToProgram("force\n", cps);
8563         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8564     }
8565     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8566     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8567     // now arrange restart after book miss
8568     if(bookHit) {
8569         // after a book hit we never send 'go', and the code after the call to this routine
8570         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8571         char buf[MSG_SIZ], *move = bookHit;
8572         if(cps->useSAN) {
8573             int fromX, fromY, toX, toY;
8574             char promoChar;
8575             ChessMove moveType;
8576             move = buf + 30;
8577             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8578                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8579                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8580                                     PosFlags(forwardMostMove),
8581                                     fromY, fromX, toY, toX, promoChar, move);
8582             } else {
8583                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8584                 bookHit = NULL;
8585             }
8586         }
8587         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8588         SendToProgram(buf, cps);
8589         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8590     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8591         SendToProgram("go\n", cps);
8592         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8593     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8594         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8595             SendToProgram("go\n", cps);
8596         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8597     }
8598     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8599 }
8600
8601 int
8602 LoadError (char *errmess, ChessProgramState *cps)
8603 {   // unloads engine and switches back to -ncp mode if it was first
8604     if(cps->initDone) return FALSE;
8605     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8606     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8607     cps->pr = NoProc;
8608     if(cps == &first) {
8609         appData.noChessProgram = TRUE;
8610         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8611         gameMode = BeginningOfGame; ModeHighlight();
8612         SetNCPMode();
8613     }
8614     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8615     DisplayMessage("", ""); // erase waiting message
8616     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8617     return TRUE;
8618 }
8619
8620 char *savedMessage;
8621 ChessProgramState *savedState;
8622 void
8623 DeferredBookMove (void)
8624 {
8625         if(savedState->lastPing != savedState->lastPong)
8626                     ScheduleDelayedEvent(DeferredBookMove, 10);
8627         else
8628         HandleMachineMove(savedMessage, savedState);
8629 }
8630
8631 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8632 static ChessProgramState *stalledEngine;
8633 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8634
8635 void
8636 HandleMachineMove (char *message, ChessProgramState *cps)
8637 {
8638     static char firstLeg[20];
8639     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8640     char realname[MSG_SIZ];
8641     int fromX, fromY, toX, toY;
8642     ChessMove moveType;
8643     char promoChar, roar;
8644     char *p, *pv=buf1;
8645     int machineWhite, oldError;
8646     char *bookHit;
8647
8648     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8649         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8650         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8651             DisplayError(_("Invalid pairing from pairing engine"), 0);
8652             return;
8653         }
8654         pairingReceived = 1;
8655         NextMatchGame();
8656         return; // Skim the pairing messages here.
8657     }
8658
8659     oldError = cps->userError; cps->userError = 0;
8660
8661 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8662     /*
8663      * Kludge to ignore BEL characters
8664      */
8665     while (*message == '\007') message++;
8666
8667     /*
8668      * [HGM] engine debug message: ignore lines starting with '#' character
8669      */
8670     if(cps->debug && *message == '#') return;
8671
8672     /*
8673      * Look for book output
8674      */
8675     if (cps == &first && bookRequested) {
8676         if (message[0] == '\t' || message[0] == ' ') {
8677             /* Part of the book output is here; append it */
8678             strcat(bookOutput, message);
8679             strcat(bookOutput, "  \n");
8680             return;
8681         } else if (bookOutput[0] != NULLCHAR) {
8682             /* All of book output has arrived; display it */
8683             char *p = bookOutput;
8684             while (*p != NULLCHAR) {
8685                 if (*p == '\t') *p = ' ';
8686                 p++;
8687             }
8688             DisplayInformation(bookOutput);
8689             bookRequested = FALSE;
8690             /* Fall through to parse the current output */
8691         }
8692     }
8693
8694     /*
8695      * Look for machine move.
8696      */
8697     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8698         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8699     {
8700         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8701             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8702             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8703             stalledEngine = cps;
8704             if(appData.ponderNextMove) { // bring opponent out of ponder
8705                 if(gameMode == TwoMachinesPlay) {
8706                     if(cps->other->pause)
8707                         PauseEngine(cps->other);
8708                     else
8709                         SendToProgram("easy\n", cps->other);
8710                 }
8711             }
8712             StopClocks();
8713             return;
8714         }
8715
8716       if(cps->usePing) {
8717
8718         /* This method is only useful on engines that support ping */
8719         if(abortEngineThink) {
8720             if (appData.debugMode) {
8721                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8722             }
8723             SendToProgram("undo\n", cps);
8724             return;
8725         }
8726
8727         if (cps->lastPing != cps->lastPong) {
8728             /* Extra move from before last new; ignore */
8729             if (appData.debugMode) {
8730                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8731             }
8732           return;
8733         }
8734
8735       } else {
8736
8737         switch (gameMode) {
8738           case BeginningOfGame:
8739             /* Extra move from before last reset; ignore */
8740             if (appData.debugMode) {
8741                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8742             }
8743             return;
8744
8745           case EndOfGame:
8746           case IcsIdle:
8747           default:
8748             /* Extra move after we tried to stop.  The mode test is
8749                not a reliable way of detecting this problem, but it's
8750                the best we can do on engines that don't support ping.
8751             */
8752             if (appData.debugMode) {
8753                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8754                         cps->which, gameMode);
8755             }
8756             SendToProgram("undo\n", cps);
8757             return;
8758
8759           case MachinePlaysWhite:
8760           case IcsPlayingWhite:
8761             machineWhite = TRUE;
8762             break;
8763
8764           case MachinePlaysBlack:
8765           case IcsPlayingBlack:
8766             machineWhite = FALSE;
8767             break;
8768
8769           case TwoMachinesPlay:
8770             machineWhite = (cps->twoMachinesColor[0] == 'w');
8771             break;
8772         }
8773         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8774             if (appData.debugMode) {
8775                 fprintf(debugFP,
8776                         "Ignoring move out of turn by %s, gameMode %d"
8777                         ", forwardMost %d\n",
8778                         cps->which, gameMode, forwardMostMove);
8779             }
8780             return;
8781         }
8782       }
8783
8784         if(cps->alphaRank) AlphaRank(machineMove, 4);
8785
8786         // [HGM] lion: (some very limited) support for Alien protocol
8787         killX = killY = kill2X = kill2Y = -1;
8788         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8789             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8790             return;
8791         }
8792         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8793             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8794             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8795         }
8796         if(firstLeg[0]) { // there was a previous leg;
8797             // only support case where same piece makes two step
8798             char buf[20], *p = machineMove+1, *q = buf+1, f;
8799             safeStrCpy(buf, machineMove, 20);
8800             while(isdigit(*q)) q++; // find start of to-square
8801             safeStrCpy(machineMove, firstLeg, 20);
8802             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8803             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8804             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8805             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8806             firstLeg[0] = NULLCHAR;
8807         }
8808
8809         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8810                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8811             /* Machine move could not be parsed; ignore it. */
8812           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8813                     machineMove, _(cps->which));
8814             DisplayMoveError(buf1);
8815             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8816                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8817             if (gameMode == TwoMachinesPlay) {
8818               GameEnds(machineWhite ? BlackWins : WhiteWins,
8819                        buf1, GE_XBOARD);
8820             }
8821             return;
8822         }
8823
8824         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8825         /* So we have to redo legality test with true e.p. status here,  */
8826         /* to make sure an illegal e.p. capture does not slip through,   */
8827         /* to cause a forfeit on a justified illegal-move complaint      */
8828         /* of the opponent.                                              */
8829         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8830            ChessMove moveType;
8831            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8832                              fromY, fromX, toY, toX, promoChar);
8833             if(moveType == IllegalMove) {
8834               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8835                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8836                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8837                            buf1, GE_XBOARD);
8838                 return;
8839            } else if(!appData.fischerCastling)
8840            /* [HGM] Kludge to handle engines that send FRC-style castling
8841               when they shouldn't (like TSCP-Gothic) */
8842            switch(moveType) {
8843              case WhiteASideCastleFR:
8844              case BlackASideCastleFR:
8845                toX+=2;
8846                currentMoveString[2]++;
8847                break;
8848              case WhiteHSideCastleFR:
8849              case BlackHSideCastleFR:
8850                toX--;
8851                currentMoveString[2]--;
8852                break;
8853              default: ; // nothing to do, but suppresses warning of pedantic compilers
8854            }
8855         }
8856         hintRequested = FALSE;
8857         lastHint[0] = NULLCHAR;
8858         bookRequested = FALSE;
8859         /* Program may be pondering now */
8860         cps->maybeThinking = TRUE;
8861         if (cps->sendTime == 2) cps->sendTime = 1;
8862         if (cps->offeredDraw) cps->offeredDraw--;
8863
8864         /* [AS] Save move info*/
8865         pvInfoList[ forwardMostMove ].score = programStats.score;
8866         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8867         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8868
8869         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8870
8871         /* Test suites abort the 'game' after one move */
8872         if(*appData.finger) {
8873            static FILE *f;
8874            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8875            if(!f) f = fopen(appData.finger, "w");
8876            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8877            else { DisplayFatalError("Bad output file", errno, 0); return; }
8878            free(fen);
8879            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8880         }
8881         if(appData.epd) {
8882            if(solvingTime >= 0) {
8883               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8884               totalTime += solvingTime; first.matchWins++;
8885            } else {
8886               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8887               second.matchWins++;
8888            }
8889            OutputKibitz(2, buf1);
8890            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8891         }
8892
8893         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8894         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8895             int count = 0;
8896
8897             while( count < adjudicateLossPlies ) {
8898                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8899
8900                 if( count & 1 ) {
8901                     score = -score; /* Flip score for winning side */
8902                 }
8903
8904                 if( score > appData.adjudicateLossThreshold ) {
8905                     break;
8906                 }
8907
8908                 count++;
8909             }
8910
8911             if( count >= adjudicateLossPlies ) {
8912                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8913
8914                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8915                     "Xboard adjudication",
8916                     GE_XBOARD );
8917
8918                 return;
8919             }
8920         }
8921
8922         if(Adjudicate(cps)) {
8923             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8924             return; // [HGM] adjudicate: for all automatic game ends
8925         }
8926
8927 #if ZIPPY
8928         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8929             first.initDone) {
8930           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8931                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8932                 SendToICS("draw ");
8933                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8934           }
8935           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8936           ics_user_moved = 1;
8937           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8938                 char buf[3*MSG_SIZ];
8939
8940                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8941                         programStats.score / 100.,
8942                         programStats.depth,
8943                         programStats.time / 100.,
8944                         (unsigned int)programStats.nodes,
8945                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8946                         programStats.movelist);
8947                 SendToICS(buf);
8948           }
8949         }
8950 #endif
8951
8952         /* [AS] Clear stats for next move */
8953         ClearProgramStats();
8954         thinkOutput[0] = NULLCHAR;
8955         hiddenThinkOutputState = 0;
8956
8957         bookHit = NULL;
8958         if (gameMode == TwoMachinesPlay) {
8959             /* [HGM] relaying draw offers moved to after reception of move */
8960             /* and interpreting offer as claim if it brings draw condition */
8961             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8962                 SendToProgram("draw\n", cps->other);
8963             }
8964             if (cps->other->sendTime) {
8965                 SendTimeRemaining(cps->other,
8966                                   cps->other->twoMachinesColor[0] == 'w');
8967             }
8968             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8969             if (firstMove && !bookHit) {
8970                 firstMove = FALSE;
8971                 if (cps->other->useColors) {
8972                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8973                 }
8974                 SendToProgram("go\n", cps->other);
8975             }
8976             cps->other->maybeThinking = TRUE;
8977         }
8978
8979         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8980
8981         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8982
8983         if (!pausing && appData.ringBellAfterMoves) {
8984             if(!roar) RingBell();
8985         }
8986
8987         /*
8988          * Reenable menu items that were disabled while
8989          * machine was thinking
8990          */
8991         if (gameMode != TwoMachinesPlay)
8992             SetUserThinkingEnables();
8993
8994         // [HGM] book: after book hit opponent has received move and is now in force mode
8995         // force the book reply into it, and then fake that it outputted this move by jumping
8996         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8997         if(bookHit) {
8998                 static char bookMove[MSG_SIZ]; // a bit generous?
8999
9000                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9001                 strcat(bookMove, bookHit);
9002                 message = bookMove;
9003                 cps = cps->other;
9004                 programStats.nodes = programStats.depth = programStats.time =
9005                 programStats.score = programStats.got_only_move = 0;
9006                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9007
9008                 if(cps->lastPing != cps->lastPong) {
9009                     savedMessage = message; // args for deferred call
9010                     savedState = cps;
9011                     ScheduleDelayedEvent(DeferredBookMove, 10);
9012                     return;
9013                 }
9014                 goto FakeBookMove;
9015         }
9016
9017         return;
9018     }
9019
9020     /* Set special modes for chess engines.  Later something general
9021      *  could be added here; for now there is just one kludge feature,
9022      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9023      *  when "xboard" is given as an interactive command.
9024      */
9025     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9026         cps->useSigint = FALSE;
9027         cps->useSigterm = FALSE;
9028     }
9029     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9030       ParseFeatures(message+8, cps);
9031       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9032     }
9033
9034     if (!strncmp(message, "setup ", 6) && 
9035         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9036           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9037                                         ) { // [HGM] allow first engine to define opening position
9038       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9039       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9040       *buf = NULLCHAR;
9041       if(sscanf(message, "setup (%s", buf) == 1) {
9042         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9043         ASSIGN(appData.pieceToCharTable, buf);
9044       }
9045       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9046       if(dummy >= 3) {
9047         while(message[s] && message[s++] != ' ');
9048         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9049            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9050             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9051             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9052           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9053           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9054           startedFromSetupPosition = FALSE;
9055         }
9056       }
9057       if(startedFromSetupPosition) return;
9058       ParseFEN(boards[0], &dummy, message+s, FALSE);
9059       DrawPosition(TRUE, boards[0]);
9060       CopyBoard(initialPosition, boards[0]);
9061       startedFromSetupPosition = TRUE;
9062       return;
9063     }
9064     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9065       ChessSquare piece = WhitePawn;
9066       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9067       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9068       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9069       piece += CharToPiece(ID & 255) - WhitePawn;
9070       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9071       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9072       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9073       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9074       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9075       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9076                                                && gameInfo.variant != VariantGreat
9077                                                && gameInfo.variant != VariantFairy    ) return;
9078       if(piece < EmptySquare) {
9079         pieceDefs = TRUE;
9080         ASSIGN(pieceDesc[piece], buf1);
9081         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9082       }
9083       return;
9084     }
9085     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9086       promoSweep = PieceToChar(forwardMostMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9087       Sweep(0);
9088       return;
9089     }
9090     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9091      * want this, I was asked to put it in, and obliged.
9092      */
9093     if (!strncmp(message, "setboard ", 9)) {
9094         Board initial_position;
9095
9096         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9097
9098         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9099             DisplayError(_("Bad FEN received from engine"), 0);
9100             return ;
9101         } else {
9102            Reset(TRUE, FALSE);
9103            CopyBoard(boards[0], initial_position);
9104            initialRulePlies = FENrulePlies;
9105            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9106            else gameMode = MachinePlaysBlack;
9107            DrawPosition(FALSE, boards[currentMove]);
9108         }
9109         return;
9110     }
9111
9112     /*
9113      * Look for communication commands
9114      */
9115     if (!strncmp(message, "telluser ", 9)) {
9116         if(message[9] == '\\' && message[10] == '\\')
9117             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9118         PlayTellSound();
9119         DisplayNote(message + 9);
9120         return;
9121     }
9122     if (!strncmp(message, "tellusererror ", 14)) {
9123         cps->userError = 1;
9124         if(message[14] == '\\' && message[15] == '\\')
9125             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9126         PlayTellSound();
9127         DisplayError(message + 14, 0);
9128         return;
9129     }
9130     if (!strncmp(message, "tellopponent ", 13)) {
9131       if (appData.icsActive) {
9132         if (loggedOn) {
9133           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9134           SendToICS(buf1);
9135         }
9136       } else {
9137         DisplayNote(message + 13);
9138       }
9139       return;
9140     }
9141     if (!strncmp(message, "tellothers ", 11)) {
9142       if (appData.icsActive) {
9143         if (loggedOn) {
9144           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9145           SendToICS(buf1);
9146         }
9147       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9148       return;
9149     }
9150     if (!strncmp(message, "tellall ", 8)) {
9151       if (appData.icsActive) {
9152         if (loggedOn) {
9153           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9154           SendToICS(buf1);
9155         }
9156       } else {
9157         DisplayNote(message + 8);
9158       }
9159       return;
9160     }
9161     if (strncmp(message, "warning", 7) == 0) {
9162         /* Undocumented feature, use tellusererror in new code */
9163         DisplayError(message, 0);
9164         return;
9165     }
9166     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9167         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9168         strcat(realname, " query");
9169         AskQuestion(realname, buf2, buf1, cps->pr);
9170         return;
9171     }
9172     /* Commands from the engine directly to ICS.  We don't allow these to be
9173      *  sent until we are logged on. Crafty kibitzes have been known to
9174      *  interfere with the login process.
9175      */
9176     if (loggedOn) {
9177         if (!strncmp(message, "tellics ", 8)) {
9178             SendToICS(message + 8);
9179             SendToICS("\n");
9180             return;
9181         }
9182         if (!strncmp(message, "tellicsnoalias ", 15)) {
9183             SendToICS(ics_prefix);
9184             SendToICS(message + 15);
9185             SendToICS("\n");
9186             return;
9187         }
9188         /* The following are for backward compatibility only */
9189         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9190             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9191             SendToICS(ics_prefix);
9192             SendToICS(message);
9193             SendToICS("\n");
9194             return;
9195         }
9196     }
9197     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9198         if(initPing == cps->lastPong) {
9199             if(gameInfo.variant == VariantUnknown) {
9200                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9201                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9202                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9203             }
9204             initPing = -1;
9205         }
9206         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9207             abortEngineThink = FALSE;
9208             DisplayMessage("", "");
9209             ThawUI();
9210         }
9211         return;
9212     }
9213     if(!strncmp(message, "highlight ", 10)) {
9214         if(appData.testLegality && !*engineVariant && appData.markers) return;
9215         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9216         return;
9217     }
9218     if(!strncmp(message, "click ", 6)) {
9219         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9220         if(appData.testLegality || !appData.oneClick) return;
9221         sscanf(message+6, "%c%d%c", &f, &y, &c);
9222         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9223         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9224         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9225         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9226         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9227         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9228             LeftClick(Release, lastLeftX, lastLeftY);
9229         controlKey  = (c == ',');
9230         LeftClick(Press, x, y);
9231         LeftClick(Release, x, y);
9232         first.highlight = f;
9233         return;
9234     }
9235     /*
9236      * If the move is illegal, cancel it and redraw the board.
9237      * Also deal with other error cases.  Matching is rather loose
9238      * here to accommodate engines written before the spec.
9239      */
9240     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9241         strncmp(message, "Error", 5) == 0) {
9242         if (StrStr(message, "name") ||
9243             StrStr(message, "rating") || StrStr(message, "?") ||
9244             StrStr(message, "result") || StrStr(message, "board") ||
9245             StrStr(message, "bk") || StrStr(message, "computer") ||
9246             StrStr(message, "variant") || StrStr(message, "hint") ||
9247             StrStr(message, "random") || StrStr(message, "depth") ||
9248             StrStr(message, "accepted")) {
9249             return;
9250         }
9251         if (StrStr(message, "protover")) {
9252           /* Program is responding to input, so it's apparently done
9253              initializing, and this error message indicates it is
9254              protocol version 1.  So we don't need to wait any longer
9255              for it to initialize and send feature commands. */
9256           FeatureDone(cps, 1);
9257           cps->protocolVersion = 1;
9258           return;
9259         }
9260         cps->maybeThinking = FALSE;
9261
9262         if (StrStr(message, "draw")) {
9263             /* Program doesn't have "draw" command */
9264             cps->sendDrawOffers = 0;
9265             return;
9266         }
9267         if (cps->sendTime != 1 &&
9268             (StrStr(message, "time") || StrStr(message, "otim"))) {
9269           /* Program apparently doesn't have "time" or "otim" command */
9270           cps->sendTime = 0;
9271           return;
9272         }
9273         if (StrStr(message, "analyze")) {
9274             cps->analysisSupport = FALSE;
9275             cps->analyzing = FALSE;
9276 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9277             EditGameEvent(); // [HGM] try to preserve loaded game
9278             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9279             DisplayError(buf2, 0);
9280             return;
9281         }
9282         if (StrStr(message, "(no matching move)st")) {
9283           /* Special kludge for GNU Chess 4 only */
9284           cps->stKludge = TRUE;
9285           SendTimeControl(cps, movesPerSession, timeControl,
9286                           timeIncrement, appData.searchDepth,
9287                           searchTime);
9288           return;
9289         }
9290         if (StrStr(message, "(no matching move)sd")) {
9291           /* Special kludge for GNU Chess 4 only */
9292           cps->sdKludge = TRUE;
9293           SendTimeControl(cps, movesPerSession, timeControl,
9294                           timeIncrement, appData.searchDepth,
9295                           searchTime);
9296           return;
9297         }
9298         if (!StrStr(message, "llegal")) {
9299             return;
9300         }
9301         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9302             gameMode == IcsIdle) return;
9303         if (forwardMostMove <= backwardMostMove) return;
9304         if (pausing) PauseEvent();
9305       if(appData.forceIllegal) {
9306             // [HGM] illegal: machine refused move; force position after move into it
9307           SendToProgram("force\n", cps);
9308           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9309                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9310                 // when black is to move, while there might be nothing on a2 or black
9311                 // might already have the move. So send the board as if white has the move.
9312                 // But first we must change the stm of the engine, as it refused the last move
9313                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9314                 if(WhiteOnMove(forwardMostMove)) {
9315                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9316                     SendBoard(cps, forwardMostMove); // kludgeless board
9317                 } else {
9318                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9319                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9320                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9321                 }
9322           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9323             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9324                  gameMode == TwoMachinesPlay)
9325               SendToProgram("go\n", cps);
9326             return;
9327       } else
9328         if (gameMode == PlayFromGameFile) {
9329             /* Stop reading this game file */
9330             gameMode = EditGame;
9331             ModeHighlight();
9332         }
9333         /* [HGM] illegal-move claim should forfeit game when Xboard */
9334         /* only passes fully legal moves                            */
9335         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9336             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9337                                 "False illegal-move claim", GE_XBOARD );
9338             return; // do not take back move we tested as valid
9339         }
9340         currentMove = forwardMostMove-1;
9341         DisplayMove(currentMove-1); /* before DisplayMoveError */
9342         SwitchClocks(forwardMostMove-1); // [HGM] race
9343         DisplayBothClocks();
9344         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9345                 parseList[currentMove], _(cps->which));
9346         DisplayMoveError(buf1);
9347         DrawPosition(FALSE, boards[currentMove]);
9348
9349         SetUserThinkingEnables();
9350         return;
9351     }
9352     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9353         /* Program has a broken "time" command that
9354            outputs a string not ending in newline.
9355            Don't use it. */
9356         cps->sendTime = 0;
9357     }
9358     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9359         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9360             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9361     }
9362
9363     /*
9364      * If chess program startup fails, exit with an error message.
9365      * Attempts to recover here are futile. [HGM] Well, we try anyway
9366      */
9367     if ((StrStr(message, "unknown host") != NULL)
9368         || (StrStr(message, "No remote directory") != NULL)
9369         || (StrStr(message, "not found") != NULL)
9370         || (StrStr(message, "No such file") != NULL)
9371         || (StrStr(message, "can't alloc") != NULL)
9372         || (StrStr(message, "Permission denied") != NULL)) {
9373
9374         cps->maybeThinking = FALSE;
9375         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9376                 _(cps->which), cps->program, cps->host, message);
9377         RemoveInputSource(cps->isr);
9378         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9379             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9380             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9381         }
9382         return;
9383     }
9384
9385     /*
9386      * Look for hint output
9387      */
9388     if (sscanf(message, "Hint: %s", buf1) == 1) {
9389         if (cps == &first && hintRequested) {
9390             hintRequested = FALSE;
9391             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9392                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9393                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9394                                     PosFlags(forwardMostMove),
9395                                     fromY, fromX, toY, toX, promoChar, buf1);
9396                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9397                 DisplayInformation(buf2);
9398             } else {
9399                 /* Hint move could not be parsed!? */
9400               snprintf(buf2, sizeof(buf2),
9401                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9402                         buf1, _(cps->which));
9403                 DisplayError(buf2, 0);
9404             }
9405         } else {
9406           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9407         }
9408         return;
9409     }
9410
9411     /*
9412      * Ignore other messages if game is not in progress
9413      */
9414     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9415         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9416
9417     /*
9418      * look for win, lose, draw, or draw offer
9419      */
9420     if (strncmp(message, "1-0", 3) == 0) {
9421         char *p, *q, *r = "";
9422         p = strchr(message, '{');
9423         if (p) {
9424             q = strchr(p, '}');
9425             if (q) {
9426                 *q = NULLCHAR;
9427                 r = p + 1;
9428             }
9429         }
9430         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9431         return;
9432     } else if (strncmp(message, "0-1", 3) == 0) {
9433         char *p, *q, *r = "";
9434         p = strchr(message, '{');
9435         if (p) {
9436             q = strchr(p, '}');
9437             if (q) {
9438                 *q = NULLCHAR;
9439                 r = p + 1;
9440             }
9441         }
9442         /* Kludge for Arasan 4.1 bug */
9443         if (strcmp(r, "Black resigns") == 0) {
9444             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9445             return;
9446         }
9447         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9448         return;
9449     } else if (strncmp(message, "1/2", 3) == 0) {
9450         char *p, *q, *r = "";
9451         p = strchr(message, '{');
9452         if (p) {
9453             q = strchr(p, '}');
9454             if (q) {
9455                 *q = NULLCHAR;
9456                 r = p + 1;
9457             }
9458         }
9459
9460         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9461         return;
9462
9463     } else if (strncmp(message, "White resign", 12) == 0) {
9464         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9465         return;
9466     } else if (strncmp(message, "Black resign", 12) == 0) {
9467         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9468         return;
9469     } else if (strncmp(message, "White matches", 13) == 0 ||
9470                strncmp(message, "Black matches", 13) == 0   ) {
9471         /* [HGM] ignore GNUShogi noises */
9472         return;
9473     } else if (strncmp(message, "White", 5) == 0 &&
9474                message[5] != '(' &&
9475                StrStr(message, "Black") == NULL) {
9476         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9477         return;
9478     } else if (strncmp(message, "Black", 5) == 0 &&
9479                message[5] != '(') {
9480         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9481         return;
9482     } else if (strcmp(message, "resign") == 0 ||
9483                strcmp(message, "computer resigns") == 0) {
9484         switch (gameMode) {
9485           case MachinePlaysBlack:
9486           case IcsPlayingBlack:
9487             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9488             break;
9489           case MachinePlaysWhite:
9490           case IcsPlayingWhite:
9491             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9492             break;
9493           case TwoMachinesPlay:
9494             if (cps->twoMachinesColor[0] == 'w')
9495               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9496             else
9497               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9498             break;
9499           default:
9500             /* can't happen */
9501             break;
9502         }
9503         return;
9504     } else if (strncmp(message, "opponent mates", 14) == 0) {
9505         switch (gameMode) {
9506           case MachinePlaysBlack:
9507           case IcsPlayingBlack:
9508             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9509             break;
9510           case MachinePlaysWhite:
9511           case IcsPlayingWhite:
9512             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9513             break;
9514           case TwoMachinesPlay:
9515             if (cps->twoMachinesColor[0] == 'w')
9516               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9517             else
9518               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9519             break;
9520           default:
9521             /* can't happen */
9522             break;
9523         }
9524         return;
9525     } else if (strncmp(message, "computer mates", 14) == 0) {
9526         switch (gameMode) {
9527           case MachinePlaysBlack:
9528           case IcsPlayingBlack:
9529             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9530             break;
9531           case MachinePlaysWhite:
9532           case IcsPlayingWhite:
9533             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9534             break;
9535           case TwoMachinesPlay:
9536             if (cps->twoMachinesColor[0] == 'w')
9537               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9538             else
9539               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9540             break;
9541           default:
9542             /* can't happen */
9543             break;
9544         }
9545         return;
9546     } else if (strncmp(message, "checkmate", 9) == 0) {
9547         if (WhiteOnMove(forwardMostMove)) {
9548             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9549         } else {
9550             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9551         }
9552         return;
9553     } else if (strstr(message, "Draw") != NULL ||
9554                strstr(message, "game is a draw") != NULL) {
9555         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9556         return;
9557     } else if (strstr(message, "offer") != NULL &&
9558                strstr(message, "draw") != NULL) {
9559 #if ZIPPY
9560         if (appData.zippyPlay && first.initDone) {
9561             /* Relay offer to ICS */
9562             SendToICS(ics_prefix);
9563             SendToICS("draw\n");
9564         }
9565 #endif
9566         cps->offeredDraw = 2; /* valid until this engine moves twice */
9567         if (gameMode == TwoMachinesPlay) {
9568             if (cps->other->offeredDraw) {
9569                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9570             /* [HGM] in two-machine mode we delay relaying draw offer      */
9571             /* until after we also have move, to see if it is really claim */
9572             }
9573         } else if (gameMode == MachinePlaysWhite ||
9574                    gameMode == MachinePlaysBlack) {
9575           if (userOfferedDraw) {
9576             DisplayInformation(_("Machine accepts your draw offer"));
9577             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9578           } else {
9579             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9580           }
9581         }
9582     }
9583
9584
9585     /*
9586      * Look for thinking output
9587      */
9588     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9589           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9590                                 ) {
9591         int plylev, mvleft, mvtot, curscore, time;
9592         char mvname[MOVE_LEN];
9593         u64 nodes; // [DM]
9594         char plyext;
9595         int ignore = FALSE;
9596         int prefixHint = FALSE;
9597         mvname[0] = NULLCHAR;
9598
9599         switch (gameMode) {
9600           case MachinePlaysBlack:
9601           case IcsPlayingBlack:
9602             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9603             break;
9604           case MachinePlaysWhite:
9605           case IcsPlayingWhite:
9606             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9607             break;
9608           case AnalyzeMode:
9609           case AnalyzeFile:
9610             break;
9611           case IcsObserving: /* [DM] icsEngineAnalyze */
9612             if (!appData.icsEngineAnalyze) ignore = TRUE;
9613             break;
9614           case TwoMachinesPlay:
9615             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9616                 ignore = TRUE;
9617             }
9618             break;
9619           default:
9620             ignore = TRUE;
9621             break;
9622         }
9623
9624         if (!ignore) {
9625             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9626             buf1[0] = NULLCHAR;
9627             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9628                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9629                 char score_buf[MSG_SIZ];
9630
9631                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9632                     nodes += u64Const(0x100000000);
9633
9634                 if (plyext != ' ' && plyext != '\t') {
9635                     time *= 100;
9636                 }
9637
9638                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9639                 if( cps->scoreIsAbsolute &&
9640                     ( gameMode == MachinePlaysBlack ||
9641                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9642                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9643                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9644                      !WhiteOnMove(currentMove)
9645                     ) )
9646                 {
9647                     curscore = -curscore;
9648                 }
9649
9650                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9651
9652                 if(*bestMove) { // rememer time best EPD move was first found
9653                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9654                     ChessMove mt;
9655                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9656                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9657                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9658                 }
9659
9660                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9661                         char buf[MSG_SIZ];
9662                         FILE *f;
9663                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9664                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9665                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9666                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9667                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9668                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9669                                 fclose(f);
9670                         }
9671                         else
9672                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9673                           DisplayError(_("failed writing PV"), 0);
9674                 }
9675
9676                 tempStats.depth = plylev;
9677                 tempStats.nodes = nodes;
9678                 tempStats.time = time;
9679                 tempStats.score = curscore;
9680                 tempStats.got_only_move = 0;
9681
9682                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9683                         int ticklen;
9684
9685                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9686                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9687                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9688                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9689                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9690                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9691                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9692                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9693                 }
9694
9695                 /* Buffer overflow protection */
9696                 if (pv[0] != NULLCHAR) {
9697                     if (strlen(pv) >= sizeof(tempStats.movelist)
9698                         && appData.debugMode) {
9699                         fprintf(debugFP,
9700                                 "PV is too long; using the first %u bytes.\n",
9701                                 (unsigned) sizeof(tempStats.movelist) - 1);
9702                     }
9703
9704                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9705                 } else {
9706                     sprintf(tempStats.movelist, " no PV\n");
9707                 }
9708
9709                 if (tempStats.seen_stat) {
9710                     tempStats.ok_to_send = 1;
9711                 }
9712
9713                 if (strchr(tempStats.movelist, '(') != NULL) {
9714                     tempStats.line_is_book = 1;
9715                     tempStats.nr_moves = 0;
9716                     tempStats.moves_left = 0;
9717                 } else {
9718                     tempStats.line_is_book = 0;
9719                 }
9720
9721                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9722                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9723
9724                 SendProgramStatsToFrontend( cps, &tempStats );
9725
9726                 /*
9727                     [AS] Protect the thinkOutput buffer from overflow... this
9728                     is only useful if buf1 hasn't overflowed first!
9729                 */
9730                 if(curscore >= MATE_SCORE) 
9731                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9732                 else if(curscore <= -MATE_SCORE) 
9733                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9734                 else
9735                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9736                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9737                          plylev,
9738                          (gameMode == TwoMachinesPlay ?
9739                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9740                          score_buf,
9741                          prefixHint ? lastHint : "",
9742                          prefixHint ? " " : "" );
9743
9744                 if( buf1[0] != NULLCHAR ) {
9745                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9746
9747                     if( strlen(pv) > max_len ) {
9748                         if( appData.debugMode) {
9749                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9750                         }
9751                         pv[max_len+1] = '\0';
9752                     }
9753
9754                     strcat( thinkOutput, pv);
9755                 }
9756
9757                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9758                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9759                     DisplayMove(currentMove - 1);
9760                 }
9761                 return;
9762
9763             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9764                 /* crafty (9.25+) says "(only move) <move>"
9765                  * if there is only 1 legal move
9766                  */
9767                 sscanf(p, "(only move) %s", buf1);
9768                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9769                 sprintf(programStats.movelist, "%s (only move)", buf1);
9770                 programStats.depth = 1;
9771                 programStats.nr_moves = 1;
9772                 programStats.moves_left = 1;
9773                 programStats.nodes = 1;
9774                 programStats.time = 1;
9775                 programStats.got_only_move = 1;
9776
9777                 /* Not really, but we also use this member to
9778                    mean "line isn't going to change" (Crafty
9779                    isn't searching, so stats won't change) */
9780                 programStats.line_is_book = 1;
9781
9782                 SendProgramStatsToFrontend( cps, &programStats );
9783
9784                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9785                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9786                     DisplayMove(currentMove - 1);
9787                 }
9788                 return;
9789             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9790                               &time, &nodes, &plylev, &mvleft,
9791                               &mvtot, mvname) >= 5) {
9792                 /* The stat01: line is from Crafty (9.29+) in response
9793                    to the "." command */
9794                 programStats.seen_stat = 1;
9795                 cps->maybeThinking = TRUE;
9796
9797                 if (programStats.got_only_move || !appData.periodicUpdates)
9798                   return;
9799
9800                 programStats.depth = plylev;
9801                 programStats.time = time;
9802                 programStats.nodes = nodes;
9803                 programStats.moves_left = mvleft;
9804                 programStats.nr_moves = mvtot;
9805                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9806                 programStats.ok_to_send = 1;
9807                 programStats.movelist[0] = '\0';
9808
9809                 SendProgramStatsToFrontend( cps, &programStats );
9810
9811                 return;
9812
9813             } else if (strncmp(message,"++",2) == 0) {
9814                 /* Crafty 9.29+ outputs this */
9815                 programStats.got_fail = 2;
9816                 return;
9817
9818             } else if (strncmp(message,"--",2) == 0) {
9819                 /* Crafty 9.29+ outputs this */
9820                 programStats.got_fail = 1;
9821                 return;
9822
9823             } else if (thinkOutput[0] != NULLCHAR &&
9824                        strncmp(message, "    ", 4) == 0) {
9825                 unsigned message_len;
9826
9827                 p = message;
9828                 while (*p && *p == ' ') p++;
9829
9830                 message_len = strlen( p );
9831
9832                 /* [AS] Avoid buffer overflow */
9833                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9834                     strcat(thinkOutput, " ");
9835                     strcat(thinkOutput, p);
9836                 }
9837
9838                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9839                     strcat(programStats.movelist, " ");
9840                     strcat(programStats.movelist, p);
9841                 }
9842
9843                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9844                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9845                     DisplayMove(currentMove - 1);
9846                 }
9847                 return;
9848             }
9849         }
9850         else {
9851             buf1[0] = NULLCHAR;
9852
9853             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9854                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9855             {
9856                 ChessProgramStats cpstats;
9857
9858                 if (plyext != ' ' && plyext != '\t') {
9859                     time *= 100;
9860                 }
9861
9862                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9863                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9864                     curscore = -curscore;
9865                 }
9866
9867                 cpstats.depth = plylev;
9868                 cpstats.nodes = nodes;
9869                 cpstats.time = time;
9870                 cpstats.score = curscore;
9871                 cpstats.got_only_move = 0;
9872                 cpstats.movelist[0] = '\0';
9873
9874                 if (buf1[0] != NULLCHAR) {
9875                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9876                 }
9877
9878                 cpstats.ok_to_send = 0;
9879                 cpstats.line_is_book = 0;
9880                 cpstats.nr_moves = 0;
9881                 cpstats.moves_left = 0;
9882
9883                 SendProgramStatsToFrontend( cps, &cpstats );
9884             }
9885         }
9886     }
9887 }
9888
9889
9890 /* Parse a game score from the character string "game", and
9891    record it as the history of the current game.  The game
9892    score is NOT assumed to start from the standard position.
9893    The display is not updated in any way.
9894    */
9895 void
9896 ParseGameHistory (char *game)
9897 {
9898     ChessMove moveType;
9899     int fromX, fromY, toX, toY, boardIndex;
9900     char promoChar;
9901     char *p, *q;
9902     char buf[MSG_SIZ];
9903
9904     if (appData.debugMode)
9905       fprintf(debugFP, "Parsing game history: %s\n", game);
9906
9907     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9908     gameInfo.site = StrSave(appData.icsHost);
9909     gameInfo.date = PGNDate();
9910     gameInfo.round = StrSave("-");
9911
9912     /* Parse out names of players */
9913     while (*game == ' ') game++;
9914     p = buf;
9915     while (*game != ' ') *p++ = *game++;
9916     *p = NULLCHAR;
9917     gameInfo.white = StrSave(buf);
9918     while (*game == ' ') game++;
9919     p = buf;
9920     while (*game != ' ' && *game != '\n') *p++ = *game++;
9921     *p = NULLCHAR;
9922     gameInfo.black = StrSave(buf);
9923
9924     /* Parse moves */
9925     boardIndex = blackPlaysFirst ? 1 : 0;
9926     yynewstr(game);
9927     for (;;) {
9928         yyboardindex = boardIndex;
9929         moveType = (ChessMove) Myylex();
9930         switch (moveType) {
9931           case IllegalMove:             /* maybe suicide chess, etc. */
9932   if (appData.debugMode) {
9933     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9934     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9935     setbuf(debugFP, NULL);
9936   }
9937           case WhitePromotion:
9938           case BlackPromotion:
9939           case WhiteNonPromotion:
9940           case BlackNonPromotion:
9941           case NormalMove:
9942           case FirstLeg:
9943           case WhiteCapturesEnPassant:
9944           case BlackCapturesEnPassant:
9945           case WhiteKingSideCastle:
9946           case WhiteQueenSideCastle:
9947           case BlackKingSideCastle:
9948           case BlackQueenSideCastle:
9949           case WhiteKingSideCastleWild:
9950           case WhiteQueenSideCastleWild:
9951           case BlackKingSideCastleWild:
9952           case BlackQueenSideCastleWild:
9953           /* PUSH Fabien */
9954           case WhiteHSideCastleFR:
9955           case WhiteASideCastleFR:
9956           case BlackHSideCastleFR:
9957           case BlackASideCastleFR:
9958           /* POP Fabien */
9959             fromX = currentMoveString[0] - AAA;
9960             fromY = currentMoveString[1] - ONE;
9961             toX = currentMoveString[2] - AAA;
9962             toY = currentMoveString[3] - ONE;
9963             promoChar = currentMoveString[4];
9964             break;
9965           case WhiteDrop:
9966           case BlackDrop:
9967             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9968             fromX = moveType == WhiteDrop ?
9969               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9970             (int) CharToPiece(ToLower(currentMoveString[0]));
9971             fromY = DROP_RANK;
9972             toX = currentMoveString[2] - AAA;
9973             toY = currentMoveString[3] - ONE;
9974             promoChar = NULLCHAR;
9975             break;
9976           case AmbiguousMove:
9977             /* bug? */
9978             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9979   if (appData.debugMode) {
9980     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9981     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9982     setbuf(debugFP, NULL);
9983   }
9984             DisplayError(buf, 0);
9985             return;
9986           case ImpossibleMove:
9987             /* bug? */
9988             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9989   if (appData.debugMode) {
9990     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9991     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9992     setbuf(debugFP, NULL);
9993   }
9994             DisplayError(buf, 0);
9995             return;
9996           case EndOfFile:
9997             if (boardIndex < backwardMostMove) {
9998                 /* Oops, gap.  How did that happen? */
9999                 DisplayError(_("Gap in move list"), 0);
10000                 return;
10001             }
10002             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10003             if (boardIndex > forwardMostMove) {
10004                 forwardMostMove = boardIndex;
10005             }
10006             return;
10007           case ElapsedTime:
10008             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10009                 strcat(parseList[boardIndex-1], " ");
10010                 strcat(parseList[boardIndex-1], yy_text);
10011             }
10012             continue;
10013           case Comment:
10014           case PGNTag:
10015           case NAG:
10016           default:
10017             /* ignore */
10018             continue;
10019           case WhiteWins:
10020           case BlackWins:
10021           case GameIsDrawn:
10022           case GameUnfinished:
10023             if (gameMode == IcsExamining) {
10024                 if (boardIndex < backwardMostMove) {
10025                     /* Oops, gap.  How did that happen? */
10026                     return;
10027                 }
10028                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10029                 return;
10030             }
10031             gameInfo.result = moveType;
10032             p = strchr(yy_text, '{');
10033             if (p == NULL) p = strchr(yy_text, '(');
10034             if (p == NULL) {
10035                 p = yy_text;
10036                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10037             } else {
10038                 q = strchr(p, *p == '{' ? '}' : ')');
10039                 if (q != NULL) *q = NULLCHAR;
10040                 p++;
10041             }
10042             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10043             gameInfo.resultDetails = StrSave(p);
10044             continue;
10045         }
10046         if (boardIndex >= forwardMostMove &&
10047             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10048             backwardMostMove = blackPlaysFirst ? 1 : 0;
10049             return;
10050         }
10051         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10052                                  fromY, fromX, toY, toX, promoChar,
10053                                  parseList[boardIndex]);
10054         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10055         /* currentMoveString is set as a side-effect of yylex */
10056         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10057         strcat(moveList[boardIndex], "\n");
10058         boardIndex++;
10059         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10060         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10061           case MT_NONE:
10062           case MT_STALEMATE:
10063           default:
10064             break;
10065           case MT_CHECK:
10066             if(!IS_SHOGI(gameInfo.variant))
10067                 strcat(parseList[boardIndex - 1], "+");
10068             break;
10069           case MT_CHECKMATE:
10070           case MT_STAINMATE:
10071             strcat(parseList[boardIndex - 1], "#");
10072             break;
10073         }
10074     }
10075 }
10076
10077
10078 /* Apply a move to the given board  */
10079 void
10080 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10081 {
10082   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10083   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10084
10085     /* [HGM] compute & store e.p. status and castling rights for new position */
10086     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10087
10088       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10089       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10090       board[EP_STATUS] = EP_NONE;
10091       board[EP_FILE] = board[EP_RANK] = 100;
10092
10093   if (fromY == DROP_RANK) {
10094         /* must be first */
10095         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10096             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10097             return;
10098         }
10099         piece = board[toY][toX] = (ChessSquare) fromX;
10100   } else {
10101 //      ChessSquare victim;
10102       int i;
10103
10104       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10105 //           victim = board[killY][killX],
10106            killed = board[killY][killX],
10107            board[killY][killX] = EmptySquare,
10108            board[EP_STATUS] = EP_CAPTURE;
10109            if( kill2X >= 0 && kill2Y >= 0)
10110              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10111       }
10112
10113       if( board[toY][toX] != EmptySquare ) {
10114            board[EP_STATUS] = EP_CAPTURE;
10115            if( (fromX != toX || fromY != toY) && // not igui!
10116                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10117                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10118                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10119            }
10120       }
10121
10122       pawn = board[fromY][fromX];
10123       if( pawn == WhiteLance || pawn == BlackLance ) {
10124            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10125                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10126                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10127            }
10128       }
10129       if( pawn == WhitePawn ) {
10130            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10131                board[EP_STATUS] = EP_PAWN_MOVE;
10132            if( toY-fromY>=2) {
10133                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10134                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10135                         gameInfo.variant != VariantBerolina || toX < fromX)
10136                       board[EP_STATUS] = toX | berolina;
10137                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10138                         gameInfo.variant != VariantBerolina || toX > fromX)
10139                       board[EP_STATUS] = toX;
10140            }
10141       } else
10142       if( pawn == BlackPawn ) {
10143            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10144                board[EP_STATUS] = EP_PAWN_MOVE;
10145            if( toY-fromY<= -2) {
10146                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10147                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10148                         gameInfo.variant != VariantBerolina || toX < fromX)
10149                       board[EP_STATUS] = toX | berolina;
10150                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10151                         gameInfo.variant != VariantBerolina || toX > fromX)
10152                       board[EP_STATUS] = toX;
10153            }
10154        }
10155
10156        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10157        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10158        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10159        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10160
10161        for(i=0; i<nrCastlingRights; i++) {
10162            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10163               board[CASTLING][i] == toX   && castlingRank[i] == toY
10164              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10165        }
10166
10167        if(gameInfo.variant == VariantSChess) { // update virginity
10168            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10169            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10170            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10171            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10172        }
10173
10174      if (fromX == toX && fromY == toY) return;
10175
10176      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10177      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10178      if(gameInfo.variant == VariantKnightmate)
10179          king += (int) WhiteUnicorn - (int) WhiteKing;
10180
10181     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10182        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10183         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10184         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10185         board[EP_STATUS] = EP_NONE; // capture was fake!
10186     } else
10187     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10188         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10189         board[toY][toX] = piece;
10190         board[EP_STATUS] = EP_NONE; // capture was fake!
10191     } else
10192     /* Code added by Tord: */
10193     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10194     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10195         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10196       board[EP_STATUS] = EP_NONE; // capture was fake!
10197       board[fromY][fromX] = EmptySquare;
10198       board[toY][toX] = EmptySquare;
10199       if((toX > fromX) != (piece == WhiteRook)) {
10200         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10201       } else {
10202         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10203       }
10204     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10205                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10206       board[EP_STATUS] = EP_NONE;
10207       board[fromY][fromX] = EmptySquare;
10208       board[toY][toX] = EmptySquare;
10209       if((toX > fromX) != (piece == BlackRook)) {
10210         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10211       } else {
10212         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10213       }
10214     /* End of code added by Tord */
10215
10216     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10217         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10218         board[toY][toX] = piece;
10219     } else if (board[fromY][fromX] == king
10220         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10221         && toY == fromY && toX > fromX+1) {
10222         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10223         board[fromY][toX-1] = board[fromY][rookX];
10224         board[fromY][rookX] = EmptySquare;
10225         board[fromY][fromX] = EmptySquare;
10226         board[toY][toX] = king;
10227     } else if (board[fromY][fromX] == king
10228         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10229                && toY == fromY && toX < fromX-1) {
10230         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10231         board[fromY][toX+1] = board[fromY][rookX];
10232         board[fromY][rookX] = EmptySquare;
10233         board[fromY][fromX] = EmptySquare;
10234         board[toY][toX] = king;
10235     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10236                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10237                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10238                ) {
10239         /* white pawn promotion */
10240         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10241         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10242             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10243         board[fromY][fromX] = EmptySquare;
10244     } else if ((fromY >= BOARD_HEIGHT>>1)
10245                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10246                && (toX != fromX)
10247                && gameInfo.variant != VariantXiangqi
10248                && gameInfo.variant != VariantBerolina
10249                && (pawn == WhitePawn)
10250                && (board[toY][toX] == EmptySquare)) {
10251         board[fromY][fromX] = EmptySquare;
10252         board[toY][toX] = piece;
10253         if(toY == epRank - 128 + 1)
10254             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10255         else
10256             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10257     } else if ((fromY == BOARD_HEIGHT-4)
10258                && (toX == fromX)
10259                && gameInfo.variant == VariantBerolina
10260                && (board[fromY][fromX] == WhitePawn)
10261                && (board[toY][toX] == EmptySquare)) {
10262         board[fromY][fromX] = EmptySquare;
10263         board[toY][toX] = WhitePawn;
10264         if(oldEP & EP_BEROLIN_A) {
10265                 captured = board[fromY][fromX-1];
10266                 board[fromY][fromX-1] = EmptySquare;
10267         }else{  captured = board[fromY][fromX+1];
10268                 board[fromY][fromX+1] = EmptySquare;
10269         }
10270     } else if (board[fromY][fromX] == king
10271         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10272                && toY == fromY && toX > fromX+1) {
10273         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10274         board[fromY][toX-1] = board[fromY][rookX];
10275         board[fromY][rookX] = EmptySquare;
10276         board[fromY][fromX] = EmptySquare;
10277         board[toY][toX] = king;
10278     } else if (board[fromY][fromX] == king
10279         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10280                && toY == fromY && toX < fromX-1) {
10281         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10282         board[fromY][toX+1] = board[fromY][rookX];
10283         board[fromY][rookX] = EmptySquare;
10284         board[fromY][fromX] = EmptySquare;
10285         board[toY][toX] = king;
10286     } else if (fromY == 7 && fromX == 3
10287                && board[fromY][fromX] == BlackKing
10288                && toY == 7 && toX == 5) {
10289         board[fromY][fromX] = EmptySquare;
10290         board[toY][toX] = BlackKing;
10291         board[fromY][7] = EmptySquare;
10292         board[toY][4] = BlackRook;
10293     } else if (fromY == 7 && fromX == 3
10294                && board[fromY][fromX] == BlackKing
10295                && toY == 7 && toX == 1) {
10296         board[fromY][fromX] = EmptySquare;
10297         board[toY][toX] = BlackKing;
10298         board[fromY][0] = EmptySquare;
10299         board[toY][2] = BlackRook;
10300     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10301                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10302                && toY < promoRank && promoChar
10303                ) {
10304         /* black pawn promotion */
10305         board[toY][toX] = CharToPiece(ToLower(promoChar));
10306         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10307             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10308         board[fromY][fromX] = EmptySquare;
10309     } else if ((fromY < BOARD_HEIGHT>>1)
10310                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10311                && (toX != fromX)
10312                && gameInfo.variant != VariantXiangqi
10313                && gameInfo.variant != VariantBerolina
10314                && (pawn == BlackPawn)
10315                && (board[toY][toX] == EmptySquare)) {
10316         board[fromY][fromX] = EmptySquare;
10317         board[toY][toX] = piece;
10318         if(toY == epRank - 128 - 1)
10319             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10320         else
10321             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10322     } else if ((fromY == 3)
10323                && (toX == fromX)
10324                && gameInfo.variant == VariantBerolina
10325                && (board[fromY][fromX] == BlackPawn)
10326                && (board[toY][toX] == EmptySquare)) {
10327         board[fromY][fromX] = EmptySquare;
10328         board[toY][toX] = BlackPawn;
10329         if(oldEP & EP_BEROLIN_A) {
10330                 captured = board[fromY][fromX-1];
10331                 board[fromY][fromX-1] = EmptySquare;
10332         }else{  captured = board[fromY][fromX+1];
10333                 board[fromY][fromX+1] = EmptySquare;
10334         }
10335     } else {
10336         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10337         board[fromY][fromX] = EmptySquare;
10338         board[toY][toX] = piece;
10339     }
10340   }
10341
10342     if (gameInfo.holdingsWidth != 0) {
10343
10344       /* !!A lot more code needs to be written to support holdings  */
10345       /* [HGM] OK, so I have written it. Holdings are stored in the */
10346       /* penultimate board files, so they are automaticlly stored   */
10347       /* in the game history.                                       */
10348       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10349                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10350         /* Delete from holdings, by decreasing count */
10351         /* and erasing image if necessary            */
10352         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10353         if(p < (int) BlackPawn) { /* white drop */
10354              p -= (int)WhitePawn;
10355                  p = PieceToNumber((ChessSquare)p);
10356              if(p >= gameInfo.holdingsSize) p = 0;
10357              if(--board[p][BOARD_WIDTH-2] <= 0)
10358                   board[p][BOARD_WIDTH-1] = EmptySquare;
10359              if((int)board[p][BOARD_WIDTH-2] < 0)
10360                         board[p][BOARD_WIDTH-2] = 0;
10361         } else {                  /* black drop */
10362              p -= (int)BlackPawn;
10363                  p = PieceToNumber((ChessSquare)p);
10364              if(p >= gameInfo.holdingsSize) p = 0;
10365              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10366                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10367              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10368                         board[BOARD_HEIGHT-1-p][1] = 0;
10369         }
10370       }
10371       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10372           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10373         /* [HGM] holdings: Add to holdings, if holdings exist */
10374         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10375                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10376                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10377         }
10378         p = (int) captured;
10379         if (p >= (int) BlackPawn) {
10380           p -= (int)BlackPawn;
10381           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10382                   /* Restore shogi-promoted piece to its original  first */
10383                   captured = (ChessSquare) (DEMOTED(captured));
10384                   p = DEMOTED(p);
10385           }
10386           p = PieceToNumber((ChessSquare)p);
10387           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10388           board[p][BOARD_WIDTH-2]++;
10389           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10390         } else {
10391           p -= (int)WhitePawn;
10392           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10393                   captured = (ChessSquare) (DEMOTED(captured));
10394                   p = DEMOTED(p);
10395           }
10396           p = PieceToNumber((ChessSquare)p);
10397           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10398           board[BOARD_HEIGHT-1-p][1]++;
10399           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10400         }
10401       }
10402     } else if (gameInfo.variant == VariantAtomic) {
10403       if (captured != EmptySquare) {
10404         int y, x;
10405         for (y = toY-1; y <= toY+1; y++) {
10406           for (x = toX-1; x <= toX+1; x++) {
10407             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10408                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10409               board[y][x] = EmptySquare;
10410             }
10411           }
10412         }
10413         board[toY][toX] = EmptySquare;
10414       }
10415     }
10416
10417     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10418         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10419     } else
10420     if(promoChar == '+') {
10421         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10422         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10423         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10424           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10425     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10426         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10427         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10428            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10429         board[toY][toX] = newPiece;
10430     }
10431     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10432                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10433         // [HGM] superchess: take promotion piece out of holdings
10434         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10435         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10436             if(!--board[k][BOARD_WIDTH-2])
10437                 board[k][BOARD_WIDTH-1] = EmptySquare;
10438         } else {
10439             if(!--board[BOARD_HEIGHT-1-k][1])
10440                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10441         }
10442     }
10443 }
10444
10445 /* Updates forwardMostMove */
10446 void
10447 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10448 {
10449     int x = toX, y = toY;
10450     char *s = parseList[forwardMostMove];
10451     ChessSquare p = boards[forwardMostMove][toY][toX];
10452 //    forwardMostMove++; // [HGM] bare: moved downstream
10453
10454     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10455     (void) CoordsToAlgebraic(boards[forwardMostMove],
10456                              PosFlags(forwardMostMove),
10457                              fromY, fromX, y, x, promoChar,
10458                              s);
10459     if(killX >= 0 && killY >= 0)
10460         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10461
10462     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10463         int timeLeft; static int lastLoadFlag=0; int king, piece;
10464         piece = boards[forwardMostMove][fromY][fromX];
10465         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10466         if(gameInfo.variant == VariantKnightmate)
10467             king += (int) WhiteUnicorn - (int) WhiteKing;
10468         if(forwardMostMove == 0) {
10469             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10470                 fprintf(serverMoves, "%s;", UserName());
10471             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10472                 fprintf(serverMoves, "%s;", second.tidy);
10473             fprintf(serverMoves, "%s;", first.tidy);
10474             if(gameMode == MachinePlaysWhite)
10475                 fprintf(serverMoves, "%s;", UserName());
10476             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10477                 fprintf(serverMoves, "%s;", second.tidy);
10478         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10479         lastLoadFlag = loadFlag;
10480         // print base move
10481         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10482         // print castling suffix
10483         if( toY == fromY && piece == king ) {
10484             if(toX-fromX > 1)
10485                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10486             if(fromX-toX >1)
10487                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10488         }
10489         // e.p. suffix
10490         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10491              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10492              boards[forwardMostMove][toY][toX] == EmptySquare
10493              && fromX != toX && fromY != toY)
10494                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10495         // promotion suffix
10496         if(promoChar != NULLCHAR) {
10497             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10498                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10499                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10500             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10501         }
10502         if(!loadFlag) {
10503                 char buf[MOVE_LEN*2], *p; int len;
10504             fprintf(serverMoves, "/%d/%d",
10505                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10506             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10507             else                      timeLeft = blackTimeRemaining/1000;
10508             fprintf(serverMoves, "/%d", timeLeft);
10509                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10510                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10511                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10512                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10513             fprintf(serverMoves, "/%s", buf);
10514         }
10515         fflush(serverMoves);
10516     }
10517
10518     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10519         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10520       return;
10521     }
10522     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10523     if (commentList[forwardMostMove+1] != NULL) {
10524         free(commentList[forwardMostMove+1]);
10525         commentList[forwardMostMove+1] = NULL;
10526     }
10527     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10528     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10529     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10530     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10531     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10532     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10533     adjustedClock = FALSE;
10534     gameInfo.result = GameUnfinished;
10535     if (gameInfo.resultDetails != NULL) {
10536         free(gameInfo.resultDetails);
10537         gameInfo.resultDetails = NULL;
10538     }
10539     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10540                               moveList[forwardMostMove - 1]);
10541     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10542       case MT_NONE:
10543       case MT_STALEMATE:
10544       default:
10545         break;
10546       case MT_CHECK:
10547         if(!IS_SHOGI(gameInfo.variant))
10548             strcat(parseList[forwardMostMove - 1], "+");
10549         break;
10550       case MT_CHECKMATE:
10551       case MT_STAINMATE:
10552         strcat(parseList[forwardMostMove - 1], "#");
10553         break;
10554     }
10555 }
10556
10557 /* Updates currentMove if not pausing */
10558 void
10559 ShowMove (int fromX, int fromY, int toX, int toY)
10560 {
10561     int instant = (gameMode == PlayFromGameFile) ?
10562         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10563     if(appData.noGUI) return;
10564     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10565         if (!instant) {
10566             if (forwardMostMove == currentMove + 1) {
10567                 AnimateMove(boards[forwardMostMove - 1],
10568                             fromX, fromY, toX, toY);
10569             }
10570         }
10571         currentMove = forwardMostMove;
10572     }
10573
10574     killX = killY = -1; // [HGM] lion: used up
10575
10576     if (instant) return;
10577
10578     DisplayMove(currentMove - 1);
10579     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10580             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10581                 SetHighlights(fromX, fromY, toX, toY);
10582             }
10583     }
10584     DrawPosition(FALSE, boards[currentMove]);
10585     DisplayBothClocks();
10586     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10587 }
10588
10589 void
10590 SendEgtPath (ChessProgramState *cps)
10591 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10592         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10593
10594         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10595
10596         while(*p) {
10597             char c, *q = name+1, *r, *s;
10598
10599             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10600             while(*p && *p != ',') *q++ = *p++;
10601             *q++ = ':'; *q = 0;
10602             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10603                 strcmp(name, ",nalimov:") == 0 ) {
10604                 // take nalimov path from the menu-changeable option first, if it is defined
10605               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10606                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10607             } else
10608             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10609                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10610                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10611                 s = r = StrStr(s, ":") + 1; // beginning of path info
10612                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10613                 c = *r; *r = 0;             // temporarily null-terminate path info
10614                     *--q = 0;               // strip of trailig ':' from name
10615                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10616                 *r = c;
10617                 SendToProgram(buf,cps);     // send egtbpath command for this format
10618             }
10619             if(*p == ',') p++; // read away comma to position for next format name
10620         }
10621 }
10622
10623 static int
10624 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10625 {
10626       int width = 8, height = 8, holdings = 0;             // most common sizes
10627       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10628       // correct the deviations default for each variant
10629       if( v == VariantXiangqi ) width = 9,  height = 10;
10630       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10631       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10632       if( v == VariantCapablanca || v == VariantCapaRandom ||
10633           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10634                                 width = 10;
10635       if( v == VariantCourier ) width = 12;
10636       if( v == VariantSuper )                            holdings = 8;
10637       if( v == VariantGreat )   width = 10,              holdings = 8;
10638       if( v == VariantSChess )                           holdings = 7;
10639       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10640       if( v == VariantChuChess) width = 10, height = 10;
10641       if( v == VariantChu )     width = 12, height = 12;
10642       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10643              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10644              holdingsSize >= 0 && holdingsSize != holdings;
10645 }
10646
10647 char variantError[MSG_SIZ];
10648
10649 char *
10650 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10651 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10652       char *p, *variant = VariantName(v);
10653       static char b[MSG_SIZ];
10654       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10655            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10656                                                holdingsSize, variant); // cook up sized variant name
10657            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10658            if(StrStr(list, b) == NULL) {
10659                // specific sized variant not known, check if general sizing allowed
10660                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10661                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10662                             boardWidth, boardHeight, holdingsSize, engine);
10663                    return NULL;
10664                }
10665                /* [HGM] here we really should compare with the maximum supported board size */
10666            }
10667       } else snprintf(b, MSG_SIZ,"%s", variant);
10668       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10669       p = StrStr(list, b);
10670       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10671       if(p == NULL) {
10672           // occurs not at all in list, or only as sub-string
10673           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10674           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10675               int l = strlen(variantError);
10676               char *q;
10677               while(p != list && p[-1] != ',') p--;
10678               q = strchr(p, ',');
10679               if(q) *q = NULLCHAR;
10680               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10681               if(q) *q= ',';
10682           }
10683           return NULL;
10684       }
10685       return b;
10686 }
10687
10688 void
10689 InitChessProgram (ChessProgramState *cps, int setup)
10690 /* setup needed to setup FRC opening position */
10691 {
10692     char buf[MSG_SIZ], *b;
10693     if (appData.noChessProgram) return;
10694     hintRequested = FALSE;
10695     bookRequested = FALSE;
10696
10697     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10698     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10699     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10700     if(cps->memSize) { /* [HGM] memory */
10701       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10702         SendToProgram(buf, cps);
10703     }
10704     SendEgtPath(cps); /* [HGM] EGT */
10705     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10706       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10707         SendToProgram(buf, cps);
10708     }
10709
10710     setboardSpoiledMachineBlack = FALSE;
10711     SendToProgram(cps->initString, cps);
10712     if (gameInfo.variant != VariantNormal &&
10713         gameInfo.variant != VariantLoadable
10714         /* [HGM] also send variant if board size non-standard */
10715         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10716
10717       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10718                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10719       if (b == NULL) {
10720         VariantClass v;
10721         char c, *q = cps->variants, *p = strchr(q, ',');
10722         if(p) *p = NULLCHAR;
10723         v = StringToVariant(q);
10724         DisplayError(variantError, 0);
10725         if(v != VariantUnknown && cps == &first) {
10726             int w, h, s;
10727             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10728                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10729             ASSIGN(appData.variant, q);
10730             Reset(TRUE, FALSE);
10731         }
10732         if(p) *p = ',';
10733         return;
10734       }
10735
10736       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10737       SendToProgram(buf, cps);
10738     }
10739     currentlyInitializedVariant = gameInfo.variant;
10740
10741     /* [HGM] send opening position in FRC to first engine */
10742     if(setup) {
10743           SendToProgram("force\n", cps);
10744           SendBoard(cps, 0);
10745           /* engine is now in force mode! Set flag to wake it up after first move. */
10746           setboardSpoiledMachineBlack = 1;
10747     }
10748
10749     if (cps->sendICS) {
10750       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10751       SendToProgram(buf, cps);
10752     }
10753     cps->maybeThinking = FALSE;
10754     cps->offeredDraw = 0;
10755     if (!appData.icsActive) {
10756         SendTimeControl(cps, movesPerSession, timeControl,
10757                         timeIncrement, appData.searchDepth,
10758                         searchTime);
10759     }
10760     if (appData.showThinking
10761         // [HGM] thinking: four options require thinking output to be sent
10762         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10763                                 ) {
10764         SendToProgram("post\n", cps);
10765     }
10766     SendToProgram("hard\n", cps);
10767     if (!appData.ponderNextMove) {
10768         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10769            it without being sure what state we are in first.  "hard"
10770            is not a toggle, so that one is OK.
10771          */
10772         SendToProgram("easy\n", cps);
10773     }
10774     if (cps->usePing) {
10775       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10776       SendToProgram(buf, cps);
10777     }
10778     cps->initDone = TRUE;
10779     ClearEngineOutputPane(cps == &second);
10780 }
10781
10782
10783 void
10784 ResendOptions (ChessProgramState *cps)
10785 { // send the stored value of the options
10786   int i;
10787   char buf[MSG_SIZ];
10788   Option *opt = cps->option;
10789   for(i=0; i<cps->nrOptions; i++, opt++) {
10790       switch(opt->type) {
10791         case Spin:
10792         case Slider:
10793         case CheckBox:
10794             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10795           break;
10796         case ComboBox:
10797           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10798           break;
10799         default:
10800             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10801           break;
10802         case Button:
10803         case SaveButton:
10804           continue;
10805       }
10806       SendToProgram(buf, cps);
10807   }
10808 }
10809
10810 void
10811 StartChessProgram (ChessProgramState *cps)
10812 {
10813     char buf[MSG_SIZ];
10814     int err;
10815
10816     if (appData.noChessProgram) return;
10817     cps->initDone = FALSE;
10818
10819     if (strcmp(cps->host, "localhost") == 0) {
10820         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10821     } else if (*appData.remoteShell == NULLCHAR) {
10822         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10823     } else {
10824         if (*appData.remoteUser == NULLCHAR) {
10825           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10826                     cps->program);
10827         } else {
10828           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10829                     cps->host, appData.remoteUser, cps->program);
10830         }
10831         err = StartChildProcess(buf, "", &cps->pr);
10832     }
10833
10834     if (err != 0) {
10835       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10836         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10837         if(cps != &first) return;
10838         appData.noChessProgram = TRUE;
10839         ThawUI();
10840         SetNCPMode();
10841 //      DisplayFatalError(buf, err, 1);
10842 //      cps->pr = NoProc;
10843 //      cps->isr = NULL;
10844         return;
10845     }
10846
10847     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10848     if (cps->protocolVersion > 1) {
10849       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10850       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10851         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10852         cps->comboCnt = 0;  //                and values of combo boxes
10853       }
10854       SendToProgram(buf, cps);
10855       if(cps->reload) ResendOptions(cps);
10856     } else {
10857       SendToProgram("xboard\n", cps);
10858     }
10859 }
10860
10861 void
10862 TwoMachinesEventIfReady P((void))
10863 {
10864   static int curMess = 0;
10865   if (first.lastPing != first.lastPong) {
10866     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10867     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10868     return;
10869   }
10870   if (second.lastPing != second.lastPong) {
10871     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10872     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10873     return;
10874   }
10875   DisplayMessage("", ""); curMess = 0;
10876   TwoMachinesEvent();
10877 }
10878
10879 char *
10880 MakeName (char *template)
10881 {
10882     time_t clock;
10883     struct tm *tm;
10884     static char buf[MSG_SIZ];
10885     char *p = buf;
10886     int i;
10887
10888     clock = time((time_t *)NULL);
10889     tm = localtime(&clock);
10890
10891     while(*p++ = *template++) if(p[-1] == '%') {
10892         switch(*template++) {
10893           case 0:   *p = 0; return buf;
10894           case 'Y': i = tm->tm_year+1900; break;
10895           case 'y': i = tm->tm_year-100; break;
10896           case 'M': i = tm->tm_mon+1; break;
10897           case 'd': i = tm->tm_mday; break;
10898           case 'h': i = tm->tm_hour; break;
10899           case 'm': i = tm->tm_min; break;
10900           case 's': i = tm->tm_sec; break;
10901           default:  i = 0;
10902         }
10903         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10904     }
10905     return buf;
10906 }
10907
10908 int
10909 CountPlayers (char *p)
10910 {
10911     int n = 0;
10912     while(p = strchr(p, '\n')) p++, n++; // count participants
10913     return n;
10914 }
10915
10916 FILE *
10917 WriteTourneyFile (char *results, FILE *f)
10918 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10919     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10920     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10921         // create a file with tournament description
10922         fprintf(f, "-participants {%s}\n", appData.participants);
10923         fprintf(f, "-seedBase %d\n", appData.seedBase);
10924         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10925         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10926         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10927         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10928         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10929         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10930         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10931         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10932         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10933         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10934         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10935         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10936         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10937         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10938         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10939         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10940         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10941         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10942         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10943         fprintf(f, "-smpCores %d\n", appData.smpCores);
10944         if(searchTime > 0)
10945                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10946         else {
10947                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10948                 fprintf(f, "-tc %s\n", appData.timeControl);
10949                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10950         }
10951         fprintf(f, "-results \"%s\"\n", results);
10952     }
10953     return f;
10954 }
10955
10956 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10957
10958 void
10959 Substitute (char *participants, int expunge)
10960 {
10961     int i, changed, changes=0, nPlayers=0;
10962     char *p, *q, *r, buf[MSG_SIZ];
10963     if(participants == NULL) return;
10964     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10965     r = p = participants; q = appData.participants;
10966     while(*p && *p == *q) {
10967         if(*p == '\n') r = p+1, nPlayers++;
10968         p++; q++;
10969     }
10970     if(*p) { // difference
10971         while(*p && *p++ != '\n');
10972         while(*q && *q++ != '\n');
10973       changed = nPlayers;
10974         changes = 1 + (strcmp(p, q) != 0);
10975     }
10976     if(changes == 1) { // a single engine mnemonic was changed
10977         q = r; while(*q) nPlayers += (*q++ == '\n');
10978         p = buf; while(*r && (*p = *r++) != '\n') p++;
10979         *p = NULLCHAR;
10980         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10981         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10982         if(mnemonic[i]) { // The substitute is valid
10983             FILE *f;
10984             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10985                 flock(fileno(f), LOCK_EX);
10986                 ParseArgsFromFile(f);
10987                 fseek(f, 0, SEEK_SET);
10988                 FREE(appData.participants); appData.participants = participants;
10989                 if(expunge) { // erase results of replaced engine
10990                     int len = strlen(appData.results), w, b, dummy;
10991                     for(i=0; i<len; i++) {
10992                         Pairing(i, nPlayers, &w, &b, &dummy);
10993                         if((w == changed || b == changed) && appData.results[i] == '*') {
10994                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10995                             fclose(f);
10996                             return;
10997                         }
10998                     }
10999                     for(i=0; i<len; i++) {
11000                         Pairing(i, nPlayers, &w, &b, &dummy);
11001                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11002                     }
11003                 }
11004                 WriteTourneyFile(appData.results, f);
11005                 fclose(f); // release lock
11006                 return;
11007             }
11008         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11009     }
11010     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11011     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11012     free(participants);
11013     return;
11014 }
11015
11016 int
11017 CheckPlayers (char *participants)
11018 {
11019         int i;
11020         char buf[MSG_SIZ], *p;
11021         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11022         while(p = strchr(participants, '\n')) {
11023             *p = NULLCHAR;
11024             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11025             if(!mnemonic[i]) {
11026                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11027                 *p = '\n';
11028                 DisplayError(buf, 0);
11029                 return 1;
11030             }
11031             *p = '\n';
11032             participants = p + 1;
11033         }
11034         return 0;
11035 }
11036
11037 int
11038 CreateTourney (char *name)
11039 {
11040         FILE *f;
11041         if(matchMode && strcmp(name, appData.tourneyFile)) {
11042              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11043         }
11044         if(name[0] == NULLCHAR) {
11045             if(appData.participants[0])
11046                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11047             return 0;
11048         }
11049         f = fopen(name, "r");
11050         if(f) { // file exists
11051             ASSIGN(appData.tourneyFile, name);
11052             ParseArgsFromFile(f); // parse it
11053         } else {
11054             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11055             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11056                 DisplayError(_("Not enough participants"), 0);
11057                 return 0;
11058             }
11059             if(CheckPlayers(appData.participants)) return 0;
11060             ASSIGN(appData.tourneyFile, name);
11061             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11062             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11063         }
11064         fclose(f);
11065         appData.noChessProgram = FALSE;
11066         appData.clockMode = TRUE;
11067         SetGNUMode();
11068         return 1;
11069 }
11070
11071 int
11072 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11073 {
11074     char buf[MSG_SIZ], *p, *q;
11075     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11076     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11077     skip = !all && group[0]; // if group requested, we start in skip mode
11078     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11079         p = names; q = buf; header = 0;
11080         while(*p && *p != '\n') *q++ = *p++;
11081         *q = 0;
11082         if(*p == '\n') p++;
11083         if(buf[0] == '#') {
11084             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11085             depth++; // we must be entering a new group
11086             if(all) continue; // suppress printing group headers when complete list requested
11087             header = 1;
11088             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11089         }
11090         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11091         if(engineList[i]) free(engineList[i]);
11092         engineList[i] = strdup(buf);
11093         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11094         if(engineMnemonic[i]) free(engineMnemonic[i]);
11095         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11096             strcat(buf, " (");
11097             sscanf(q + 8, "%s", buf + strlen(buf));
11098             strcat(buf, ")");
11099         }
11100         engineMnemonic[i] = strdup(buf);
11101         i++;
11102     }
11103     engineList[i] = engineMnemonic[i] = NULL;
11104     return i;
11105 }
11106
11107 // following implemented as macro to avoid type limitations
11108 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11109
11110 void
11111 SwapEngines (int n)
11112 {   // swap settings for first engine and other engine (so far only some selected options)
11113     int h;
11114     char *p;
11115     if(n == 0) return;
11116     SWAP(directory, p)
11117     SWAP(chessProgram, p)
11118     SWAP(isUCI, h)
11119     SWAP(hasOwnBookUCI, h)
11120     SWAP(protocolVersion, h)
11121     SWAP(reuse, h)
11122     SWAP(scoreIsAbsolute, h)
11123     SWAP(timeOdds, h)
11124     SWAP(logo, p)
11125     SWAP(pgnName, p)
11126     SWAP(pvSAN, h)
11127     SWAP(engOptions, p)
11128     SWAP(engInitString, p)
11129     SWAP(computerString, p)
11130     SWAP(features, p)
11131     SWAP(fenOverride, p)
11132     SWAP(NPS, h)
11133     SWAP(accumulateTC, h)
11134     SWAP(drawDepth, h)
11135     SWAP(host, p)
11136     SWAP(pseudo, h)
11137 }
11138
11139 int
11140 GetEngineLine (char *s, int n)
11141 {
11142     int i;
11143     char buf[MSG_SIZ];
11144     extern char *icsNames;
11145     if(!s || !*s) return 0;
11146     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11147     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11148     if(!mnemonic[i]) return 0;
11149     if(n == 11) return 1; // just testing if there was a match
11150     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11151     if(n == 1) SwapEngines(n);
11152     ParseArgsFromString(buf);
11153     if(n == 1) SwapEngines(n);
11154     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11155         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11156         ParseArgsFromString(buf);
11157     }
11158     return 1;
11159 }
11160
11161 int
11162 SetPlayer (int player, char *p)
11163 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11164     int i;
11165     char buf[MSG_SIZ], *engineName;
11166     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11167     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11168     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11169     if(mnemonic[i]) {
11170         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11171         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11172         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11173         ParseArgsFromString(buf);
11174     } else { // no engine with this nickname is installed!
11175         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11176         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11177         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11178         ModeHighlight();
11179         DisplayError(buf, 0);
11180         return 0;
11181     }
11182     free(engineName);
11183     return i;
11184 }
11185
11186 char *recentEngines;
11187
11188 void
11189 RecentEngineEvent (int nr)
11190 {
11191     int n;
11192 //    SwapEngines(1); // bump first to second
11193 //    ReplaceEngine(&second, 1); // and load it there
11194     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11195     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11196     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11197         ReplaceEngine(&first, 0);
11198         FloatToFront(&appData.recentEngineList, command[n]);
11199     }
11200 }
11201
11202 int
11203 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11204 {   // determine players from game number
11205     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11206
11207     if(appData.tourneyType == 0) {
11208         roundsPerCycle = (nPlayers - 1) | 1;
11209         pairingsPerRound = nPlayers / 2;
11210     } else if(appData.tourneyType > 0) {
11211         roundsPerCycle = nPlayers - appData.tourneyType;
11212         pairingsPerRound = appData.tourneyType;
11213     }
11214     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11215     gamesPerCycle = gamesPerRound * roundsPerCycle;
11216     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11217     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11218     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11219     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11220     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11221     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11222
11223     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11224     if(appData.roundSync) *syncInterval = gamesPerRound;
11225
11226     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11227
11228     if(appData.tourneyType == 0) {
11229         if(curPairing == (nPlayers-1)/2 ) {
11230             *whitePlayer = curRound;
11231             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11232         } else {
11233             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11234             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11235             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11236             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11237         }
11238     } else if(appData.tourneyType > 1) {
11239         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11240         *whitePlayer = curRound + appData.tourneyType;
11241     } else if(appData.tourneyType > 0) {
11242         *whitePlayer = curPairing;
11243         *blackPlayer = curRound + appData.tourneyType;
11244     }
11245
11246     // take care of white/black alternation per round.
11247     // For cycles and games this is already taken care of by default, derived from matchGame!
11248     return curRound & 1;
11249 }
11250
11251 int
11252 NextTourneyGame (int nr, int *swapColors)
11253 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11254     char *p, *q;
11255     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11256     FILE *tf;
11257     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11258     tf = fopen(appData.tourneyFile, "r");
11259     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11260     ParseArgsFromFile(tf); fclose(tf);
11261     InitTimeControls(); // TC might be altered from tourney file
11262
11263     nPlayers = CountPlayers(appData.participants); // count participants
11264     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11265     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11266
11267     if(syncInterval) {
11268         p = q = appData.results;
11269         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11270         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11271             DisplayMessage(_("Waiting for other game(s)"),"");
11272             waitingForGame = TRUE;
11273             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11274             return 0;
11275         }
11276         waitingForGame = FALSE;
11277     }
11278
11279     if(appData.tourneyType < 0) {
11280         if(nr>=0 && !pairingReceived) {
11281             char buf[1<<16];
11282             if(pairing.pr == NoProc) {
11283                 if(!appData.pairingEngine[0]) {
11284                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11285                     return 0;
11286                 }
11287                 StartChessProgram(&pairing); // starts the pairing engine
11288             }
11289             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11290             SendToProgram(buf, &pairing);
11291             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11292             SendToProgram(buf, &pairing);
11293             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11294         }
11295         pairingReceived = 0;                              // ... so we continue here
11296         *swapColors = 0;
11297         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11298         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11299         matchGame = 1; roundNr = nr / syncInterval + 1;
11300     }
11301
11302     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11303
11304     // redefine engines, engine dir, etc.
11305     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11306     if(first.pr == NoProc) {
11307       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11308       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11309     }
11310     if(second.pr == NoProc) {
11311       SwapEngines(1);
11312       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11313       SwapEngines(1);         // and make that valid for second engine by swapping
11314       InitEngine(&second, 1);
11315     }
11316     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11317     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11318     return OK;
11319 }
11320
11321 void
11322 NextMatchGame ()
11323 {   // performs game initialization that does not invoke engines, and then tries to start the game
11324     int res, firstWhite, swapColors = 0;
11325     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11326     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
11327         char buf[MSG_SIZ];
11328         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11329         if(strcmp(buf, currentDebugFile)) { // name has changed
11330             FILE *f = fopen(buf, "w");
11331             if(f) { // if opening the new file failed, just keep using the old one
11332                 ASSIGN(currentDebugFile, buf);
11333                 fclose(debugFP);
11334                 debugFP = f;
11335             }
11336             if(appData.serverFileName) {
11337                 if(serverFP) fclose(serverFP);
11338                 serverFP = fopen(appData.serverFileName, "w");
11339                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11340                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11341             }
11342         }
11343     }
11344     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11345     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11346     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11347     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11348     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11349     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11350     Reset(FALSE, first.pr != NoProc);
11351     res = LoadGameOrPosition(matchGame); // setup game
11352     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11353     if(!res) return; // abort when bad game/pos file
11354     TwoMachinesEvent();
11355 }
11356
11357 void
11358 UserAdjudicationEvent (int result)
11359 {
11360     ChessMove gameResult = GameIsDrawn;
11361
11362     if( result > 0 ) {
11363         gameResult = WhiteWins;
11364     }
11365     else if( result < 0 ) {
11366         gameResult = BlackWins;
11367     }
11368
11369     if( gameMode == TwoMachinesPlay ) {
11370         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11371     }
11372 }
11373
11374
11375 // [HGM] save: calculate checksum of game to make games easily identifiable
11376 int
11377 StringCheckSum (char *s)
11378 {
11379         int i = 0;
11380         if(s==NULL) return 0;
11381         while(*s) i = i*259 + *s++;
11382         return i;
11383 }
11384
11385 int
11386 GameCheckSum ()
11387 {
11388         int i, sum=0;
11389         for(i=backwardMostMove; i<forwardMostMove; i++) {
11390                 sum += pvInfoList[i].depth;
11391                 sum += StringCheckSum(parseList[i]);
11392                 sum += StringCheckSum(commentList[i]);
11393                 sum *= 261;
11394         }
11395         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11396         return sum + StringCheckSum(commentList[i]);
11397 } // end of save patch
11398
11399 void
11400 GameEnds (ChessMove result, char *resultDetails, int whosays)
11401 {
11402     GameMode nextGameMode;
11403     int isIcsGame;
11404     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11405
11406     if(endingGame) return; /* [HGM] crash: forbid recursion */
11407     endingGame = 1;
11408     if(twoBoards) { // [HGM] dual: switch back to one board
11409         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11410         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11411     }
11412     if (appData.debugMode) {
11413       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11414               result, resultDetails ? resultDetails : "(null)", whosays);
11415     }
11416
11417     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11418
11419     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11420
11421     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11422         /* If we are playing on ICS, the server decides when the
11423            game is over, but the engine can offer to draw, claim
11424            a draw, or resign.
11425          */
11426 #if ZIPPY
11427         if (appData.zippyPlay && first.initDone) {
11428             if (result == GameIsDrawn) {
11429                 /* In case draw still needs to be claimed */
11430                 SendToICS(ics_prefix);
11431                 SendToICS("draw\n");
11432             } else if (StrCaseStr(resultDetails, "resign")) {
11433                 SendToICS(ics_prefix);
11434                 SendToICS("resign\n");
11435             }
11436         }
11437 #endif
11438         endingGame = 0; /* [HGM] crash */
11439         return;
11440     }
11441
11442     /* If we're loading the game from a file, stop */
11443     if (whosays == GE_FILE) {
11444       (void) StopLoadGameTimer();
11445       gameFileFP = NULL;
11446     }
11447
11448     /* Cancel draw offers */
11449     first.offeredDraw = second.offeredDraw = 0;
11450
11451     /* If this is an ICS game, only ICS can really say it's done;
11452        if not, anyone can. */
11453     isIcsGame = (gameMode == IcsPlayingWhite ||
11454                  gameMode == IcsPlayingBlack ||
11455                  gameMode == IcsObserving    ||
11456                  gameMode == IcsExamining);
11457
11458     if (!isIcsGame || whosays == GE_ICS) {
11459         /* OK -- not an ICS game, or ICS said it was done */
11460         StopClocks();
11461         if (!isIcsGame && !appData.noChessProgram)
11462           SetUserThinkingEnables();
11463
11464         /* [HGM] if a machine claims the game end we verify this claim */
11465         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11466             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11467                 char claimer;
11468                 ChessMove trueResult = (ChessMove) -1;
11469
11470                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11471                                             first.twoMachinesColor[0] :
11472                                             second.twoMachinesColor[0] ;
11473
11474                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11475                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11476                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11477                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11478                 } else
11479                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11480                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11481                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11482                 } else
11483                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11484                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11485                 }
11486
11487                 // now verify win claims, but not in drop games, as we don't understand those yet
11488                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11489                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11490                     (result == WhiteWins && claimer == 'w' ||
11491                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11492                       if (appData.debugMode) {
11493                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11494                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11495                       }
11496                       if(result != trueResult) {
11497                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11498                               result = claimer == 'w' ? BlackWins : WhiteWins;
11499                               resultDetails = buf;
11500                       }
11501                 } else
11502                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11503                     && (forwardMostMove <= backwardMostMove ||
11504                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11505                         (claimer=='b')==(forwardMostMove&1))
11506                                                                                   ) {
11507                       /* [HGM] verify: draws that were not flagged are false claims */
11508                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11509                       result = claimer == 'w' ? BlackWins : WhiteWins;
11510                       resultDetails = buf;
11511                 }
11512                 /* (Claiming a loss is accepted no questions asked!) */
11513             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11514                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11515                 result = GameUnfinished;
11516                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11517             }
11518             /* [HGM] bare: don't allow bare King to win */
11519             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11520                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11521                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11522                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11523                && result != GameIsDrawn)
11524             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11525                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11526                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11527                         if(p >= 0 && p <= (int)WhiteKing) k++;
11528                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11529                 }
11530                 if (appData.debugMode) {
11531                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11532                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11533                 }
11534                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11535                         result = GameIsDrawn;
11536                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11537                         resultDetails = buf;
11538                 }
11539             }
11540         }
11541
11542
11543         if(serverMoves != NULL && !loadFlag) { char c = '=';
11544             if(result==WhiteWins) c = '+';
11545             if(result==BlackWins) c = '-';
11546             if(resultDetails != NULL)
11547                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11548         }
11549         if (resultDetails != NULL) {
11550             gameInfo.result = result;
11551             gameInfo.resultDetails = StrSave(resultDetails);
11552
11553             /* display last move only if game was not loaded from file */
11554             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11555                 DisplayMove(currentMove - 1);
11556
11557             if (forwardMostMove != 0) {
11558                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11559                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11560                                                                 ) {
11561                     if (*appData.saveGameFile != NULLCHAR) {
11562                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11563                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11564                         else
11565                         SaveGameToFile(appData.saveGameFile, TRUE);
11566                     } else if (appData.autoSaveGames) {
11567                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11568                     }
11569                     if (*appData.savePositionFile != NULLCHAR) {
11570                         SavePositionToFile(appData.savePositionFile);
11571                     }
11572                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11573                 }
11574             }
11575
11576             /* Tell program how game ended in case it is learning */
11577             /* [HGM] Moved this to after saving the PGN, just in case */
11578             /* engine died and we got here through time loss. In that */
11579             /* case we will get a fatal error writing the pipe, which */
11580             /* would otherwise lose us the PGN.                       */
11581             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11582             /* output during GameEnds should never be fatal anymore   */
11583             if (gameMode == MachinePlaysWhite ||
11584                 gameMode == MachinePlaysBlack ||
11585                 gameMode == TwoMachinesPlay ||
11586                 gameMode == IcsPlayingWhite ||
11587                 gameMode == IcsPlayingBlack ||
11588                 gameMode == BeginningOfGame) {
11589                 char buf[MSG_SIZ];
11590                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11591                         resultDetails);
11592                 if (first.pr != NoProc) {
11593                     SendToProgram(buf, &first);
11594                 }
11595                 if (second.pr != NoProc &&
11596                     gameMode == TwoMachinesPlay) {
11597                     SendToProgram(buf, &second);
11598                 }
11599             }
11600         }
11601
11602         if (appData.icsActive) {
11603             if (appData.quietPlay &&
11604                 (gameMode == IcsPlayingWhite ||
11605                  gameMode == IcsPlayingBlack)) {
11606                 SendToICS(ics_prefix);
11607                 SendToICS("set shout 1\n");
11608             }
11609             nextGameMode = IcsIdle;
11610             ics_user_moved = FALSE;
11611             /* clean up premove.  It's ugly when the game has ended and the
11612              * premove highlights are still on the board.
11613              */
11614             if (gotPremove) {
11615               gotPremove = FALSE;
11616               ClearPremoveHighlights();
11617               DrawPosition(FALSE, boards[currentMove]);
11618             }
11619             if (whosays == GE_ICS) {
11620                 switch (result) {
11621                 case WhiteWins:
11622                     if (gameMode == IcsPlayingWhite)
11623                         PlayIcsWinSound();
11624                     else if(gameMode == IcsPlayingBlack)
11625                         PlayIcsLossSound();
11626                     break;
11627                 case BlackWins:
11628                     if (gameMode == IcsPlayingBlack)
11629                         PlayIcsWinSound();
11630                     else if(gameMode == IcsPlayingWhite)
11631                         PlayIcsLossSound();
11632                     break;
11633                 case GameIsDrawn:
11634                     PlayIcsDrawSound();
11635                     break;
11636                 default:
11637                     PlayIcsUnfinishedSound();
11638                 }
11639             }
11640             if(appData.quitNext) { ExitEvent(0); return; }
11641         } else if (gameMode == EditGame ||
11642                    gameMode == PlayFromGameFile ||
11643                    gameMode == AnalyzeMode ||
11644                    gameMode == AnalyzeFile) {
11645             nextGameMode = gameMode;
11646         } else {
11647             nextGameMode = EndOfGame;
11648         }
11649         pausing = FALSE;
11650         ModeHighlight();
11651     } else {
11652         nextGameMode = gameMode;
11653     }
11654
11655     if (appData.noChessProgram) {
11656         gameMode = nextGameMode;
11657         ModeHighlight();
11658         endingGame = 0; /* [HGM] crash */
11659         return;
11660     }
11661
11662     if (first.reuse) {
11663         /* Put first chess program into idle state */
11664         if (first.pr != NoProc &&
11665             (gameMode == MachinePlaysWhite ||
11666              gameMode == MachinePlaysBlack ||
11667              gameMode == TwoMachinesPlay ||
11668              gameMode == IcsPlayingWhite ||
11669              gameMode == IcsPlayingBlack ||
11670              gameMode == BeginningOfGame)) {
11671             SendToProgram("force\n", &first);
11672             if (first.usePing) {
11673               char buf[MSG_SIZ];
11674               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11675               SendToProgram(buf, &first);
11676             }
11677         }
11678     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11679         /* Kill off first chess program */
11680         if (first.isr != NULL)
11681           RemoveInputSource(first.isr);
11682         first.isr = NULL;
11683
11684         if (first.pr != NoProc) {
11685             ExitAnalyzeMode();
11686             DoSleep( appData.delayBeforeQuit );
11687             SendToProgram("quit\n", &first);
11688             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11689             first.reload = TRUE;
11690         }
11691         first.pr = NoProc;
11692     }
11693     if (second.reuse) {
11694         /* Put second chess program into idle state */
11695         if (second.pr != NoProc &&
11696             gameMode == TwoMachinesPlay) {
11697             SendToProgram("force\n", &second);
11698             if (second.usePing) {
11699               char buf[MSG_SIZ];
11700               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11701               SendToProgram(buf, &second);
11702             }
11703         }
11704     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11705         /* Kill off second chess program */
11706         if (second.isr != NULL)
11707           RemoveInputSource(second.isr);
11708         second.isr = NULL;
11709
11710         if (second.pr != NoProc) {
11711             DoSleep( appData.delayBeforeQuit );
11712             SendToProgram("quit\n", &second);
11713             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11714             second.reload = TRUE;
11715         }
11716         second.pr = NoProc;
11717     }
11718
11719     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11720         char resChar = '=';
11721         switch (result) {
11722         case WhiteWins:
11723           resChar = '+';
11724           if (first.twoMachinesColor[0] == 'w') {
11725             first.matchWins++;
11726           } else {
11727             second.matchWins++;
11728           }
11729           break;
11730         case BlackWins:
11731           resChar = '-';
11732           if (first.twoMachinesColor[0] == 'b') {
11733             first.matchWins++;
11734           } else {
11735             second.matchWins++;
11736           }
11737           break;
11738         case GameUnfinished:
11739           resChar = ' ';
11740         default:
11741           break;
11742         }
11743
11744         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11745         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11746             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11747             ReserveGame(nextGame, resChar); // sets nextGame
11748             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11749             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11750         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11751
11752         if (nextGame <= appData.matchGames && !abortMatch) {
11753             gameMode = nextGameMode;
11754             matchGame = nextGame; // this will be overruled in tourney mode!
11755             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11756             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11757             endingGame = 0; /* [HGM] crash */
11758             return;
11759         } else {
11760             gameMode = nextGameMode;
11761             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11762                      first.tidy, second.tidy,
11763                      first.matchWins, second.matchWins,
11764                      appData.matchGames - (first.matchWins + second.matchWins));
11765             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11766             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11767             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11768             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11769                 first.twoMachinesColor = "black\n";
11770                 second.twoMachinesColor = "white\n";
11771             } else {
11772                 first.twoMachinesColor = "white\n";
11773                 second.twoMachinesColor = "black\n";
11774             }
11775         }
11776     }
11777     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11778         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11779       ExitAnalyzeMode();
11780     gameMode = nextGameMode;
11781     ModeHighlight();
11782     endingGame = 0;  /* [HGM] crash */
11783     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11784         if(matchMode == TRUE) { // match through command line: exit with or without popup
11785             if(ranking) {
11786                 ToNrEvent(forwardMostMove);
11787                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11788                 else ExitEvent(0);
11789             } else DisplayFatalError(buf, 0, 0);
11790         } else { // match through menu; just stop, with or without popup
11791             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11792             ModeHighlight();
11793             if(ranking){
11794                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11795             } else DisplayNote(buf);
11796       }
11797       if(ranking) free(ranking);
11798     }
11799 }
11800
11801 /* Assumes program was just initialized (initString sent).
11802    Leaves program in force mode. */
11803 void
11804 FeedMovesToProgram (ChessProgramState *cps, int upto)
11805 {
11806     int i;
11807
11808     if (appData.debugMode)
11809       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11810               startedFromSetupPosition ? "position and " : "",
11811               backwardMostMove, upto, cps->which);
11812     if(currentlyInitializedVariant != gameInfo.variant) {
11813       char buf[MSG_SIZ];
11814         // [HGM] variantswitch: make engine aware of new variant
11815         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11816                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11817                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11818         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11819         SendToProgram(buf, cps);
11820         currentlyInitializedVariant = gameInfo.variant;
11821     }
11822     SendToProgram("force\n", cps);
11823     if (startedFromSetupPosition) {
11824         SendBoard(cps, backwardMostMove);
11825     if (appData.debugMode) {
11826         fprintf(debugFP, "feedMoves\n");
11827     }
11828     }
11829     for (i = backwardMostMove; i < upto; i++) {
11830         SendMoveToProgram(i, cps);
11831     }
11832 }
11833
11834
11835 int
11836 ResurrectChessProgram ()
11837 {
11838      /* The chess program may have exited.
11839         If so, restart it and feed it all the moves made so far. */
11840     static int doInit = 0;
11841
11842     if (appData.noChessProgram) return 1;
11843
11844     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11845         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11846         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11847         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11848     } else {
11849         if (first.pr != NoProc) return 1;
11850         StartChessProgram(&first);
11851     }
11852     InitChessProgram(&first, FALSE);
11853     FeedMovesToProgram(&first, currentMove);
11854
11855     if (!first.sendTime) {
11856         /* can't tell gnuchess what its clock should read,
11857            so we bow to its notion. */
11858         ResetClocks();
11859         timeRemaining[0][currentMove] = whiteTimeRemaining;
11860         timeRemaining[1][currentMove] = blackTimeRemaining;
11861     }
11862
11863     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11864                 appData.icsEngineAnalyze) && first.analysisSupport) {
11865       SendToProgram("analyze\n", &first);
11866       first.analyzing = TRUE;
11867     }
11868     return 1;
11869 }
11870
11871 /*
11872  * Button procedures
11873  */
11874 void
11875 Reset (int redraw, int init)
11876 {
11877     int i;
11878
11879     if (appData.debugMode) {
11880         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11881                 redraw, init, gameMode);
11882     }
11883     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11884     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11885     CleanupTail(); // [HGM] vari: delete any stored variations
11886     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11887     pausing = pauseExamInvalid = FALSE;
11888     startedFromSetupPosition = blackPlaysFirst = FALSE;
11889     firstMove = TRUE;
11890     whiteFlag = blackFlag = FALSE;
11891     userOfferedDraw = FALSE;
11892     hintRequested = bookRequested = FALSE;
11893     first.maybeThinking = FALSE;
11894     second.maybeThinking = FALSE;
11895     first.bookSuspend = FALSE; // [HGM] book
11896     second.bookSuspend = FALSE;
11897     thinkOutput[0] = NULLCHAR;
11898     lastHint[0] = NULLCHAR;
11899     ClearGameInfo(&gameInfo);
11900     gameInfo.variant = StringToVariant(appData.variant);
11901     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11902     ics_user_moved = ics_clock_paused = FALSE;
11903     ics_getting_history = H_FALSE;
11904     ics_gamenum = -1;
11905     white_holding[0] = black_holding[0] = NULLCHAR;
11906     ClearProgramStats();
11907     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11908
11909     ResetFrontEnd();
11910     ClearHighlights();
11911     flipView = appData.flipView;
11912     ClearPremoveHighlights();
11913     gotPremove = FALSE;
11914     alarmSounded = FALSE;
11915     killX = killY = -1; // [HGM] lion
11916
11917     GameEnds(EndOfFile, NULL, GE_PLAYER);
11918     if(appData.serverMovesName != NULL) {
11919         /* [HGM] prepare to make moves file for broadcasting */
11920         clock_t t = clock();
11921         if(serverMoves != NULL) fclose(serverMoves);
11922         serverMoves = fopen(appData.serverMovesName, "r");
11923         if(serverMoves != NULL) {
11924             fclose(serverMoves);
11925             /* delay 15 sec before overwriting, so all clients can see end */
11926             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11927         }
11928         serverMoves = fopen(appData.serverMovesName, "w");
11929     }
11930
11931     ExitAnalyzeMode();
11932     gameMode = BeginningOfGame;
11933     ModeHighlight();
11934     if(appData.icsActive) gameInfo.variant = VariantNormal;
11935     currentMove = forwardMostMove = backwardMostMove = 0;
11936     MarkTargetSquares(1);
11937     InitPosition(redraw);
11938     for (i = 0; i < MAX_MOVES; i++) {
11939         if (commentList[i] != NULL) {
11940             free(commentList[i]);
11941             commentList[i] = NULL;
11942         }
11943     }
11944     ResetClocks();
11945     timeRemaining[0][0] = whiteTimeRemaining;
11946     timeRemaining[1][0] = blackTimeRemaining;
11947
11948     if (first.pr == NoProc) {
11949         StartChessProgram(&first);
11950     }
11951     if (init) {
11952             InitChessProgram(&first, startedFromSetupPosition);
11953     }
11954     DisplayTitle("");
11955     DisplayMessage("", "");
11956     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11957     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11958     ClearMap();        // [HGM] exclude: invalidate map
11959 }
11960
11961 void
11962 AutoPlayGameLoop ()
11963 {
11964     for (;;) {
11965         if (!AutoPlayOneMove())
11966           return;
11967         if (matchMode || appData.timeDelay == 0)
11968           continue;
11969         if (appData.timeDelay < 0)
11970           return;
11971         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11972         break;
11973     }
11974 }
11975
11976 void
11977 AnalyzeNextGame()
11978 {
11979     ReloadGame(1); // next game
11980 }
11981
11982 int
11983 AutoPlayOneMove ()
11984 {
11985     int fromX, fromY, toX, toY;
11986
11987     if (appData.debugMode) {
11988       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11989     }
11990
11991     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11992       return FALSE;
11993
11994     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11995       pvInfoList[currentMove].depth = programStats.depth;
11996       pvInfoList[currentMove].score = programStats.score;
11997       pvInfoList[currentMove].time  = 0;
11998       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11999       else { // append analysis of final position as comment
12000         char buf[MSG_SIZ];
12001         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12002         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12003       }
12004       programStats.depth = 0;
12005     }
12006
12007     if (currentMove >= forwardMostMove) {
12008       if(gameMode == AnalyzeFile) {
12009           if(appData.loadGameIndex == -1) {
12010             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12011           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12012           } else {
12013           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12014         }
12015       }
12016 //      gameMode = EndOfGame;
12017 //      ModeHighlight();
12018
12019       /* [AS] Clear current move marker at the end of a game */
12020       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12021
12022       return FALSE;
12023     }
12024
12025     toX = moveList[currentMove][2] - AAA;
12026     toY = moveList[currentMove][3] - ONE;
12027
12028     if (moveList[currentMove][1] == '@') {
12029         if (appData.highlightLastMove) {
12030             SetHighlights(-1, -1, toX, toY);
12031         }
12032     } else {
12033         int viaX = moveList[currentMove][5] - AAA;
12034         int viaY = moveList[currentMove][6] - ONE;
12035         fromX = moveList[currentMove][0] - AAA;
12036         fromY = moveList[currentMove][1] - ONE;
12037
12038         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12039
12040         if(moveList[currentMove][4] == ';') { // multi-leg
12041             ChessSquare piece = boards[currentMove][viaY][viaX];
12042             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12043             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12044             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12045             boards[currentMove][viaY][viaX] = piece;
12046         } else
12047         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12048
12049         if (appData.highlightLastMove) {
12050             SetHighlights(fromX, fromY, toX, toY);
12051         }
12052     }
12053     DisplayMove(currentMove);
12054     SendMoveToProgram(currentMove++, &first);
12055     DisplayBothClocks();
12056     DrawPosition(FALSE, boards[currentMove]);
12057     // [HGM] PV info: always display, routine tests if empty
12058     DisplayComment(currentMove - 1, commentList[currentMove]);
12059     return TRUE;
12060 }
12061
12062
12063 int
12064 LoadGameOneMove (ChessMove readAhead)
12065 {
12066     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12067     char promoChar = NULLCHAR;
12068     ChessMove moveType;
12069     char move[MSG_SIZ];
12070     char *p, *q;
12071
12072     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12073         gameMode != AnalyzeMode && gameMode != Training) {
12074         gameFileFP = NULL;
12075         return FALSE;
12076     }
12077
12078     yyboardindex = forwardMostMove;
12079     if (readAhead != EndOfFile) {
12080       moveType = readAhead;
12081     } else {
12082       if (gameFileFP == NULL)
12083           return FALSE;
12084       moveType = (ChessMove) Myylex();
12085     }
12086
12087     done = FALSE;
12088     switch (moveType) {
12089       case Comment:
12090         if (appData.debugMode)
12091           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12092         p = yy_text;
12093
12094         /* append the comment but don't display it */
12095         AppendComment(currentMove, p, FALSE);
12096         return TRUE;
12097
12098       case WhiteCapturesEnPassant:
12099       case BlackCapturesEnPassant:
12100       case WhitePromotion:
12101       case BlackPromotion:
12102       case WhiteNonPromotion:
12103       case BlackNonPromotion:
12104       case NormalMove:
12105       case FirstLeg:
12106       case WhiteKingSideCastle:
12107       case WhiteQueenSideCastle:
12108       case BlackKingSideCastle:
12109       case BlackQueenSideCastle:
12110       case WhiteKingSideCastleWild:
12111       case WhiteQueenSideCastleWild:
12112       case BlackKingSideCastleWild:
12113       case BlackQueenSideCastleWild:
12114       /* PUSH Fabien */
12115       case WhiteHSideCastleFR:
12116       case WhiteASideCastleFR:
12117       case BlackHSideCastleFR:
12118       case BlackASideCastleFR:
12119       /* POP Fabien */
12120         if (appData.debugMode)
12121           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12122         fromX = currentMoveString[0] - AAA;
12123         fromY = currentMoveString[1] - ONE;
12124         toX = currentMoveString[2] - AAA;
12125         toY = currentMoveString[3] - ONE;
12126         promoChar = currentMoveString[4];
12127         if(promoChar == ';') promoChar = NULLCHAR;
12128         break;
12129
12130       case WhiteDrop:
12131       case BlackDrop:
12132         if (appData.debugMode)
12133           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12134         fromX = moveType == WhiteDrop ?
12135           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12136         (int) CharToPiece(ToLower(currentMoveString[0]));
12137         fromY = DROP_RANK;
12138         toX = currentMoveString[2] - AAA;
12139         toY = currentMoveString[3] - ONE;
12140         break;
12141
12142       case WhiteWins:
12143       case BlackWins:
12144       case GameIsDrawn:
12145       case GameUnfinished:
12146         if (appData.debugMode)
12147           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12148         p = strchr(yy_text, '{');
12149         if (p == NULL) p = strchr(yy_text, '(');
12150         if (p == NULL) {
12151             p = yy_text;
12152             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12153         } else {
12154             q = strchr(p, *p == '{' ? '}' : ')');
12155             if (q != NULL) *q = NULLCHAR;
12156             p++;
12157         }
12158         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12159         GameEnds(moveType, p, GE_FILE);
12160         done = TRUE;
12161         if (cmailMsgLoaded) {
12162             ClearHighlights();
12163             flipView = WhiteOnMove(currentMove);
12164             if (moveType == GameUnfinished) flipView = !flipView;
12165             if (appData.debugMode)
12166               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12167         }
12168         break;
12169
12170       case EndOfFile:
12171         if (appData.debugMode)
12172           fprintf(debugFP, "Parser hit end of file\n");
12173         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12174           case MT_NONE:
12175           case MT_CHECK:
12176             break;
12177           case MT_CHECKMATE:
12178           case MT_STAINMATE:
12179             if (WhiteOnMove(currentMove)) {
12180                 GameEnds(BlackWins, "Black mates", GE_FILE);
12181             } else {
12182                 GameEnds(WhiteWins, "White mates", GE_FILE);
12183             }
12184             break;
12185           case MT_STALEMATE:
12186             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12187             break;
12188         }
12189         done = TRUE;
12190         break;
12191
12192       case MoveNumberOne:
12193         if (lastLoadGameStart == GNUChessGame) {
12194             /* GNUChessGames have numbers, but they aren't move numbers */
12195             if (appData.debugMode)
12196               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12197                       yy_text, (int) moveType);
12198             return LoadGameOneMove(EndOfFile); /* tail recursion */
12199         }
12200         /* else fall thru */
12201
12202       case XBoardGame:
12203       case GNUChessGame:
12204       case PGNTag:
12205         /* Reached start of next game in file */
12206         if (appData.debugMode)
12207           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12208         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12209           case MT_NONE:
12210           case MT_CHECK:
12211             break;
12212           case MT_CHECKMATE:
12213           case MT_STAINMATE:
12214             if (WhiteOnMove(currentMove)) {
12215                 GameEnds(BlackWins, "Black mates", GE_FILE);
12216             } else {
12217                 GameEnds(WhiteWins, "White mates", GE_FILE);
12218             }
12219             break;
12220           case MT_STALEMATE:
12221             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12222             break;
12223         }
12224         done = TRUE;
12225         break;
12226
12227       case PositionDiagram:     /* should not happen; ignore */
12228       case ElapsedTime:         /* ignore */
12229       case NAG:                 /* ignore */
12230         if (appData.debugMode)
12231           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12232                   yy_text, (int) moveType);
12233         return LoadGameOneMove(EndOfFile); /* tail recursion */
12234
12235       case IllegalMove:
12236         if (appData.testLegality) {
12237             if (appData.debugMode)
12238               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12239             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12240                     (forwardMostMove / 2) + 1,
12241                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12242             DisplayError(move, 0);
12243             done = TRUE;
12244         } else {
12245             if (appData.debugMode)
12246               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12247                       yy_text, currentMoveString);
12248             if(currentMoveString[1] == '@') {
12249                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12250                 fromY = DROP_RANK;
12251             } else {
12252                 fromX = currentMoveString[0] - AAA;
12253                 fromY = currentMoveString[1] - ONE;
12254             }
12255             toX = currentMoveString[2] - AAA;
12256             toY = currentMoveString[3] - ONE;
12257             promoChar = currentMoveString[4];
12258         }
12259         break;
12260
12261       case AmbiguousMove:
12262         if (appData.debugMode)
12263           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12264         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12265                 (forwardMostMove / 2) + 1,
12266                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12267         DisplayError(move, 0);
12268         done = TRUE;
12269         break;
12270
12271       default:
12272       case ImpossibleMove:
12273         if (appData.debugMode)
12274           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12275         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12276                 (forwardMostMove / 2) + 1,
12277                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12278         DisplayError(move, 0);
12279         done = TRUE;
12280         break;
12281     }
12282
12283     if (done) {
12284         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12285             DrawPosition(FALSE, boards[currentMove]);
12286             DisplayBothClocks();
12287             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12288               DisplayComment(currentMove - 1, commentList[currentMove]);
12289         }
12290         (void) StopLoadGameTimer();
12291         gameFileFP = NULL;
12292         cmailOldMove = forwardMostMove;
12293         return FALSE;
12294     } else {
12295         /* currentMoveString is set as a side-effect of yylex */
12296
12297         thinkOutput[0] = NULLCHAR;
12298         MakeMove(fromX, fromY, toX, toY, promoChar);
12299         killX = killY = -1; // [HGM] lion: used up
12300         currentMove = forwardMostMove;
12301         return TRUE;
12302     }
12303 }
12304
12305 /* Load the nth game from the given file */
12306 int
12307 LoadGameFromFile (char *filename, int n, char *title, int useList)
12308 {
12309     FILE *f;
12310     char buf[MSG_SIZ];
12311
12312     if (strcmp(filename, "-") == 0) {
12313         f = stdin;
12314         title = "stdin";
12315     } else {
12316         f = fopen(filename, "rb");
12317         if (f == NULL) {
12318           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12319             DisplayError(buf, errno);
12320             return FALSE;
12321         }
12322     }
12323     if (fseek(f, 0, 0) == -1) {
12324         /* f is not seekable; probably a pipe */
12325         useList = FALSE;
12326     }
12327     if (useList && n == 0) {
12328         int error = GameListBuild(f);
12329         if (error) {
12330             DisplayError(_("Cannot build game list"), error);
12331         } else if (!ListEmpty(&gameList) &&
12332                    ((ListGame *) gameList.tailPred)->number > 1) {
12333             GameListPopUp(f, title);
12334             return TRUE;
12335         }
12336         GameListDestroy();
12337         n = 1;
12338     }
12339     if (n == 0) n = 1;
12340     return LoadGame(f, n, title, FALSE);
12341 }
12342
12343
12344 void
12345 MakeRegisteredMove ()
12346 {
12347     int fromX, fromY, toX, toY;
12348     char promoChar;
12349     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12350         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12351           case CMAIL_MOVE:
12352           case CMAIL_DRAW:
12353             if (appData.debugMode)
12354               fprintf(debugFP, "Restoring %s for game %d\n",
12355                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12356
12357             thinkOutput[0] = NULLCHAR;
12358             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12359             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12360             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12361             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12362             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12363             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12364             MakeMove(fromX, fromY, toX, toY, promoChar);
12365             ShowMove(fromX, fromY, toX, toY);
12366
12367             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12368               case MT_NONE:
12369               case MT_CHECK:
12370                 break;
12371
12372               case MT_CHECKMATE:
12373               case MT_STAINMATE:
12374                 if (WhiteOnMove(currentMove)) {
12375                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12376                 } else {
12377                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12378                 }
12379                 break;
12380
12381               case MT_STALEMATE:
12382                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12383                 break;
12384             }
12385
12386             break;
12387
12388           case CMAIL_RESIGN:
12389             if (WhiteOnMove(currentMove)) {
12390                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12391             } else {
12392                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12393             }
12394             break;
12395
12396           case CMAIL_ACCEPT:
12397             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12398             break;
12399
12400           default:
12401             break;
12402         }
12403     }
12404
12405     return;
12406 }
12407
12408 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12409 int
12410 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12411 {
12412     int retVal;
12413
12414     if (gameNumber > nCmailGames) {
12415         DisplayError(_("No more games in this message"), 0);
12416         return FALSE;
12417     }
12418     if (f == lastLoadGameFP) {
12419         int offset = gameNumber - lastLoadGameNumber;
12420         if (offset == 0) {
12421             cmailMsg[0] = NULLCHAR;
12422             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12423                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12424                 nCmailMovesRegistered--;
12425             }
12426             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12427             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12428                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12429             }
12430         } else {
12431             if (! RegisterMove()) return FALSE;
12432         }
12433     }
12434
12435     retVal = LoadGame(f, gameNumber, title, useList);
12436
12437     /* Make move registered during previous look at this game, if any */
12438     MakeRegisteredMove();
12439
12440     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12441         commentList[currentMove]
12442           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12443         DisplayComment(currentMove - 1, commentList[currentMove]);
12444     }
12445
12446     return retVal;
12447 }
12448
12449 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12450 int
12451 ReloadGame (int offset)
12452 {
12453     int gameNumber = lastLoadGameNumber + offset;
12454     if (lastLoadGameFP == NULL) {
12455         DisplayError(_("No game has been loaded yet"), 0);
12456         return FALSE;
12457     }
12458     if (gameNumber <= 0) {
12459         DisplayError(_("Can't back up any further"), 0);
12460         return FALSE;
12461     }
12462     if (cmailMsgLoaded) {
12463         return CmailLoadGame(lastLoadGameFP, gameNumber,
12464                              lastLoadGameTitle, lastLoadGameUseList);
12465     } else {
12466         return LoadGame(lastLoadGameFP, gameNumber,
12467                         lastLoadGameTitle, lastLoadGameUseList);
12468     }
12469 }
12470
12471 int keys[EmptySquare+1];
12472
12473 int
12474 PositionMatches (Board b1, Board b2)
12475 {
12476     int r, f, sum=0;
12477     switch(appData.searchMode) {
12478         case 1: return CompareWithRights(b1, b2);
12479         case 2:
12480             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12481                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12482             }
12483             return TRUE;
12484         case 3:
12485             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12486               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12487                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12488             }
12489             return sum==0;
12490         case 4:
12491             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12492                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12493             }
12494             return sum==0;
12495     }
12496     return TRUE;
12497 }
12498
12499 #define Q_PROMO  4
12500 #define Q_EP     3
12501 #define Q_BCASTL 2
12502 #define Q_WCASTL 1
12503
12504 int pieceList[256], quickBoard[256];
12505 ChessSquare pieceType[256] = { EmptySquare };
12506 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12507 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12508 int soughtTotal, turn;
12509 Boolean epOK, flipSearch;
12510
12511 typedef struct {
12512     unsigned char piece, to;
12513 } Move;
12514
12515 #define DSIZE (250000)
12516
12517 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12518 Move *moveDatabase = initialSpace;
12519 unsigned int movePtr, dataSize = DSIZE;
12520
12521 int
12522 MakePieceList (Board board, int *counts)
12523 {
12524     int r, f, n=Q_PROMO, total=0;
12525     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12526     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12527         int sq = f + (r<<4);
12528         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12529             quickBoard[sq] = ++n;
12530             pieceList[n] = sq;
12531             pieceType[n] = board[r][f];
12532             counts[board[r][f]]++;
12533             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12534             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12535             total++;
12536         }
12537     }
12538     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12539     return total;
12540 }
12541
12542 void
12543 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12544 {
12545     int sq = fromX + (fromY<<4);
12546     int piece = quickBoard[sq], rook;
12547     quickBoard[sq] = 0;
12548     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12549     if(piece == pieceList[1] && fromY == toY) {
12550       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12551         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12552         moveDatabase[movePtr++].piece = Q_WCASTL;
12553         quickBoard[sq] = piece;
12554         piece = quickBoard[from]; quickBoard[from] = 0;
12555         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12556       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12557         quickBoard[sq] = 0; // remove Rook
12558         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12559         moveDatabase[movePtr++].piece = Q_WCASTL;
12560         quickBoard[sq] = pieceList[1]; // put King
12561         piece = rook;
12562         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12563       }
12564     } else
12565     if(piece == pieceList[2] && fromY == toY) {
12566       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12567         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12568         moveDatabase[movePtr++].piece = Q_BCASTL;
12569         quickBoard[sq] = piece;
12570         piece = quickBoard[from]; quickBoard[from] = 0;
12571         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12572       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12573         quickBoard[sq] = 0; // remove Rook
12574         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12575         moveDatabase[movePtr++].piece = Q_BCASTL;
12576         quickBoard[sq] = pieceList[2]; // put King
12577         piece = rook;
12578         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12579       }
12580     } else
12581     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12582         quickBoard[(fromY<<4)+toX] = 0;
12583         moveDatabase[movePtr].piece = Q_EP;
12584         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12585         moveDatabase[movePtr].to = sq;
12586     } else
12587     if(promoPiece != pieceType[piece]) {
12588         moveDatabase[movePtr++].piece = Q_PROMO;
12589         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12590     }
12591     moveDatabase[movePtr].piece = piece;
12592     quickBoard[sq] = piece;
12593     movePtr++;
12594 }
12595
12596 int
12597 PackGame (Board board)
12598 {
12599     Move *newSpace = NULL;
12600     moveDatabase[movePtr].piece = 0; // terminate previous game
12601     if(movePtr > dataSize) {
12602         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12603         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12604         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12605         if(newSpace) {
12606             int i;
12607             Move *p = moveDatabase, *q = newSpace;
12608             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12609             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12610             moveDatabase = newSpace;
12611         } else { // calloc failed, we must be out of memory. Too bad...
12612             dataSize = 0; // prevent calloc events for all subsequent games
12613             return 0;     // and signal this one isn't cached
12614         }
12615     }
12616     movePtr++;
12617     MakePieceList(board, counts);
12618     return movePtr;
12619 }
12620
12621 int
12622 QuickCompare (Board board, int *minCounts, int *maxCounts)
12623 {   // compare according to search mode
12624     int r, f;
12625     switch(appData.searchMode)
12626     {
12627       case 1: // exact position match
12628         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12629         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12630             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12631         }
12632         break;
12633       case 2: // can have extra material on empty squares
12634         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12635             if(board[r][f] == EmptySquare) continue;
12636             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12637         }
12638         break;
12639       case 3: // material with exact Pawn structure
12640         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12641             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12642             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12643         } // fall through to material comparison
12644       case 4: // exact material
12645         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12646         break;
12647       case 6: // material range with given imbalance
12648         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12649         // fall through to range comparison
12650       case 5: // material range
12651         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12652     }
12653     return TRUE;
12654 }
12655
12656 int
12657 QuickScan (Board board, Move *move)
12658 {   // reconstruct game,and compare all positions in it
12659     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12660     do {
12661         int piece = move->piece;
12662         int to = move->to, from = pieceList[piece];
12663         if(found < 0) { // if already found just scan to game end for final piece count
12664           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12665            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12666            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12667                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12668             ) {
12669             static int lastCounts[EmptySquare+1];
12670             int i;
12671             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12672             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12673           } else stretch = 0;
12674           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12675           if(found >= 0 && !appData.minPieces) return found;
12676         }
12677         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12678           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12679           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12680             piece = (++move)->piece;
12681             from = pieceList[piece];
12682             counts[pieceType[piece]]--;
12683             pieceType[piece] = (ChessSquare) move->to;
12684             counts[move->to]++;
12685           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12686             counts[pieceType[quickBoard[to]]]--;
12687             quickBoard[to] = 0; total--;
12688             move++;
12689             continue;
12690           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12691             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12692             from  = pieceList[piece]; // so this must be King
12693             quickBoard[from] = 0;
12694             pieceList[piece] = to;
12695             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12696             quickBoard[from] = 0; // rook
12697             quickBoard[to] = piece;
12698             to = move->to; piece = move->piece;
12699             goto aftercastle;
12700           }
12701         }
12702         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12703         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12704         quickBoard[from] = 0;
12705       aftercastle:
12706         quickBoard[to] = piece;
12707         pieceList[piece] = to;
12708         cnt++; turn ^= 3;
12709         move++;
12710     } while(1);
12711 }
12712
12713 void
12714 InitSearch ()
12715 {
12716     int r, f;
12717     flipSearch = FALSE;
12718     CopyBoard(soughtBoard, boards[currentMove]);
12719     soughtTotal = MakePieceList(soughtBoard, maxSought);
12720     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12721     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12722     CopyBoard(reverseBoard, boards[currentMove]);
12723     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12724         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12725         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12726         reverseBoard[r][f] = piece;
12727     }
12728     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12729     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12730     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12731                  || (boards[currentMove][CASTLING][2] == NoRights ||
12732                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12733                  && (boards[currentMove][CASTLING][5] == NoRights ||
12734                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12735       ) {
12736         flipSearch = TRUE;
12737         CopyBoard(flipBoard, soughtBoard);
12738         CopyBoard(rotateBoard, reverseBoard);
12739         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12740             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12741             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12742         }
12743     }
12744     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12745     if(appData.searchMode >= 5) {
12746         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12747         MakePieceList(soughtBoard, minSought);
12748         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12749     }
12750     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12751         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12752 }
12753
12754 GameInfo dummyInfo;
12755 static int creatingBook;
12756
12757 int
12758 GameContainsPosition (FILE *f, ListGame *lg)
12759 {
12760     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12761     int fromX, fromY, toX, toY;
12762     char promoChar;
12763     static int initDone=FALSE;
12764
12765     // weed out games based on numerical tag comparison
12766     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12767     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12768     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12769     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12770     if(!initDone) {
12771         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12772         initDone = TRUE;
12773     }
12774     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12775     else CopyBoard(boards[scratch], initialPosition); // default start position
12776     if(lg->moves) {
12777         turn = btm + 1;
12778         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12779         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12780     }
12781     if(btm) plyNr++;
12782     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12783     fseek(f, lg->offset, 0);
12784     yynewfile(f);
12785     while(1) {
12786         yyboardindex = scratch;
12787         quickFlag = plyNr+1;
12788         next = Myylex();
12789         quickFlag = 0;
12790         switch(next) {
12791             case PGNTag:
12792                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12793             default:
12794                 continue;
12795
12796             case XBoardGame:
12797             case GNUChessGame:
12798                 if(plyNr) return -1; // after we have seen moves, this is for new game
12799               continue;
12800
12801             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12802             case ImpossibleMove:
12803             case WhiteWins: // game ends here with these four
12804             case BlackWins:
12805             case GameIsDrawn:
12806             case GameUnfinished:
12807                 return -1;
12808
12809             case IllegalMove:
12810                 if(appData.testLegality) return -1;
12811             case WhiteCapturesEnPassant:
12812             case BlackCapturesEnPassant:
12813             case WhitePromotion:
12814             case BlackPromotion:
12815             case WhiteNonPromotion:
12816             case BlackNonPromotion:
12817             case NormalMove:
12818             case FirstLeg:
12819             case WhiteKingSideCastle:
12820             case WhiteQueenSideCastle:
12821             case BlackKingSideCastle:
12822             case BlackQueenSideCastle:
12823             case WhiteKingSideCastleWild:
12824             case WhiteQueenSideCastleWild:
12825             case BlackKingSideCastleWild:
12826             case BlackQueenSideCastleWild:
12827             case WhiteHSideCastleFR:
12828             case WhiteASideCastleFR:
12829             case BlackHSideCastleFR:
12830             case BlackASideCastleFR:
12831                 fromX = currentMoveString[0] - AAA;
12832                 fromY = currentMoveString[1] - ONE;
12833                 toX = currentMoveString[2] - AAA;
12834                 toY = currentMoveString[3] - ONE;
12835                 promoChar = currentMoveString[4];
12836                 break;
12837             case WhiteDrop:
12838             case BlackDrop:
12839                 fromX = next == WhiteDrop ?
12840                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12841                   (int) CharToPiece(ToLower(currentMoveString[0]));
12842                 fromY = DROP_RANK;
12843                 toX = currentMoveString[2] - AAA;
12844                 toY = currentMoveString[3] - ONE;
12845                 promoChar = 0;
12846                 break;
12847         }
12848         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12849         plyNr++;
12850         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12851         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12852         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12853         if(appData.findMirror) {
12854             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12855             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12856         }
12857     }
12858 }
12859
12860 /* Load the nth game from open file f */
12861 int
12862 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12863 {
12864     ChessMove cm;
12865     char buf[MSG_SIZ];
12866     int gn = gameNumber;
12867     ListGame *lg = NULL;
12868     int numPGNTags = 0;
12869     int err, pos = -1;
12870     GameMode oldGameMode;
12871     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12872     char oldName[MSG_SIZ];
12873
12874     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12875
12876     if (appData.debugMode)
12877         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12878
12879     if (gameMode == Training )
12880         SetTrainingModeOff();
12881
12882     oldGameMode = gameMode;
12883     if (gameMode != BeginningOfGame) {
12884       Reset(FALSE, TRUE);
12885     }
12886     killX = killY = -1; // [HGM] lion: in case we did not Reset
12887
12888     gameFileFP = f;
12889     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12890         fclose(lastLoadGameFP);
12891     }
12892
12893     if (useList) {
12894         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12895
12896         if (lg) {
12897             fseek(f, lg->offset, 0);
12898             GameListHighlight(gameNumber);
12899             pos = lg->position;
12900             gn = 1;
12901         }
12902         else {
12903             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12904               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12905             else
12906             DisplayError(_("Game number out of range"), 0);
12907             return FALSE;
12908         }
12909     } else {
12910         GameListDestroy();
12911         if (fseek(f, 0, 0) == -1) {
12912             if (f == lastLoadGameFP ?
12913                 gameNumber == lastLoadGameNumber + 1 :
12914                 gameNumber == 1) {
12915                 gn = 1;
12916             } else {
12917                 DisplayError(_("Can't seek on game file"), 0);
12918                 return FALSE;
12919             }
12920         }
12921     }
12922     lastLoadGameFP = f;
12923     lastLoadGameNumber = gameNumber;
12924     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12925     lastLoadGameUseList = useList;
12926
12927     yynewfile(f);
12928
12929     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12930       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12931                 lg->gameInfo.black);
12932             DisplayTitle(buf);
12933     } else if (*title != NULLCHAR) {
12934         if (gameNumber > 1) {
12935           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12936             DisplayTitle(buf);
12937         } else {
12938             DisplayTitle(title);
12939         }
12940     }
12941
12942     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12943         gameMode = PlayFromGameFile;
12944         ModeHighlight();
12945     }
12946
12947     currentMove = forwardMostMove = backwardMostMove = 0;
12948     CopyBoard(boards[0], initialPosition);
12949     StopClocks();
12950
12951     /*
12952      * Skip the first gn-1 games in the file.
12953      * Also skip over anything that precedes an identifiable
12954      * start of game marker, to avoid being confused by
12955      * garbage at the start of the file.  Currently
12956      * recognized start of game markers are the move number "1",
12957      * the pattern "gnuchess .* game", the pattern
12958      * "^[#;%] [^ ]* game file", and a PGN tag block.
12959      * A game that starts with one of the latter two patterns
12960      * will also have a move number 1, possibly
12961      * following a position diagram.
12962      * 5-4-02: Let's try being more lenient and allowing a game to
12963      * start with an unnumbered move.  Does that break anything?
12964      */
12965     cm = lastLoadGameStart = EndOfFile;
12966     while (gn > 0) {
12967         yyboardindex = forwardMostMove;
12968         cm = (ChessMove) Myylex();
12969         switch (cm) {
12970           case EndOfFile:
12971             if (cmailMsgLoaded) {
12972                 nCmailGames = CMAIL_MAX_GAMES - gn;
12973             } else {
12974                 Reset(TRUE, TRUE);
12975                 DisplayError(_("Game not found in file"), 0);
12976             }
12977             return FALSE;
12978
12979           case GNUChessGame:
12980           case XBoardGame:
12981             gn--;
12982             lastLoadGameStart = cm;
12983             break;
12984
12985           case MoveNumberOne:
12986             switch (lastLoadGameStart) {
12987               case GNUChessGame:
12988               case XBoardGame:
12989               case PGNTag:
12990                 break;
12991               case MoveNumberOne:
12992               case EndOfFile:
12993                 gn--;           /* count this game */
12994                 lastLoadGameStart = cm;
12995                 break;
12996               default:
12997                 /* impossible */
12998                 break;
12999             }
13000             break;
13001
13002           case PGNTag:
13003             switch (lastLoadGameStart) {
13004               case GNUChessGame:
13005               case PGNTag:
13006               case MoveNumberOne:
13007               case EndOfFile:
13008                 gn--;           /* count this game */
13009                 lastLoadGameStart = cm;
13010                 break;
13011               case XBoardGame:
13012                 lastLoadGameStart = cm; /* game counted already */
13013                 break;
13014               default:
13015                 /* impossible */
13016                 break;
13017             }
13018             if (gn > 0) {
13019                 do {
13020                     yyboardindex = forwardMostMove;
13021                     cm = (ChessMove) Myylex();
13022                 } while (cm == PGNTag || cm == Comment);
13023             }
13024             break;
13025
13026           case WhiteWins:
13027           case BlackWins:
13028           case GameIsDrawn:
13029             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13030                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13031                     != CMAIL_OLD_RESULT) {
13032                     nCmailResults ++ ;
13033                     cmailResult[  CMAIL_MAX_GAMES
13034                                 - gn - 1] = CMAIL_OLD_RESULT;
13035                 }
13036             }
13037             break;
13038
13039           case NormalMove:
13040           case FirstLeg:
13041             /* Only a NormalMove can be at the start of a game
13042              * without a position diagram. */
13043             if (lastLoadGameStart == EndOfFile ) {
13044               gn--;
13045               lastLoadGameStart = MoveNumberOne;
13046             }
13047             break;
13048
13049           default:
13050             break;
13051         }
13052     }
13053
13054     if (appData.debugMode)
13055       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13056
13057     if (cm == XBoardGame) {
13058         /* Skip any header junk before position diagram and/or move 1 */
13059         for (;;) {
13060             yyboardindex = forwardMostMove;
13061             cm = (ChessMove) Myylex();
13062
13063             if (cm == EndOfFile ||
13064                 cm == GNUChessGame || cm == XBoardGame) {
13065                 /* Empty game; pretend end-of-file and handle later */
13066                 cm = EndOfFile;
13067                 break;
13068             }
13069
13070             if (cm == MoveNumberOne || cm == PositionDiagram ||
13071                 cm == PGNTag || cm == Comment)
13072               break;
13073         }
13074     } else if (cm == GNUChessGame) {
13075         if (gameInfo.event != NULL) {
13076             free(gameInfo.event);
13077         }
13078         gameInfo.event = StrSave(yy_text);
13079     }
13080
13081     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13082     while (cm == PGNTag) {
13083         if (appData.debugMode)
13084           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13085         err = ParsePGNTag(yy_text, &gameInfo);
13086         if (!err) numPGNTags++;
13087
13088         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13089         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13090             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13091             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13092             InitPosition(TRUE);
13093             oldVariant = gameInfo.variant;
13094             if (appData.debugMode)
13095               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13096         }
13097
13098
13099         if (gameInfo.fen != NULL) {
13100           Board initial_position;
13101           startedFromSetupPosition = TRUE;
13102           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13103             Reset(TRUE, TRUE);
13104             DisplayError(_("Bad FEN position in file"), 0);
13105             return FALSE;
13106           }
13107           CopyBoard(boards[0], initial_position);
13108           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13109             CopyBoard(initialPosition, initial_position);
13110           if (blackPlaysFirst) {
13111             currentMove = forwardMostMove = backwardMostMove = 1;
13112             CopyBoard(boards[1], initial_position);
13113             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13114             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13115             timeRemaining[0][1] = whiteTimeRemaining;
13116             timeRemaining[1][1] = blackTimeRemaining;
13117             if (commentList[0] != NULL) {
13118               commentList[1] = commentList[0];
13119               commentList[0] = NULL;
13120             }
13121           } else {
13122             currentMove = forwardMostMove = backwardMostMove = 0;
13123           }
13124           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13125           {   int i;
13126               initialRulePlies = FENrulePlies;
13127               for( i=0; i< nrCastlingRights; i++ )
13128                   initialRights[i] = initial_position[CASTLING][i];
13129           }
13130           yyboardindex = forwardMostMove;
13131           free(gameInfo.fen);
13132           gameInfo.fen = NULL;
13133         }
13134
13135         yyboardindex = forwardMostMove;
13136         cm = (ChessMove) Myylex();
13137
13138         /* Handle comments interspersed among the tags */
13139         while (cm == Comment) {
13140             char *p;
13141             if (appData.debugMode)
13142               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13143             p = yy_text;
13144             AppendComment(currentMove, p, FALSE);
13145             yyboardindex = forwardMostMove;
13146             cm = (ChessMove) Myylex();
13147         }
13148     }
13149
13150     /* don't rely on existence of Event tag since if game was
13151      * pasted from clipboard the Event tag may not exist
13152      */
13153     if (numPGNTags > 0){
13154         char *tags;
13155         if (gameInfo.variant == VariantNormal) {
13156           VariantClass v = StringToVariant(gameInfo.event);
13157           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13158           if(v < VariantShogi) gameInfo.variant = v;
13159         }
13160         if (!matchMode) {
13161           if( appData.autoDisplayTags ) {
13162             tags = PGNTags(&gameInfo);
13163             TagsPopUp(tags, CmailMsg());
13164             free(tags);
13165           }
13166         }
13167     } else {
13168         /* Make something up, but don't display it now */
13169         SetGameInfo();
13170         TagsPopDown();
13171     }
13172
13173     if (cm == PositionDiagram) {
13174         int i, j;
13175         char *p;
13176         Board initial_position;
13177
13178         if (appData.debugMode)
13179           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13180
13181         if (!startedFromSetupPosition) {
13182             p = yy_text;
13183             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13184               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13185                 switch (*p) {
13186                   case '{':
13187                   case '[':
13188                   case '-':
13189                   case ' ':
13190                   case '\t':
13191                   case '\n':
13192                   case '\r':
13193                     break;
13194                   default:
13195                     initial_position[i][j++] = CharToPiece(*p);
13196                     break;
13197                 }
13198             while (*p == ' ' || *p == '\t' ||
13199                    *p == '\n' || *p == '\r') p++;
13200
13201             if (strncmp(p, "black", strlen("black"))==0)
13202               blackPlaysFirst = TRUE;
13203             else
13204               blackPlaysFirst = FALSE;
13205             startedFromSetupPosition = TRUE;
13206
13207             CopyBoard(boards[0], initial_position);
13208             if (blackPlaysFirst) {
13209                 currentMove = forwardMostMove = backwardMostMove = 1;
13210                 CopyBoard(boards[1], initial_position);
13211                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13212                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13213                 timeRemaining[0][1] = whiteTimeRemaining;
13214                 timeRemaining[1][1] = blackTimeRemaining;
13215                 if (commentList[0] != NULL) {
13216                     commentList[1] = commentList[0];
13217                     commentList[0] = NULL;
13218                 }
13219             } else {
13220                 currentMove = forwardMostMove = backwardMostMove = 0;
13221             }
13222         }
13223         yyboardindex = forwardMostMove;
13224         cm = (ChessMove) Myylex();
13225     }
13226
13227   if(!creatingBook) {
13228     if (first.pr == NoProc) {
13229         StartChessProgram(&first);
13230     }
13231     InitChessProgram(&first, FALSE);
13232     if(gameInfo.variant == VariantUnknown && *oldName) {
13233         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13234         gameInfo.variant = v;
13235     }
13236     SendToProgram("force\n", &first);
13237     if (startedFromSetupPosition) {
13238         SendBoard(&first, forwardMostMove);
13239     if (appData.debugMode) {
13240         fprintf(debugFP, "Load Game\n");
13241     }
13242         DisplayBothClocks();
13243     }
13244   }
13245
13246     /* [HGM] server: flag to write setup moves in broadcast file as one */
13247     loadFlag = appData.suppressLoadMoves;
13248
13249     while (cm == Comment) {
13250         char *p;
13251         if (appData.debugMode)
13252           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13253         p = yy_text;
13254         AppendComment(currentMove, p, FALSE);
13255         yyboardindex = forwardMostMove;
13256         cm = (ChessMove) Myylex();
13257     }
13258
13259     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13260         cm == WhiteWins || cm == BlackWins ||
13261         cm == GameIsDrawn || cm == GameUnfinished) {
13262         DisplayMessage("", _("No moves in game"));
13263         if (cmailMsgLoaded) {
13264             if (appData.debugMode)
13265               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13266             ClearHighlights();
13267             flipView = FALSE;
13268         }
13269         DrawPosition(FALSE, boards[currentMove]);
13270         DisplayBothClocks();
13271         gameMode = EditGame;
13272         ModeHighlight();
13273         gameFileFP = NULL;
13274         cmailOldMove = 0;
13275         return TRUE;
13276     }
13277
13278     // [HGM] PV info: routine tests if comment empty
13279     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13280         DisplayComment(currentMove - 1, commentList[currentMove]);
13281     }
13282     if (!matchMode && appData.timeDelay != 0)
13283       DrawPosition(FALSE, boards[currentMove]);
13284
13285     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13286       programStats.ok_to_send = 1;
13287     }
13288
13289     /* if the first token after the PGN tags is a move
13290      * and not move number 1, retrieve it from the parser
13291      */
13292     if (cm != MoveNumberOne)
13293         LoadGameOneMove(cm);
13294
13295     /* load the remaining moves from the file */
13296     while (LoadGameOneMove(EndOfFile)) {
13297       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13298       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13299     }
13300
13301     /* rewind to the start of the game */
13302     currentMove = backwardMostMove;
13303
13304     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13305
13306     if (oldGameMode == AnalyzeFile) {
13307       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13308       AnalyzeFileEvent();
13309     } else
13310     if (oldGameMode == AnalyzeMode) {
13311       AnalyzeFileEvent();
13312     }
13313
13314     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13315         long int w, b; // [HGM] adjourn: restore saved clock times
13316         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13317         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13318             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13319             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13320         }
13321     }
13322
13323     if(creatingBook) return TRUE;
13324     if (!matchMode && pos > 0) {
13325         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13326     } else
13327     if (matchMode || appData.timeDelay == 0) {
13328       ToEndEvent();
13329     } else if (appData.timeDelay > 0) {
13330       AutoPlayGameLoop();
13331     }
13332
13333     if (appData.debugMode)
13334         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13335
13336     loadFlag = 0; /* [HGM] true game starts */
13337     return TRUE;
13338 }
13339
13340 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13341 int
13342 ReloadPosition (int offset)
13343 {
13344     int positionNumber = lastLoadPositionNumber + offset;
13345     if (lastLoadPositionFP == NULL) {
13346         DisplayError(_("No position has been loaded yet"), 0);
13347         return FALSE;
13348     }
13349     if (positionNumber <= 0) {
13350         DisplayError(_("Can't back up any further"), 0);
13351         return FALSE;
13352     }
13353     return LoadPosition(lastLoadPositionFP, positionNumber,
13354                         lastLoadPositionTitle);
13355 }
13356
13357 /* Load the nth position from the given file */
13358 int
13359 LoadPositionFromFile (char *filename, int n, char *title)
13360 {
13361     FILE *f;
13362     char buf[MSG_SIZ];
13363
13364     if (strcmp(filename, "-") == 0) {
13365         return LoadPosition(stdin, n, "stdin");
13366     } else {
13367         f = fopen(filename, "rb");
13368         if (f == NULL) {
13369             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13370             DisplayError(buf, errno);
13371             return FALSE;
13372         } else {
13373             return LoadPosition(f, n, title);
13374         }
13375     }
13376 }
13377
13378 /* Load the nth position from the given open file, and close it */
13379 int
13380 LoadPosition (FILE *f, int positionNumber, char *title)
13381 {
13382     char *p, line[MSG_SIZ];
13383     Board initial_position;
13384     int i, j, fenMode, pn;
13385
13386     if (gameMode == Training )
13387         SetTrainingModeOff();
13388
13389     if (gameMode != BeginningOfGame) {
13390         Reset(FALSE, TRUE);
13391     }
13392     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13393         fclose(lastLoadPositionFP);
13394     }
13395     if (positionNumber == 0) positionNumber = 1;
13396     lastLoadPositionFP = f;
13397     lastLoadPositionNumber = positionNumber;
13398     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13399     if (first.pr == NoProc && !appData.noChessProgram) {
13400       StartChessProgram(&first);
13401       InitChessProgram(&first, FALSE);
13402     }
13403     pn = positionNumber;
13404     if (positionNumber < 0) {
13405         /* Negative position number means to seek to that byte offset */
13406         if (fseek(f, -positionNumber, 0) == -1) {
13407             DisplayError(_("Can't seek on position file"), 0);
13408             return FALSE;
13409         };
13410         pn = 1;
13411     } else {
13412         if (fseek(f, 0, 0) == -1) {
13413             if (f == lastLoadPositionFP ?
13414                 positionNumber == lastLoadPositionNumber + 1 :
13415                 positionNumber == 1) {
13416                 pn = 1;
13417             } else {
13418                 DisplayError(_("Can't seek on position file"), 0);
13419                 return FALSE;
13420             }
13421         }
13422     }
13423     /* See if this file is FEN or old-style xboard */
13424     if (fgets(line, MSG_SIZ, f) == NULL) {
13425         DisplayError(_("Position not found in file"), 0);
13426         return FALSE;
13427     }
13428     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13429     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13430
13431     if (pn >= 2) {
13432         if (fenMode || line[0] == '#') pn--;
13433         while (pn > 0) {
13434             /* skip positions before number pn */
13435             if (fgets(line, MSG_SIZ, f) == NULL) {
13436                 Reset(TRUE, TRUE);
13437                 DisplayError(_("Position not found in file"), 0);
13438                 return FALSE;
13439             }
13440             if (fenMode || line[0] == '#') pn--;
13441         }
13442     }
13443
13444     if (fenMode) {
13445         char *p;
13446         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13447             DisplayError(_("Bad FEN position in file"), 0);
13448             return FALSE;
13449         }
13450         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13451             sscanf(p+3, "%s", bestMove);
13452         } else *bestMove = NULLCHAR;
13453     } else {
13454         (void) fgets(line, MSG_SIZ, f);
13455         (void) fgets(line, MSG_SIZ, f);
13456
13457         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13458             (void) fgets(line, MSG_SIZ, f);
13459             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13460                 if (*p == ' ')
13461                   continue;
13462                 initial_position[i][j++] = CharToPiece(*p);
13463             }
13464         }
13465
13466         blackPlaysFirst = FALSE;
13467         if (!feof(f)) {
13468             (void) fgets(line, MSG_SIZ, f);
13469             if (strncmp(line, "black", strlen("black"))==0)
13470               blackPlaysFirst = TRUE;
13471         }
13472     }
13473     startedFromSetupPosition = TRUE;
13474
13475     CopyBoard(boards[0], initial_position);
13476     if (blackPlaysFirst) {
13477         currentMove = forwardMostMove = backwardMostMove = 1;
13478         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13479         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13480         CopyBoard(boards[1], initial_position);
13481         DisplayMessage("", _("Black to play"));
13482     } else {
13483         currentMove = forwardMostMove = backwardMostMove = 0;
13484         DisplayMessage("", _("White to play"));
13485     }
13486     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13487     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13488         SendToProgram("force\n", &first);
13489         SendBoard(&first, forwardMostMove);
13490     }
13491     if (appData.debugMode) {
13492 int i, j;
13493   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13494   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13495         fprintf(debugFP, "Load Position\n");
13496     }
13497
13498     if (positionNumber > 1) {
13499       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13500         DisplayTitle(line);
13501     } else {
13502         DisplayTitle(title);
13503     }
13504     gameMode = EditGame;
13505     ModeHighlight();
13506     ResetClocks();
13507     timeRemaining[0][1] = whiteTimeRemaining;
13508     timeRemaining[1][1] = blackTimeRemaining;
13509     DrawPosition(FALSE, boards[currentMove]);
13510
13511     return TRUE;
13512 }
13513
13514
13515 void
13516 CopyPlayerNameIntoFileName (char **dest, char *src)
13517 {
13518     while (*src != NULLCHAR && *src != ',') {
13519         if (*src == ' ') {
13520             *(*dest)++ = '_';
13521             src++;
13522         } else {
13523             *(*dest)++ = *src++;
13524         }
13525     }
13526 }
13527
13528 char *
13529 DefaultFileName (char *ext)
13530 {
13531     static char def[MSG_SIZ];
13532     char *p;
13533
13534     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13535         p = def;
13536         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13537         *p++ = '-';
13538         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13539         *p++ = '.';
13540         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13541     } else {
13542         def[0] = NULLCHAR;
13543     }
13544     return def;
13545 }
13546
13547 /* Save the current game to the given file */
13548 int
13549 SaveGameToFile (char *filename, int append)
13550 {
13551     FILE *f;
13552     char buf[MSG_SIZ];
13553     int result, i, t,tot=0;
13554
13555     if (strcmp(filename, "-") == 0) {
13556         return SaveGame(stdout, 0, NULL);
13557     } else {
13558         for(i=0; i<10; i++) { // upto 10 tries
13559              f = fopen(filename, append ? "a" : "w");
13560              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13561              if(f || errno != 13) break;
13562              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13563              tot += t;
13564         }
13565         if (f == NULL) {
13566             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13567             DisplayError(buf, errno);
13568             return FALSE;
13569         } else {
13570             safeStrCpy(buf, lastMsg, MSG_SIZ);
13571             DisplayMessage(_("Waiting for access to save file"), "");
13572             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13573             DisplayMessage(_("Saving game"), "");
13574             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13575             result = SaveGame(f, 0, NULL);
13576             DisplayMessage(buf, "");
13577             return result;
13578         }
13579     }
13580 }
13581
13582 char *
13583 SavePart (char *str)
13584 {
13585     static char buf[MSG_SIZ];
13586     char *p;
13587
13588     p = strchr(str, ' ');
13589     if (p == NULL) return str;
13590     strncpy(buf, str, p - str);
13591     buf[p - str] = NULLCHAR;
13592     return buf;
13593 }
13594
13595 #define PGN_MAX_LINE 75
13596
13597 #define PGN_SIDE_WHITE  0
13598 #define PGN_SIDE_BLACK  1
13599
13600 static int
13601 FindFirstMoveOutOfBook (int side)
13602 {
13603     int result = -1;
13604
13605     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13606         int index = backwardMostMove;
13607         int has_book_hit = 0;
13608
13609         if( (index % 2) != side ) {
13610             index++;
13611         }
13612
13613         while( index < forwardMostMove ) {
13614             /* Check to see if engine is in book */
13615             int depth = pvInfoList[index].depth;
13616             int score = pvInfoList[index].score;
13617             int in_book = 0;
13618
13619             if( depth <= 2 ) {
13620                 in_book = 1;
13621             }
13622             else if( score == 0 && depth == 63 ) {
13623                 in_book = 1; /* Zappa */
13624             }
13625             else if( score == 2 && depth == 99 ) {
13626                 in_book = 1; /* Abrok */
13627             }
13628
13629             has_book_hit += in_book;
13630
13631             if( ! in_book ) {
13632                 result = index;
13633
13634                 break;
13635             }
13636
13637             index += 2;
13638         }
13639     }
13640
13641     return result;
13642 }
13643
13644 void
13645 GetOutOfBookInfo (char * buf)
13646 {
13647     int oob[2];
13648     int i;
13649     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13650
13651     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13652     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13653
13654     *buf = '\0';
13655
13656     if( oob[0] >= 0 || oob[1] >= 0 ) {
13657         for( i=0; i<2; i++ ) {
13658             int idx = oob[i];
13659
13660             if( idx >= 0 ) {
13661                 if( i > 0 && oob[0] >= 0 ) {
13662                     strcat( buf, "   " );
13663                 }
13664
13665                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13666                 sprintf( buf+strlen(buf), "%s%.2f",
13667                     pvInfoList[idx].score >= 0 ? "+" : "",
13668                     pvInfoList[idx].score / 100.0 );
13669             }
13670         }
13671     }
13672 }
13673
13674 /* Save game in PGN style */
13675 static void
13676 SaveGamePGN2 (FILE *f)
13677 {
13678     int i, offset, linelen, newblock;
13679 //    char *movetext;
13680     char numtext[32];
13681     int movelen, numlen, blank;
13682     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13683
13684     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13685
13686     PrintPGNTags(f, &gameInfo);
13687
13688     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13689
13690     if (backwardMostMove > 0 || startedFromSetupPosition) {
13691         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13692         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13693         fprintf(f, "\n{--------------\n");
13694         PrintPosition(f, backwardMostMove);
13695         fprintf(f, "--------------}\n");
13696         free(fen);
13697     }
13698     else {
13699         /* [AS] Out of book annotation */
13700         if( appData.saveOutOfBookInfo ) {
13701             char buf[64];
13702
13703             GetOutOfBookInfo( buf );
13704
13705             if( buf[0] != '\0' ) {
13706                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13707             }
13708         }
13709
13710         fprintf(f, "\n");
13711     }
13712
13713     i = backwardMostMove;
13714     linelen = 0;
13715     newblock = TRUE;
13716
13717     while (i < forwardMostMove) {
13718         /* Print comments preceding this move */
13719         if (commentList[i] != NULL) {
13720             if (linelen > 0) fprintf(f, "\n");
13721             fprintf(f, "%s", commentList[i]);
13722             linelen = 0;
13723             newblock = TRUE;
13724         }
13725
13726         /* Format move number */
13727         if ((i % 2) == 0)
13728           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13729         else
13730           if (newblock)
13731             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13732           else
13733             numtext[0] = NULLCHAR;
13734
13735         numlen = strlen(numtext);
13736         newblock = FALSE;
13737
13738         /* Print move number */
13739         blank = linelen > 0 && numlen > 0;
13740         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13741             fprintf(f, "\n");
13742             linelen = 0;
13743             blank = 0;
13744         }
13745         if (blank) {
13746             fprintf(f, " ");
13747             linelen++;
13748         }
13749         fprintf(f, "%s", numtext);
13750         linelen += numlen;
13751
13752         /* Get move */
13753         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13754         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13755
13756         /* Print move */
13757         blank = linelen > 0 && movelen > 0;
13758         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13759             fprintf(f, "\n");
13760             linelen = 0;
13761             blank = 0;
13762         }
13763         if (blank) {
13764             fprintf(f, " ");
13765             linelen++;
13766         }
13767         fprintf(f, "%s", move_buffer);
13768         linelen += movelen;
13769
13770         /* [AS] Add PV info if present */
13771         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13772             /* [HGM] add time */
13773             char buf[MSG_SIZ]; int seconds;
13774
13775             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13776
13777             if( seconds <= 0)
13778               buf[0] = 0;
13779             else
13780               if( seconds < 30 )
13781                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13782               else
13783                 {
13784                   seconds = (seconds + 4)/10; // round to full seconds
13785                   if( seconds < 60 )
13786                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13787                   else
13788                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13789                 }
13790
13791             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13792                       pvInfoList[i].score >= 0 ? "+" : "",
13793                       pvInfoList[i].score / 100.0,
13794                       pvInfoList[i].depth,
13795                       buf );
13796
13797             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13798
13799             /* Print score/depth */
13800             blank = linelen > 0 && movelen > 0;
13801             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13802                 fprintf(f, "\n");
13803                 linelen = 0;
13804                 blank = 0;
13805             }
13806             if (blank) {
13807                 fprintf(f, " ");
13808                 linelen++;
13809             }
13810             fprintf(f, "%s", move_buffer);
13811             linelen += movelen;
13812         }
13813
13814         i++;
13815     }
13816
13817     /* Start a new line */
13818     if (linelen > 0) fprintf(f, "\n");
13819
13820     /* Print comments after last move */
13821     if (commentList[i] != NULL) {
13822         fprintf(f, "%s\n", commentList[i]);
13823     }
13824
13825     /* Print result */
13826     if (gameInfo.resultDetails != NULL &&
13827         gameInfo.resultDetails[0] != NULLCHAR) {
13828         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13829         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13830            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13831             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13832         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13833     } else {
13834         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13835     }
13836 }
13837
13838 /* Save game in PGN style and close the file */
13839 int
13840 SaveGamePGN (FILE *f)
13841 {
13842     SaveGamePGN2(f);
13843     fclose(f);
13844     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13845     return TRUE;
13846 }
13847
13848 /* Save game in old style and close the file */
13849 int
13850 SaveGameOldStyle (FILE *f)
13851 {
13852     int i, offset;
13853     time_t tm;
13854
13855     tm = time((time_t *) NULL);
13856
13857     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13858     PrintOpponents(f);
13859
13860     if (backwardMostMove > 0 || startedFromSetupPosition) {
13861         fprintf(f, "\n[--------------\n");
13862         PrintPosition(f, backwardMostMove);
13863         fprintf(f, "--------------]\n");
13864     } else {
13865         fprintf(f, "\n");
13866     }
13867
13868     i = backwardMostMove;
13869     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13870
13871     while (i < forwardMostMove) {
13872         if (commentList[i] != NULL) {
13873             fprintf(f, "[%s]\n", commentList[i]);
13874         }
13875
13876         if ((i % 2) == 1) {
13877             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13878             i++;
13879         } else {
13880             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13881             i++;
13882             if (commentList[i] != NULL) {
13883                 fprintf(f, "\n");
13884                 continue;
13885             }
13886             if (i >= forwardMostMove) {
13887                 fprintf(f, "\n");
13888                 break;
13889             }
13890             fprintf(f, "%s\n", parseList[i]);
13891             i++;
13892         }
13893     }
13894
13895     if (commentList[i] != NULL) {
13896         fprintf(f, "[%s]\n", commentList[i]);
13897     }
13898
13899     /* This isn't really the old style, but it's close enough */
13900     if (gameInfo.resultDetails != NULL &&
13901         gameInfo.resultDetails[0] != NULLCHAR) {
13902         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13903                 gameInfo.resultDetails);
13904     } else {
13905         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13906     }
13907
13908     fclose(f);
13909     return TRUE;
13910 }
13911
13912 /* Save the current game to open file f and close the file */
13913 int
13914 SaveGame (FILE *f, int dummy, char *dummy2)
13915 {
13916     if (gameMode == EditPosition) EditPositionDone(TRUE);
13917     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13918     if (appData.oldSaveStyle)
13919       return SaveGameOldStyle(f);
13920     else
13921       return SaveGamePGN(f);
13922 }
13923
13924 /* Save the current position to the given file */
13925 int
13926 SavePositionToFile (char *filename)
13927 {
13928     FILE *f;
13929     char buf[MSG_SIZ];
13930
13931     if (strcmp(filename, "-") == 0) {
13932         return SavePosition(stdout, 0, NULL);
13933     } else {
13934         f = fopen(filename, "a");
13935         if (f == NULL) {
13936             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13937             DisplayError(buf, errno);
13938             return FALSE;
13939         } else {
13940             safeStrCpy(buf, lastMsg, MSG_SIZ);
13941             DisplayMessage(_("Waiting for access to save file"), "");
13942             flock(fileno(f), LOCK_EX); // [HGM] lock
13943             DisplayMessage(_("Saving position"), "");
13944             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13945             SavePosition(f, 0, NULL);
13946             DisplayMessage(buf, "");
13947             return TRUE;
13948         }
13949     }
13950 }
13951
13952 /* Save the current position to the given open file and close the file */
13953 int
13954 SavePosition (FILE *f, int dummy, char *dummy2)
13955 {
13956     time_t tm;
13957     char *fen;
13958
13959     if (gameMode == EditPosition) EditPositionDone(TRUE);
13960     if (appData.oldSaveStyle) {
13961         tm = time((time_t *) NULL);
13962
13963         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13964         PrintOpponents(f);
13965         fprintf(f, "[--------------\n");
13966         PrintPosition(f, currentMove);
13967         fprintf(f, "--------------]\n");
13968     } else {
13969         fen = PositionToFEN(currentMove, NULL, 1);
13970         fprintf(f, "%s\n", fen);
13971         free(fen);
13972     }
13973     fclose(f);
13974     return TRUE;
13975 }
13976
13977 void
13978 ReloadCmailMsgEvent (int unregister)
13979 {
13980 #if !WIN32
13981     static char *inFilename = NULL;
13982     static char *outFilename;
13983     int i;
13984     struct stat inbuf, outbuf;
13985     int status;
13986
13987     /* Any registered moves are unregistered if unregister is set, */
13988     /* i.e. invoked by the signal handler */
13989     if (unregister) {
13990         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13991             cmailMoveRegistered[i] = FALSE;
13992             if (cmailCommentList[i] != NULL) {
13993                 free(cmailCommentList[i]);
13994                 cmailCommentList[i] = NULL;
13995             }
13996         }
13997         nCmailMovesRegistered = 0;
13998     }
13999
14000     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14001         cmailResult[i] = CMAIL_NOT_RESULT;
14002     }
14003     nCmailResults = 0;
14004
14005     if (inFilename == NULL) {
14006         /* Because the filenames are static they only get malloced once  */
14007         /* and they never get freed                                      */
14008         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14009         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14010
14011         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14012         sprintf(outFilename, "%s.out", appData.cmailGameName);
14013     }
14014
14015     status = stat(outFilename, &outbuf);
14016     if (status < 0) {
14017         cmailMailedMove = FALSE;
14018     } else {
14019         status = stat(inFilename, &inbuf);
14020         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14021     }
14022
14023     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14024        counts the games, notes how each one terminated, etc.
14025
14026        It would be nice to remove this kludge and instead gather all
14027        the information while building the game list.  (And to keep it
14028        in the game list nodes instead of having a bunch of fixed-size
14029        parallel arrays.)  Note this will require getting each game's
14030        termination from the PGN tags, as the game list builder does
14031        not process the game moves.  --mann
14032        */
14033     cmailMsgLoaded = TRUE;
14034     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14035
14036     /* Load first game in the file or popup game menu */
14037     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14038
14039 #endif /* !WIN32 */
14040     return;
14041 }
14042
14043 int
14044 RegisterMove ()
14045 {
14046     FILE *f;
14047     char string[MSG_SIZ];
14048
14049     if (   cmailMailedMove
14050         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14051         return TRUE;            /* Allow free viewing  */
14052     }
14053
14054     /* Unregister move to ensure that we don't leave RegisterMove        */
14055     /* with the move registered when the conditions for registering no   */
14056     /* longer hold                                                       */
14057     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14058         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14059         nCmailMovesRegistered --;
14060
14061         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14062           {
14063               free(cmailCommentList[lastLoadGameNumber - 1]);
14064               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14065           }
14066     }
14067
14068     if (cmailOldMove == -1) {
14069         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14070         return FALSE;
14071     }
14072
14073     if (currentMove > cmailOldMove + 1) {
14074         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14075         return FALSE;
14076     }
14077
14078     if (currentMove < cmailOldMove) {
14079         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14080         return FALSE;
14081     }
14082
14083     if (forwardMostMove > currentMove) {
14084         /* Silently truncate extra moves */
14085         TruncateGame();
14086     }
14087
14088     if (   (currentMove == cmailOldMove + 1)
14089         || (   (currentMove == cmailOldMove)
14090             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14091                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14092         if (gameInfo.result != GameUnfinished) {
14093             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14094         }
14095
14096         if (commentList[currentMove] != NULL) {
14097             cmailCommentList[lastLoadGameNumber - 1]
14098               = StrSave(commentList[currentMove]);
14099         }
14100         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14101
14102         if (appData.debugMode)
14103           fprintf(debugFP, "Saving %s for game %d\n",
14104                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14105
14106         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14107
14108         f = fopen(string, "w");
14109         if (appData.oldSaveStyle) {
14110             SaveGameOldStyle(f); /* also closes the file */
14111
14112             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14113             f = fopen(string, "w");
14114             SavePosition(f, 0, NULL); /* also closes the file */
14115         } else {
14116             fprintf(f, "{--------------\n");
14117             PrintPosition(f, currentMove);
14118             fprintf(f, "--------------}\n\n");
14119
14120             SaveGame(f, 0, NULL); /* also closes the file*/
14121         }
14122
14123         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14124         nCmailMovesRegistered ++;
14125     } else if (nCmailGames == 1) {
14126         DisplayError(_("You have not made a move yet"), 0);
14127         return FALSE;
14128     }
14129
14130     return TRUE;
14131 }
14132
14133 void
14134 MailMoveEvent ()
14135 {
14136 #if !WIN32
14137     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14138     FILE *commandOutput;
14139     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14140     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14141     int nBuffers;
14142     int i;
14143     int archived;
14144     char *arcDir;
14145
14146     if (! cmailMsgLoaded) {
14147         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14148         return;
14149     }
14150
14151     if (nCmailGames == nCmailResults) {
14152         DisplayError(_("No unfinished games"), 0);
14153         return;
14154     }
14155
14156 #if CMAIL_PROHIBIT_REMAIL
14157     if (cmailMailedMove) {
14158       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);
14159         DisplayError(msg, 0);
14160         return;
14161     }
14162 #endif
14163
14164     if (! (cmailMailedMove || RegisterMove())) return;
14165
14166     if (   cmailMailedMove
14167         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14168       snprintf(string, MSG_SIZ, partCommandString,
14169                appData.debugMode ? " -v" : "", appData.cmailGameName);
14170         commandOutput = popen(string, "r");
14171
14172         if (commandOutput == NULL) {
14173             DisplayError(_("Failed to invoke cmail"), 0);
14174         } else {
14175             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14176                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14177             }
14178             if (nBuffers > 1) {
14179                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14180                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14181                 nBytes = MSG_SIZ - 1;
14182             } else {
14183                 (void) memcpy(msg, buffer, nBytes);
14184             }
14185             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14186
14187             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14188                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14189
14190                 archived = TRUE;
14191                 for (i = 0; i < nCmailGames; i ++) {
14192                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14193                         archived = FALSE;
14194                     }
14195                 }
14196                 if (   archived
14197                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14198                         != NULL)) {
14199                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14200                            arcDir,
14201                            appData.cmailGameName,
14202                            gameInfo.date);
14203                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14204                     cmailMsgLoaded = FALSE;
14205                 }
14206             }
14207
14208             DisplayInformation(msg);
14209             pclose(commandOutput);
14210         }
14211     } else {
14212         if ((*cmailMsg) != '\0') {
14213             DisplayInformation(cmailMsg);
14214         }
14215     }
14216
14217     return;
14218 #endif /* !WIN32 */
14219 }
14220
14221 char *
14222 CmailMsg ()
14223 {
14224 #if WIN32
14225     return NULL;
14226 #else
14227     int  prependComma = 0;
14228     char number[5];
14229     char string[MSG_SIZ];       /* Space for game-list */
14230     int  i;
14231
14232     if (!cmailMsgLoaded) return "";
14233
14234     if (cmailMailedMove) {
14235       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14236     } else {
14237         /* Create a list of games left */
14238       snprintf(string, MSG_SIZ, "[");
14239         for (i = 0; i < nCmailGames; i ++) {
14240             if (! (   cmailMoveRegistered[i]
14241                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14242                 if (prependComma) {
14243                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14244                 } else {
14245                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14246                     prependComma = 1;
14247                 }
14248
14249                 strcat(string, number);
14250             }
14251         }
14252         strcat(string, "]");
14253
14254         if (nCmailMovesRegistered + nCmailResults == 0) {
14255             switch (nCmailGames) {
14256               case 1:
14257                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14258                 break;
14259
14260               case 2:
14261                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14262                 break;
14263
14264               default:
14265                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14266                          nCmailGames);
14267                 break;
14268             }
14269         } else {
14270             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14271               case 1:
14272                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14273                          string);
14274                 break;
14275
14276               case 0:
14277                 if (nCmailResults == nCmailGames) {
14278                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14279                 } else {
14280                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14281                 }
14282                 break;
14283
14284               default:
14285                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14286                          string);
14287             }
14288         }
14289     }
14290     return cmailMsg;
14291 #endif /* WIN32 */
14292 }
14293
14294 void
14295 ResetGameEvent ()
14296 {
14297     if (gameMode == Training)
14298       SetTrainingModeOff();
14299
14300     Reset(TRUE, TRUE);
14301     cmailMsgLoaded = FALSE;
14302     if (appData.icsActive) {
14303       SendToICS(ics_prefix);
14304       SendToICS("refresh\n");
14305     }
14306 }
14307
14308 void
14309 ExitEvent (int status)
14310 {
14311     exiting++;
14312     if (exiting > 2) {
14313       /* Give up on clean exit */
14314       exit(status);
14315     }
14316     if (exiting > 1) {
14317       /* Keep trying for clean exit */
14318       return;
14319     }
14320
14321     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14322     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14323
14324     if (telnetISR != NULL) {
14325       RemoveInputSource(telnetISR);
14326     }
14327     if (icsPR != NoProc) {
14328       DestroyChildProcess(icsPR, TRUE);
14329     }
14330
14331     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14332     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14333
14334     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14335     /* make sure this other one finishes before killing it!                  */
14336     if(endingGame) { int count = 0;
14337         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14338         while(endingGame && count++ < 10) DoSleep(1);
14339         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14340     }
14341
14342     /* Kill off chess programs */
14343     if (first.pr != NoProc) {
14344         ExitAnalyzeMode();
14345
14346         DoSleep( appData.delayBeforeQuit );
14347         SendToProgram("quit\n", &first);
14348         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14349     }
14350     if (second.pr != NoProc) {
14351         DoSleep( appData.delayBeforeQuit );
14352         SendToProgram("quit\n", &second);
14353         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14354     }
14355     if (first.isr != NULL) {
14356         RemoveInputSource(first.isr);
14357     }
14358     if (second.isr != NULL) {
14359         RemoveInputSource(second.isr);
14360     }
14361
14362     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14363     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14364
14365     ShutDownFrontEnd();
14366     exit(status);
14367 }
14368
14369 void
14370 PauseEngine (ChessProgramState *cps)
14371 {
14372     SendToProgram("pause\n", cps);
14373     cps->pause = 2;
14374 }
14375
14376 void
14377 UnPauseEngine (ChessProgramState *cps)
14378 {
14379     SendToProgram("resume\n", cps);
14380     cps->pause = 1;
14381 }
14382
14383 void
14384 PauseEvent ()
14385 {
14386     if (appData.debugMode)
14387         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14388     if (pausing) {
14389         pausing = FALSE;
14390         ModeHighlight();
14391         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14392             StartClocks();
14393             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14394                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14395                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14396             }
14397             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14398             HandleMachineMove(stashedInputMove, stalledEngine);
14399             stalledEngine = NULL;
14400             return;
14401         }
14402         if (gameMode == MachinePlaysWhite ||
14403             gameMode == TwoMachinesPlay   ||
14404             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14405             if(first.pause)  UnPauseEngine(&first);
14406             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14407             if(second.pause) UnPauseEngine(&second);
14408             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14409             StartClocks();
14410         } else {
14411             DisplayBothClocks();
14412         }
14413         if (gameMode == PlayFromGameFile) {
14414             if (appData.timeDelay >= 0)
14415                 AutoPlayGameLoop();
14416         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14417             Reset(FALSE, TRUE);
14418             SendToICS(ics_prefix);
14419             SendToICS("refresh\n");
14420         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14421             ForwardInner(forwardMostMove);
14422         }
14423         pauseExamInvalid = FALSE;
14424     } else {
14425         switch (gameMode) {
14426           default:
14427             return;
14428           case IcsExamining:
14429             pauseExamForwardMostMove = forwardMostMove;
14430             pauseExamInvalid = FALSE;
14431             /* fall through */
14432           case IcsObserving:
14433           case IcsPlayingWhite:
14434           case IcsPlayingBlack:
14435             pausing = TRUE;
14436             ModeHighlight();
14437             return;
14438           case PlayFromGameFile:
14439             (void) StopLoadGameTimer();
14440             pausing = TRUE;
14441             ModeHighlight();
14442             break;
14443           case BeginningOfGame:
14444             if (appData.icsActive) return;
14445             /* else fall through */
14446           case MachinePlaysWhite:
14447           case MachinePlaysBlack:
14448           case TwoMachinesPlay:
14449             if (forwardMostMove == 0)
14450               return;           /* don't pause if no one has moved */
14451             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14452                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14453                 if(onMove->pause) {           // thinking engine can be paused
14454                     PauseEngine(onMove);      // do it
14455                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14456                         PauseEngine(onMove->other);
14457                     else
14458                         SendToProgram("easy\n", onMove->other);
14459                     StopClocks();
14460                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14461             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14462                 if(first.pause) {
14463                     PauseEngine(&first);
14464                     StopClocks();
14465                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14466             } else { // human on move, pause pondering by either method
14467                 if(first.pause)
14468                     PauseEngine(&first);
14469                 else if(appData.ponderNextMove)
14470                     SendToProgram("easy\n", &first);
14471                 StopClocks();
14472             }
14473             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14474           case AnalyzeMode:
14475             pausing = TRUE;
14476             ModeHighlight();
14477             break;
14478         }
14479     }
14480 }
14481
14482 void
14483 EditCommentEvent ()
14484 {
14485     char title[MSG_SIZ];
14486
14487     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14488       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14489     } else {
14490       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14491                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14492                parseList[currentMove - 1]);
14493     }
14494
14495     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14496 }
14497
14498
14499 void
14500 EditTagsEvent ()
14501 {
14502     char *tags = PGNTags(&gameInfo);
14503     bookUp = FALSE;
14504     EditTagsPopUp(tags, NULL);
14505     free(tags);
14506 }
14507
14508 void
14509 ToggleSecond ()
14510 {
14511   if(second.analyzing) {
14512     SendToProgram("exit\n", &second);
14513     second.analyzing = FALSE;
14514   } else {
14515     if (second.pr == NoProc) StartChessProgram(&second);
14516     InitChessProgram(&second, FALSE);
14517     FeedMovesToProgram(&second, currentMove);
14518
14519     SendToProgram("analyze\n", &second);
14520     second.analyzing = TRUE;
14521   }
14522 }
14523
14524 /* Toggle ShowThinking */
14525 void
14526 ToggleShowThinking()
14527 {
14528   appData.showThinking = !appData.showThinking;
14529   ShowThinkingEvent();
14530 }
14531
14532 int
14533 AnalyzeModeEvent ()
14534 {
14535     char buf[MSG_SIZ];
14536
14537     if (!first.analysisSupport) {
14538       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14539       DisplayError(buf, 0);
14540       return 0;
14541     }
14542     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14543     if (appData.icsActive) {
14544         if (gameMode != IcsObserving) {
14545           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14546             DisplayError(buf, 0);
14547             /* secure check */
14548             if (appData.icsEngineAnalyze) {
14549                 if (appData.debugMode)
14550                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14551                 ExitAnalyzeMode();
14552                 ModeHighlight();
14553             }
14554             return 0;
14555         }
14556         /* if enable, user wants to disable icsEngineAnalyze */
14557         if (appData.icsEngineAnalyze) {
14558                 ExitAnalyzeMode();
14559                 ModeHighlight();
14560                 return 0;
14561         }
14562         appData.icsEngineAnalyze = TRUE;
14563         if (appData.debugMode)
14564             fprintf(debugFP, "ICS engine analyze starting... \n");
14565     }
14566
14567     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14568     if (appData.noChessProgram || gameMode == AnalyzeMode)
14569       return 0;
14570
14571     if (gameMode != AnalyzeFile) {
14572         if (!appData.icsEngineAnalyze) {
14573                EditGameEvent();
14574                if (gameMode != EditGame) return 0;
14575         }
14576         if (!appData.showThinking) ToggleShowThinking();
14577         ResurrectChessProgram();
14578         SendToProgram("analyze\n", &first);
14579         first.analyzing = TRUE;
14580         /*first.maybeThinking = TRUE;*/
14581         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14582         EngineOutputPopUp();
14583     }
14584     if (!appData.icsEngineAnalyze) {
14585         gameMode = AnalyzeMode;
14586         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14587     }
14588     pausing = FALSE;
14589     ModeHighlight();
14590     SetGameInfo();
14591
14592     StartAnalysisClock();
14593     GetTimeMark(&lastNodeCountTime);
14594     lastNodeCount = 0;
14595     return 1;
14596 }
14597
14598 void
14599 AnalyzeFileEvent ()
14600 {
14601     if (appData.noChessProgram || gameMode == AnalyzeFile)
14602       return;
14603
14604     if (!first.analysisSupport) {
14605       char buf[MSG_SIZ];
14606       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14607       DisplayError(buf, 0);
14608       return;
14609     }
14610
14611     if (gameMode != AnalyzeMode) {
14612         keepInfo = 1; // mere annotating should not alter PGN tags
14613         EditGameEvent();
14614         keepInfo = 0;
14615         if (gameMode != EditGame) return;
14616         if (!appData.showThinking) ToggleShowThinking();
14617         ResurrectChessProgram();
14618         SendToProgram("analyze\n", &first);
14619         first.analyzing = TRUE;
14620         /*first.maybeThinking = TRUE;*/
14621         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14622         EngineOutputPopUp();
14623     }
14624     gameMode = AnalyzeFile;
14625     pausing = FALSE;
14626     ModeHighlight();
14627
14628     StartAnalysisClock();
14629     GetTimeMark(&lastNodeCountTime);
14630     lastNodeCount = 0;
14631     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14632     AnalysisPeriodicEvent(1);
14633 }
14634
14635 void
14636 MachineWhiteEvent ()
14637 {
14638     char buf[MSG_SIZ];
14639     char *bookHit = NULL;
14640
14641     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14642       return;
14643
14644
14645     if (gameMode == PlayFromGameFile ||
14646         gameMode == TwoMachinesPlay  ||
14647         gameMode == Training         ||
14648         gameMode == AnalyzeMode      ||
14649         gameMode == EndOfGame)
14650         EditGameEvent();
14651
14652     if (gameMode == EditPosition)
14653         EditPositionDone(TRUE);
14654
14655     if (!WhiteOnMove(currentMove)) {
14656         DisplayError(_("It is not White's turn"), 0);
14657         return;
14658     }
14659
14660     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14661       ExitAnalyzeMode();
14662
14663     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14664         gameMode == AnalyzeFile)
14665         TruncateGame();
14666
14667     ResurrectChessProgram();    /* in case it isn't running */
14668     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14669         gameMode = MachinePlaysWhite;
14670         ResetClocks();
14671     } else
14672     gameMode = MachinePlaysWhite;
14673     pausing = FALSE;
14674     ModeHighlight();
14675     SetGameInfo();
14676     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14677     DisplayTitle(buf);
14678     if (first.sendName) {
14679       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14680       SendToProgram(buf, &first);
14681     }
14682     if (first.sendTime) {
14683       if (first.useColors) {
14684         SendToProgram("black\n", &first); /*gnu kludge*/
14685       }
14686       SendTimeRemaining(&first, TRUE);
14687     }
14688     if (first.useColors) {
14689       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14690     }
14691     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14692     SetMachineThinkingEnables();
14693     first.maybeThinking = TRUE;
14694     StartClocks();
14695     firstMove = FALSE;
14696
14697     if (appData.autoFlipView && !flipView) {
14698       flipView = !flipView;
14699       DrawPosition(FALSE, NULL);
14700       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14701     }
14702
14703     if(bookHit) { // [HGM] book: simulate book reply
14704         static char bookMove[MSG_SIZ]; // a bit generous?
14705
14706         programStats.nodes = programStats.depth = programStats.time =
14707         programStats.score = programStats.got_only_move = 0;
14708         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14709
14710         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14711         strcat(bookMove, bookHit);
14712         HandleMachineMove(bookMove, &first);
14713     }
14714 }
14715
14716 void
14717 MachineBlackEvent ()
14718 {
14719   char buf[MSG_SIZ];
14720   char *bookHit = NULL;
14721
14722     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14723         return;
14724
14725
14726     if (gameMode == PlayFromGameFile ||
14727         gameMode == TwoMachinesPlay  ||
14728         gameMode == Training         ||
14729         gameMode == AnalyzeMode      ||
14730         gameMode == EndOfGame)
14731         EditGameEvent();
14732
14733     if (gameMode == EditPosition)
14734         EditPositionDone(TRUE);
14735
14736     if (WhiteOnMove(currentMove)) {
14737         DisplayError(_("It is not Black's turn"), 0);
14738         return;
14739     }
14740
14741     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14742       ExitAnalyzeMode();
14743
14744     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14745         gameMode == AnalyzeFile)
14746         TruncateGame();
14747
14748     ResurrectChessProgram();    /* in case it isn't running */
14749     gameMode = MachinePlaysBlack;
14750     pausing = FALSE;
14751     ModeHighlight();
14752     SetGameInfo();
14753     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14754     DisplayTitle(buf);
14755     if (first.sendName) {
14756       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14757       SendToProgram(buf, &first);
14758     }
14759     if (first.sendTime) {
14760       if (first.useColors) {
14761         SendToProgram("white\n", &first); /*gnu kludge*/
14762       }
14763       SendTimeRemaining(&first, FALSE);
14764     }
14765     if (first.useColors) {
14766       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14767     }
14768     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14769     SetMachineThinkingEnables();
14770     first.maybeThinking = TRUE;
14771     StartClocks();
14772
14773     if (appData.autoFlipView && flipView) {
14774       flipView = !flipView;
14775       DrawPosition(FALSE, NULL);
14776       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14777     }
14778     if(bookHit) { // [HGM] book: simulate book reply
14779         static char bookMove[MSG_SIZ]; // a bit generous?
14780
14781         programStats.nodes = programStats.depth = programStats.time =
14782         programStats.score = programStats.got_only_move = 0;
14783         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14784
14785         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14786         strcat(bookMove, bookHit);
14787         HandleMachineMove(bookMove, &first);
14788     }
14789 }
14790
14791
14792 void
14793 DisplayTwoMachinesTitle ()
14794 {
14795     char buf[MSG_SIZ];
14796     if (appData.matchGames > 0) {
14797         if(appData.tourneyFile[0]) {
14798           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14799                    gameInfo.white, _("vs."), gameInfo.black,
14800                    nextGame+1, appData.matchGames+1,
14801                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14802         } else
14803         if (first.twoMachinesColor[0] == 'w') {
14804           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14805                    gameInfo.white, _("vs."),  gameInfo.black,
14806                    first.matchWins, second.matchWins,
14807                    matchGame - 1 - (first.matchWins + second.matchWins));
14808         } else {
14809           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14810                    gameInfo.white, _("vs."), gameInfo.black,
14811                    second.matchWins, first.matchWins,
14812                    matchGame - 1 - (first.matchWins + second.matchWins));
14813         }
14814     } else {
14815       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14816     }
14817     DisplayTitle(buf);
14818 }
14819
14820 void
14821 SettingsMenuIfReady ()
14822 {
14823   if (second.lastPing != second.lastPong) {
14824     DisplayMessage("", _("Waiting for second chess program"));
14825     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14826     return;
14827   }
14828   ThawUI();
14829   DisplayMessage("", "");
14830   SettingsPopUp(&second);
14831 }
14832
14833 int
14834 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14835 {
14836     char buf[MSG_SIZ];
14837     if (cps->pr == NoProc) {
14838         StartChessProgram(cps);
14839         if (cps->protocolVersion == 1) {
14840           retry();
14841           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14842         } else {
14843           /* kludge: allow timeout for initial "feature" command */
14844           if(retry != TwoMachinesEventIfReady) FreezeUI();
14845           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14846           DisplayMessage("", buf);
14847           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14848         }
14849         return 1;
14850     }
14851     return 0;
14852 }
14853
14854 void
14855 TwoMachinesEvent P((void))
14856 {
14857     int i;
14858     char buf[MSG_SIZ];
14859     ChessProgramState *onmove;
14860     char *bookHit = NULL;
14861     static int stalling = 0;
14862     TimeMark now;
14863     long wait;
14864
14865     if (appData.noChessProgram) return;
14866
14867     switch (gameMode) {
14868       case TwoMachinesPlay:
14869         return;
14870       case MachinePlaysWhite:
14871       case MachinePlaysBlack:
14872         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14873             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14874             return;
14875         }
14876         /* fall through */
14877       case BeginningOfGame:
14878       case PlayFromGameFile:
14879       case EndOfGame:
14880         EditGameEvent();
14881         if (gameMode != EditGame) return;
14882         break;
14883       case EditPosition:
14884         EditPositionDone(TRUE);
14885         break;
14886       case AnalyzeMode:
14887       case AnalyzeFile:
14888         ExitAnalyzeMode();
14889         break;
14890       case EditGame:
14891       default:
14892         break;
14893     }
14894
14895 //    forwardMostMove = currentMove;
14896     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14897     startingEngine = TRUE;
14898
14899     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14900
14901     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14902     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14903       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14904       return;
14905     }
14906     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14907
14908     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14909                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14910         startingEngine = matchMode = FALSE;
14911         DisplayError("second engine does not play this", 0);
14912         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14913         EditGameEvent(); // switch back to EditGame mode
14914         return;
14915     }
14916
14917     if(!stalling) {
14918       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14919       SendToProgram("force\n", &second);
14920       stalling = 1;
14921       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14922       return;
14923     }
14924     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14925     if(appData.matchPause>10000 || appData.matchPause<10)
14926                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14927     wait = SubtractTimeMarks(&now, &pauseStart);
14928     if(wait < appData.matchPause) {
14929         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14930         return;
14931     }
14932     // we are now committed to starting the game
14933     stalling = 0;
14934     DisplayMessage("", "");
14935     if (startedFromSetupPosition) {
14936         SendBoard(&second, backwardMostMove);
14937     if (appData.debugMode) {
14938         fprintf(debugFP, "Two Machines\n");
14939     }
14940     }
14941     for (i = backwardMostMove; i < forwardMostMove; i++) {
14942         SendMoveToProgram(i, &second);
14943     }
14944
14945     gameMode = TwoMachinesPlay;
14946     pausing = startingEngine = FALSE;
14947     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14948     SetGameInfo();
14949     DisplayTwoMachinesTitle();
14950     firstMove = TRUE;
14951     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14952         onmove = &first;
14953     } else {
14954         onmove = &second;
14955     }
14956     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14957     SendToProgram(first.computerString, &first);
14958     if (first.sendName) {
14959       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14960       SendToProgram(buf, &first);
14961     }
14962     SendToProgram(second.computerString, &second);
14963     if (second.sendName) {
14964       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14965       SendToProgram(buf, &second);
14966     }
14967
14968     ResetClocks();
14969     if (!first.sendTime || !second.sendTime) {
14970         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14971         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14972     }
14973     if (onmove->sendTime) {
14974       if (onmove->useColors) {
14975         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14976       }
14977       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14978     }
14979     if (onmove->useColors) {
14980       SendToProgram(onmove->twoMachinesColor, onmove);
14981     }
14982     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14983 //    SendToProgram("go\n", onmove);
14984     onmove->maybeThinking = TRUE;
14985     SetMachineThinkingEnables();
14986
14987     StartClocks();
14988
14989     if(bookHit) { // [HGM] book: simulate book reply
14990         static char bookMove[MSG_SIZ]; // a bit generous?
14991
14992         programStats.nodes = programStats.depth = programStats.time =
14993         programStats.score = programStats.got_only_move = 0;
14994         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14995
14996         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14997         strcat(bookMove, bookHit);
14998         savedMessage = bookMove; // args for deferred call
14999         savedState = onmove;
15000         ScheduleDelayedEvent(DeferredBookMove, 1);
15001     }
15002 }
15003
15004 void
15005 TrainingEvent ()
15006 {
15007     if (gameMode == Training) {
15008       SetTrainingModeOff();
15009       gameMode = PlayFromGameFile;
15010       DisplayMessage("", _("Training mode off"));
15011     } else {
15012       gameMode = Training;
15013       animateTraining = appData.animate;
15014
15015       /* make sure we are not already at the end of the game */
15016       if (currentMove < forwardMostMove) {
15017         SetTrainingModeOn();
15018         DisplayMessage("", _("Training mode on"));
15019       } else {
15020         gameMode = PlayFromGameFile;
15021         DisplayError(_("Already at end of game"), 0);
15022       }
15023     }
15024     ModeHighlight();
15025 }
15026
15027 void
15028 IcsClientEvent ()
15029 {
15030     if (!appData.icsActive) return;
15031     switch (gameMode) {
15032       case IcsPlayingWhite:
15033       case IcsPlayingBlack:
15034       case IcsObserving:
15035       case IcsIdle:
15036       case BeginningOfGame:
15037       case IcsExamining:
15038         return;
15039
15040       case EditGame:
15041         break;
15042
15043       case EditPosition:
15044         EditPositionDone(TRUE);
15045         break;
15046
15047       case AnalyzeMode:
15048       case AnalyzeFile:
15049         ExitAnalyzeMode();
15050         break;
15051
15052       default:
15053         EditGameEvent();
15054         break;
15055     }
15056
15057     gameMode = IcsIdle;
15058     ModeHighlight();
15059     return;
15060 }
15061
15062 void
15063 EditGameEvent ()
15064 {
15065     int i;
15066
15067     switch (gameMode) {
15068       case Training:
15069         SetTrainingModeOff();
15070         break;
15071       case MachinePlaysWhite:
15072       case MachinePlaysBlack:
15073       case BeginningOfGame:
15074         SendToProgram("force\n", &first);
15075         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15076             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15077                 char buf[MSG_SIZ];
15078                 abortEngineThink = TRUE;
15079                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15080                 SendToProgram(buf, &first);
15081                 DisplayMessage("Aborting engine think", "");
15082                 FreezeUI();
15083             }
15084         }
15085         SetUserThinkingEnables();
15086         break;
15087       case PlayFromGameFile:
15088         (void) StopLoadGameTimer();
15089         if (gameFileFP != NULL) {
15090             gameFileFP = NULL;
15091         }
15092         break;
15093       case EditPosition:
15094         EditPositionDone(TRUE);
15095         break;
15096       case AnalyzeMode:
15097       case AnalyzeFile:
15098         ExitAnalyzeMode();
15099         SendToProgram("force\n", &first);
15100         break;
15101       case TwoMachinesPlay:
15102         GameEnds(EndOfFile, NULL, GE_PLAYER);
15103         ResurrectChessProgram();
15104         SetUserThinkingEnables();
15105         break;
15106       case EndOfGame:
15107         ResurrectChessProgram();
15108         break;
15109       case IcsPlayingBlack:
15110       case IcsPlayingWhite:
15111         DisplayError(_("Warning: You are still playing a game"), 0);
15112         break;
15113       case IcsObserving:
15114         DisplayError(_("Warning: You are still observing a game"), 0);
15115         break;
15116       case IcsExamining:
15117         DisplayError(_("Warning: You are still examining a game"), 0);
15118         break;
15119       case IcsIdle:
15120         break;
15121       case EditGame:
15122       default:
15123         return;
15124     }
15125
15126     pausing = FALSE;
15127     StopClocks();
15128     first.offeredDraw = second.offeredDraw = 0;
15129
15130     if (gameMode == PlayFromGameFile) {
15131         whiteTimeRemaining = timeRemaining[0][currentMove];
15132         blackTimeRemaining = timeRemaining[1][currentMove];
15133         DisplayTitle("");
15134     }
15135
15136     if (gameMode == MachinePlaysWhite ||
15137         gameMode == MachinePlaysBlack ||
15138         gameMode == TwoMachinesPlay ||
15139         gameMode == EndOfGame) {
15140         i = forwardMostMove;
15141         while (i > currentMove) {
15142             SendToProgram("undo\n", &first);
15143             i--;
15144         }
15145         if(!adjustedClock) {
15146         whiteTimeRemaining = timeRemaining[0][currentMove];
15147         blackTimeRemaining = timeRemaining[1][currentMove];
15148         DisplayBothClocks();
15149         }
15150         if (whiteFlag || blackFlag) {
15151             whiteFlag = blackFlag = 0;
15152         }
15153         DisplayTitle("");
15154     }
15155
15156     gameMode = EditGame;
15157     ModeHighlight();
15158     SetGameInfo();
15159 }
15160
15161
15162 void
15163 EditPositionEvent ()
15164 {
15165     if (gameMode == EditPosition) {
15166         EditGameEvent();
15167         return;
15168     }
15169
15170     EditGameEvent();
15171     if (gameMode != EditGame) return;
15172
15173     gameMode = EditPosition;
15174     ModeHighlight();
15175     SetGameInfo();
15176     if (currentMove > 0)
15177       CopyBoard(boards[0], boards[currentMove]);
15178
15179     blackPlaysFirst = !WhiteOnMove(currentMove);
15180     ResetClocks();
15181     currentMove = forwardMostMove = backwardMostMove = 0;
15182     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15183     DisplayMove(-1);
15184     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15185 }
15186
15187 void
15188 ExitAnalyzeMode ()
15189 {
15190     /* [DM] icsEngineAnalyze - possible call from other functions */
15191     if (appData.icsEngineAnalyze) {
15192         appData.icsEngineAnalyze = FALSE;
15193
15194         DisplayMessage("",_("Close ICS engine analyze..."));
15195     }
15196     if (first.analysisSupport && first.analyzing) {
15197       SendToBoth("exit\n");
15198       first.analyzing = second.analyzing = FALSE;
15199     }
15200     thinkOutput[0] = NULLCHAR;
15201 }
15202
15203 void
15204 EditPositionDone (Boolean fakeRights)
15205 {
15206     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15207
15208     startedFromSetupPosition = TRUE;
15209     InitChessProgram(&first, FALSE);
15210     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15211       boards[0][EP_STATUS] = EP_NONE;
15212       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15213       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15214         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15215         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15216       } else boards[0][CASTLING][2] = NoRights;
15217       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15218         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15219         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15220       } else boards[0][CASTLING][5] = NoRights;
15221       if(gameInfo.variant == VariantSChess) {
15222         int i;
15223         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15224           boards[0][VIRGIN][i] = 0;
15225           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15226           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15227         }
15228       }
15229     }
15230     SendToProgram("force\n", &first);
15231     if (blackPlaysFirst) {
15232         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15233         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15234         currentMove = forwardMostMove = backwardMostMove = 1;
15235         CopyBoard(boards[1], boards[0]);
15236     } else {
15237         currentMove = forwardMostMove = backwardMostMove = 0;
15238     }
15239     SendBoard(&first, forwardMostMove);
15240     if (appData.debugMode) {
15241         fprintf(debugFP, "EditPosDone\n");
15242     }
15243     DisplayTitle("");
15244     DisplayMessage("", "");
15245     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15246     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15247     gameMode = EditGame;
15248     ModeHighlight();
15249     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15250     ClearHighlights(); /* [AS] */
15251 }
15252
15253 /* Pause for `ms' milliseconds */
15254 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15255 void
15256 TimeDelay (long ms)
15257 {
15258     TimeMark m1, m2;
15259
15260     GetTimeMark(&m1);
15261     do {
15262         GetTimeMark(&m2);
15263     } while (SubtractTimeMarks(&m2, &m1) < ms);
15264 }
15265
15266 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15267 void
15268 SendMultiLineToICS (char *buf)
15269 {
15270     char temp[MSG_SIZ+1], *p;
15271     int len;
15272
15273     len = strlen(buf);
15274     if (len > MSG_SIZ)
15275       len = MSG_SIZ;
15276
15277     strncpy(temp, buf, len);
15278     temp[len] = 0;
15279
15280     p = temp;
15281     while (*p) {
15282         if (*p == '\n' || *p == '\r')
15283           *p = ' ';
15284         ++p;
15285     }
15286
15287     strcat(temp, "\n");
15288     SendToICS(temp);
15289     SendToPlayer(temp, strlen(temp));
15290 }
15291
15292 void
15293 SetWhiteToPlayEvent ()
15294 {
15295     if (gameMode == EditPosition) {
15296         blackPlaysFirst = FALSE;
15297         DisplayBothClocks();    /* works because currentMove is 0 */
15298     } else if (gameMode == IcsExamining) {
15299         SendToICS(ics_prefix);
15300         SendToICS("tomove white\n");
15301     }
15302 }
15303
15304 void
15305 SetBlackToPlayEvent ()
15306 {
15307     if (gameMode == EditPosition) {
15308         blackPlaysFirst = TRUE;
15309         currentMove = 1;        /* kludge */
15310         DisplayBothClocks();
15311         currentMove = 0;
15312     } else if (gameMode == IcsExamining) {
15313         SendToICS(ics_prefix);
15314         SendToICS("tomove black\n");
15315     }
15316 }
15317
15318 void
15319 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15320 {
15321     char buf[MSG_SIZ];
15322     ChessSquare piece = boards[0][y][x];
15323     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15324     static int lastVariant;
15325
15326     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15327
15328     switch (selection) {
15329       case ClearBoard:
15330         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15331         MarkTargetSquares(1);
15332         CopyBoard(currentBoard, boards[0]);
15333         CopyBoard(menuBoard, initialPosition);
15334         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15335             SendToICS(ics_prefix);
15336             SendToICS("bsetup clear\n");
15337         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15338             SendToICS(ics_prefix);
15339             SendToICS("clearboard\n");
15340         } else {
15341             int nonEmpty = 0;
15342             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15343                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15344                 for (y = 0; y < BOARD_HEIGHT; y++) {
15345                     if (gameMode == IcsExamining) {
15346                         if (boards[currentMove][y][x] != EmptySquare) {
15347                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15348                                     AAA + x, ONE + y);
15349                             SendToICS(buf);
15350                         }
15351                     } else if(boards[0][y][x] != DarkSquare) {
15352                         if(boards[0][y][x] != p) nonEmpty++;
15353                         boards[0][y][x] = p;
15354                     }
15355                 }
15356             }
15357             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15358                 int r;
15359                 for(r = 0; r < BOARD_HEIGHT; r++) {
15360                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15361                     ChessSquare p = menuBoard[r][x];
15362                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15363                   }
15364                 }
15365                 DisplayMessage("Clicking clock again restores position", "");
15366                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15367                 if(!nonEmpty) { // asked to clear an empty board
15368                     CopyBoard(boards[0], menuBoard);
15369                 } else
15370                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15371                     CopyBoard(boards[0], initialPosition);
15372                 } else
15373                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15374                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15375                     CopyBoard(boards[0], erasedBoard);
15376                 } else
15377                     CopyBoard(erasedBoard, currentBoard);
15378
15379             }
15380         }
15381         if (gameMode == EditPosition) {
15382             DrawPosition(FALSE, boards[0]);
15383         }
15384         break;
15385
15386       case WhitePlay:
15387         SetWhiteToPlayEvent();
15388         break;
15389
15390       case BlackPlay:
15391         SetBlackToPlayEvent();
15392         break;
15393
15394       case EmptySquare:
15395         if (gameMode == IcsExamining) {
15396             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15397             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15398             SendToICS(buf);
15399         } else {
15400             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15401                 if(x == BOARD_LEFT-2) {
15402                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15403                     boards[0][y][1] = 0;
15404                 } else
15405                 if(x == BOARD_RGHT+1) {
15406                     if(y >= gameInfo.holdingsSize) break;
15407                     boards[0][y][BOARD_WIDTH-2] = 0;
15408                 } else break;
15409             }
15410             boards[0][y][x] = EmptySquare;
15411             DrawPosition(FALSE, boards[0]);
15412         }
15413         break;
15414
15415       case PromotePiece:
15416         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15417            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15418             selection = (ChessSquare) (PROMOTED(piece));
15419         } else if(piece == EmptySquare) selection = WhiteSilver;
15420         else selection = (ChessSquare)((int)piece - 1);
15421         goto defaultlabel;
15422
15423       case DemotePiece:
15424         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15425            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15426             selection = (ChessSquare) (DEMOTED(piece));
15427         } else if(piece == EmptySquare) selection = BlackSilver;
15428         else selection = (ChessSquare)((int)piece + 1);
15429         goto defaultlabel;
15430
15431       case WhiteQueen:
15432       case BlackQueen:
15433         if(gameInfo.variant == VariantShatranj ||
15434            gameInfo.variant == VariantXiangqi  ||
15435            gameInfo.variant == VariantCourier  ||
15436            gameInfo.variant == VariantASEAN    ||
15437            gameInfo.variant == VariantMakruk     )
15438             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15439         goto defaultlabel;
15440
15441       case WhiteKing:
15442       case BlackKing:
15443         if(gameInfo.variant == VariantXiangqi)
15444             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15445         if(gameInfo.variant == VariantKnightmate)
15446             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15447       default:
15448         defaultlabel:
15449         if (gameMode == IcsExamining) {
15450             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15451             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15452                      PieceToChar(selection), AAA + x, ONE + y);
15453             SendToICS(buf);
15454         } else {
15455             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15456                 int n;
15457                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15458                     n = PieceToNumber(selection - BlackPawn);
15459                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15460                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15461                     boards[0][BOARD_HEIGHT-1-n][1]++;
15462                 } else
15463                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15464                     n = PieceToNumber(selection);
15465                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15466                     boards[0][n][BOARD_WIDTH-1] = selection;
15467                     boards[0][n][BOARD_WIDTH-2]++;
15468                 }
15469             } else
15470             boards[0][y][x] = selection;
15471             DrawPosition(TRUE, boards[0]);
15472             ClearHighlights();
15473             fromX = fromY = -1;
15474         }
15475         break;
15476     }
15477 }
15478
15479
15480 void
15481 DropMenuEvent (ChessSquare selection, int x, int y)
15482 {
15483     ChessMove moveType;
15484
15485     switch (gameMode) {
15486       case IcsPlayingWhite:
15487       case MachinePlaysBlack:
15488         if (!WhiteOnMove(currentMove)) {
15489             DisplayMoveError(_("It is Black's turn"));
15490             return;
15491         }
15492         moveType = WhiteDrop;
15493         break;
15494       case IcsPlayingBlack:
15495       case MachinePlaysWhite:
15496         if (WhiteOnMove(currentMove)) {
15497             DisplayMoveError(_("It is White's turn"));
15498             return;
15499         }
15500         moveType = BlackDrop;
15501         break;
15502       case EditGame:
15503         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15504         break;
15505       default:
15506         return;
15507     }
15508
15509     if (moveType == BlackDrop && selection < BlackPawn) {
15510       selection = (ChessSquare) ((int) selection
15511                                  + (int) BlackPawn - (int) WhitePawn);
15512     }
15513     if (boards[currentMove][y][x] != EmptySquare) {
15514         DisplayMoveError(_("That square is occupied"));
15515         return;
15516     }
15517
15518     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15519 }
15520
15521 void
15522 AcceptEvent ()
15523 {
15524     /* Accept a pending offer of any kind from opponent */
15525
15526     if (appData.icsActive) {
15527         SendToICS(ics_prefix);
15528         SendToICS("accept\n");
15529     } else if (cmailMsgLoaded) {
15530         if (currentMove == cmailOldMove &&
15531             commentList[cmailOldMove] != NULL &&
15532             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15533                    "Black offers a draw" : "White offers a draw")) {
15534             TruncateGame();
15535             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15536             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15537         } else {
15538             DisplayError(_("There is no pending offer on this move"), 0);
15539             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15540         }
15541     } else {
15542         /* Not used for offers from chess program */
15543     }
15544 }
15545
15546 void
15547 DeclineEvent ()
15548 {
15549     /* Decline a pending offer of any kind from opponent */
15550
15551     if (appData.icsActive) {
15552         SendToICS(ics_prefix);
15553         SendToICS("decline\n");
15554     } else if (cmailMsgLoaded) {
15555         if (currentMove == cmailOldMove &&
15556             commentList[cmailOldMove] != NULL &&
15557             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15558                    "Black offers a draw" : "White offers a draw")) {
15559 #ifdef NOTDEF
15560             AppendComment(cmailOldMove, "Draw declined", TRUE);
15561             DisplayComment(cmailOldMove - 1, "Draw declined");
15562 #endif /*NOTDEF*/
15563         } else {
15564             DisplayError(_("There is no pending offer on this move"), 0);
15565         }
15566     } else {
15567         /* Not used for offers from chess program */
15568     }
15569 }
15570
15571 void
15572 RematchEvent ()
15573 {
15574     /* Issue ICS rematch command */
15575     if (appData.icsActive) {
15576         SendToICS(ics_prefix);
15577         SendToICS("rematch\n");
15578     }
15579 }
15580
15581 void
15582 CallFlagEvent ()
15583 {
15584     /* Call your opponent's flag (claim a win on time) */
15585     if (appData.icsActive) {
15586         SendToICS(ics_prefix);
15587         SendToICS("flag\n");
15588     } else {
15589         switch (gameMode) {
15590           default:
15591             return;
15592           case MachinePlaysWhite:
15593             if (whiteFlag) {
15594                 if (blackFlag)
15595                   GameEnds(GameIsDrawn, "Both players ran out of time",
15596                            GE_PLAYER);
15597                 else
15598                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15599             } else {
15600                 DisplayError(_("Your opponent is not out of time"), 0);
15601             }
15602             break;
15603           case MachinePlaysBlack:
15604             if (blackFlag) {
15605                 if (whiteFlag)
15606                   GameEnds(GameIsDrawn, "Both players ran out of time",
15607                            GE_PLAYER);
15608                 else
15609                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15610             } else {
15611                 DisplayError(_("Your opponent is not out of time"), 0);
15612             }
15613             break;
15614         }
15615     }
15616 }
15617
15618 void
15619 ClockClick (int which)
15620 {       // [HGM] code moved to back-end from winboard.c
15621         if(which) { // black clock
15622           if (gameMode == EditPosition || gameMode == IcsExamining) {
15623             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15624             SetBlackToPlayEvent();
15625           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15626                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15627           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15628           } else if (shiftKey) {
15629             AdjustClock(which, -1);
15630           } else if (gameMode == IcsPlayingWhite ||
15631                      gameMode == MachinePlaysBlack) {
15632             CallFlagEvent();
15633           }
15634         } else { // white clock
15635           if (gameMode == EditPosition || gameMode == IcsExamining) {
15636             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15637             SetWhiteToPlayEvent();
15638           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15639                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15640           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15641           } else if (shiftKey) {
15642             AdjustClock(which, -1);
15643           } else if (gameMode == IcsPlayingBlack ||
15644                    gameMode == MachinePlaysWhite) {
15645             CallFlagEvent();
15646           }
15647         }
15648 }
15649
15650 void
15651 DrawEvent ()
15652 {
15653     /* Offer draw or accept pending draw offer from opponent */
15654
15655     if (appData.icsActive) {
15656         /* Note: tournament rules require draw offers to be
15657            made after you make your move but before you punch
15658            your clock.  Currently ICS doesn't let you do that;
15659            instead, you immediately punch your clock after making
15660            a move, but you can offer a draw at any time. */
15661
15662         SendToICS(ics_prefix);
15663         SendToICS("draw\n");
15664         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15665     } else if (cmailMsgLoaded) {
15666         if (currentMove == cmailOldMove &&
15667             commentList[cmailOldMove] != NULL &&
15668             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15669                    "Black offers a draw" : "White offers a draw")) {
15670             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15671             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15672         } else if (currentMove == cmailOldMove + 1) {
15673             char *offer = WhiteOnMove(cmailOldMove) ?
15674               "White offers a draw" : "Black offers a draw";
15675             AppendComment(currentMove, offer, TRUE);
15676             DisplayComment(currentMove - 1, offer);
15677             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15678         } else {
15679             DisplayError(_("You must make your move before offering a draw"), 0);
15680             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15681         }
15682     } else if (first.offeredDraw) {
15683         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15684     } else {
15685         if (first.sendDrawOffers) {
15686             SendToProgram("draw\n", &first);
15687             userOfferedDraw = TRUE;
15688         }
15689     }
15690 }
15691
15692 void
15693 AdjournEvent ()
15694 {
15695     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15696
15697     if (appData.icsActive) {
15698         SendToICS(ics_prefix);
15699         SendToICS("adjourn\n");
15700     } else {
15701         /* Currently GNU Chess doesn't offer or accept Adjourns */
15702     }
15703 }
15704
15705
15706 void
15707 AbortEvent ()
15708 {
15709     /* Offer Abort or accept pending Abort offer from opponent */
15710
15711     if (appData.icsActive) {
15712         SendToICS(ics_prefix);
15713         SendToICS("abort\n");
15714     } else {
15715         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15716     }
15717 }
15718
15719 void
15720 ResignEvent ()
15721 {
15722     /* Resign.  You can do this even if it's not your turn. */
15723
15724     if (appData.icsActive) {
15725         SendToICS(ics_prefix);
15726         SendToICS("resign\n");
15727     } else {
15728         switch (gameMode) {
15729           case MachinePlaysWhite:
15730             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15731             break;
15732           case MachinePlaysBlack:
15733             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15734             break;
15735           case EditGame:
15736             if (cmailMsgLoaded) {
15737                 TruncateGame();
15738                 if (WhiteOnMove(cmailOldMove)) {
15739                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15740                 } else {
15741                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15742                 }
15743                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15744             }
15745             break;
15746           default:
15747             break;
15748         }
15749     }
15750 }
15751
15752
15753 void
15754 StopObservingEvent ()
15755 {
15756     /* Stop observing current games */
15757     SendToICS(ics_prefix);
15758     SendToICS("unobserve\n");
15759 }
15760
15761 void
15762 StopExaminingEvent ()
15763 {
15764     /* Stop observing current game */
15765     SendToICS(ics_prefix);
15766     SendToICS("unexamine\n");
15767 }
15768
15769 void
15770 ForwardInner (int target)
15771 {
15772     int limit; int oldSeekGraphUp = seekGraphUp;
15773
15774     if (appData.debugMode)
15775         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15776                 target, currentMove, forwardMostMove);
15777
15778     if (gameMode == EditPosition)
15779       return;
15780
15781     seekGraphUp = FALSE;
15782     MarkTargetSquares(1);
15783     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15784
15785     if (gameMode == PlayFromGameFile && !pausing)
15786       PauseEvent();
15787
15788     if (gameMode == IcsExamining && pausing)
15789       limit = pauseExamForwardMostMove;
15790     else
15791       limit = forwardMostMove;
15792
15793     if (target > limit) target = limit;
15794
15795     if (target > 0 && moveList[target - 1][0]) {
15796         int fromX, fromY, toX, toY;
15797         toX = moveList[target - 1][2] - AAA;
15798         toY = moveList[target - 1][3] - ONE;
15799         if (moveList[target - 1][1] == '@') {
15800             if (appData.highlightLastMove) {
15801                 SetHighlights(-1, -1, toX, toY);
15802             }
15803         } else {
15804             int viaX = moveList[target - 1][5] - AAA;
15805             int viaY = moveList[target - 1][6] - ONE;
15806             fromX = moveList[target - 1][0] - AAA;
15807             fromY = moveList[target - 1][1] - ONE;
15808             if (target == currentMove + 1) {
15809                 if(moveList[target - 1][4] == ';') { // multi-leg
15810                     ChessSquare piece = boards[currentMove][viaY][viaX];
15811                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15812                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15813                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15814                     boards[currentMove][viaY][viaX] = piece;
15815                 } else
15816                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15817             }
15818             if (appData.highlightLastMove) {
15819                 SetHighlights(fromX, fromY, toX, toY);
15820             }
15821         }
15822     }
15823     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15824         gameMode == Training || gameMode == PlayFromGameFile ||
15825         gameMode == AnalyzeFile) {
15826         while (currentMove < target) {
15827             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15828             SendMoveToProgram(currentMove++, &first);
15829         }
15830     } else {
15831         currentMove = target;
15832     }
15833
15834     if (gameMode == EditGame || gameMode == EndOfGame) {
15835         whiteTimeRemaining = timeRemaining[0][currentMove];
15836         blackTimeRemaining = timeRemaining[1][currentMove];
15837     }
15838     DisplayBothClocks();
15839     DisplayMove(currentMove - 1);
15840     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15841     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15842     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15843         DisplayComment(currentMove - 1, commentList[currentMove]);
15844     }
15845     ClearMap(); // [HGM] exclude: invalidate map
15846 }
15847
15848
15849 void
15850 ForwardEvent ()
15851 {
15852     if (gameMode == IcsExamining && !pausing) {
15853         SendToICS(ics_prefix);
15854         SendToICS("forward\n");
15855     } else {
15856         ForwardInner(currentMove + 1);
15857     }
15858 }
15859
15860 void
15861 ToEndEvent ()
15862 {
15863     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15864         /* to optimze, we temporarily turn off analysis mode while we feed
15865          * the remaining moves to the engine. Otherwise we get analysis output
15866          * after each move.
15867          */
15868         if (first.analysisSupport) {
15869           SendToProgram("exit\nforce\n", &first);
15870           first.analyzing = FALSE;
15871         }
15872     }
15873
15874     if (gameMode == IcsExamining && !pausing) {
15875         SendToICS(ics_prefix);
15876         SendToICS("forward 999999\n");
15877     } else {
15878         ForwardInner(forwardMostMove);
15879     }
15880
15881     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15882         /* we have fed all the moves, so reactivate analysis mode */
15883         SendToProgram("analyze\n", &first);
15884         first.analyzing = TRUE;
15885         /*first.maybeThinking = TRUE;*/
15886         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15887     }
15888 }
15889
15890 void
15891 BackwardInner (int target)
15892 {
15893     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15894
15895     if (appData.debugMode)
15896         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15897                 target, currentMove, forwardMostMove);
15898
15899     if (gameMode == EditPosition) return;
15900     seekGraphUp = FALSE;
15901     MarkTargetSquares(1);
15902     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15903     if (currentMove <= backwardMostMove) {
15904         ClearHighlights();
15905         DrawPosition(full_redraw, boards[currentMove]);
15906         return;
15907     }
15908     if (gameMode == PlayFromGameFile && !pausing)
15909       PauseEvent();
15910
15911     if (moveList[target][0]) {
15912         int fromX, fromY, toX, toY;
15913         toX = moveList[target][2] - AAA;
15914         toY = moveList[target][3] - ONE;
15915         if (moveList[target][1] == '@') {
15916             if (appData.highlightLastMove) {
15917                 SetHighlights(-1, -1, toX, toY);
15918             }
15919         } else {
15920             fromX = moveList[target][0] - AAA;
15921             fromY = moveList[target][1] - ONE;
15922             if (target == currentMove - 1) {
15923                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15924             }
15925             if (appData.highlightLastMove) {
15926                 SetHighlights(fromX, fromY, toX, toY);
15927             }
15928         }
15929     }
15930     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15931         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15932         while (currentMove > target) {
15933             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15934                 // null move cannot be undone. Reload program with move history before it.
15935                 int i;
15936                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15937                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15938                 }
15939                 SendBoard(&first, i);
15940               if(second.analyzing) SendBoard(&second, i);
15941                 for(currentMove=i; currentMove<target; currentMove++) {
15942                     SendMoveToProgram(currentMove, &first);
15943                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15944                 }
15945                 break;
15946             }
15947             SendToBoth("undo\n");
15948             currentMove--;
15949         }
15950     } else {
15951         currentMove = target;
15952     }
15953
15954     if (gameMode == EditGame || gameMode == EndOfGame) {
15955         whiteTimeRemaining = timeRemaining[0][currentMove];
15956         blackTimeRemaining = timeRemaining[1][currentMove];
15957     }
15958     DisplayBothClocks();
15959     DisplayMove(currentMove - 1);
15960     DrawPosition(full_redraw, boards[currentMove]);
15961     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15962     // [HGM] PV info: routine tests if comment empty
15963     DisplayComment(currentMove - 1, commentList[currentMove]);
15964     ClearMap(); // [HGM] exclude: invalidate map
15965 }
15966
15967 void
15968 BackwardEvent ()
15969 {
15970     if (gameMode == IcsExamining && !pausing) {
15971         SendToICS(ics_prefix);
15972         SendToICS("backward\n");
15973     } else {
15974         BackwardInner(currentMove - 1);
15975     }
15976 }
15977
15978 void
15979 ToStartEvent ()
15980 {
15981     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15982         /* to optimize, we temporarily turn off analysis mode while we undo
15983          * all the moves. Otherwise we get analysis output after each undo.
15984          */
15985         if (first.analysisSupport) {
15986           SendToProgram("exit\nforce\n", &first);
15987           first.analyzing = FALSE;
15988         }
15989     }
15990
15991     if (gameMode == IcsExamining && !pausing) {
15992         SendToICS(ics_prefix);
15993         SendToICS("backward 999999\n");
15994     } else {
15995         BackwardInner(backwardMostMove);
15996     }
15997
15998     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15999         /* we have fed all the moves, so reactivate analysis mode */
16000         SendToProgram("analyze\n", &first);
16001         first.analyzing = TRUE;
16002         /*first.maybeThinking = TRUE;*/
16003         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16004     }
16005 }
16006
16007 void
16008 ToNrEvent (int to)
16009 {
16010   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16011   if (to >= forwardMostMove) to = forwardMostMove;
16012   if (to <= backwardMostMove) to = backwardMostMove;
16013   if (to < currentMove) {
16014     BackwardInner(to);
16015   } else {
16016     ForwardInner(to);
16017   }
16018 }
16019
16020 void
16021 RevertEvent (Boolean annotate)
16022 {
16023     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16024         return;
16025     }
16026     if (gameMode != IcsExamining) {
16027         DisplayError(_("You are not examining a game"), 0);
16028         return;
16029     }
16030     if (pausing) {
16031         DisplayError(_("You can't revert while pausing"), 0);
16032         return;
16033     }
16034     SendToICS(ics_prefix);
16035     SendToICS("revert\n");
16036 }
16037
16038 void
16039 RetractMoveEvent ()
16040 {
16041     switch (gameMode) {
16042       case MachinePlaysWhite:
16043       case MachinePlaysBlack:
16044         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16045             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16046             return;
16047         }
16048         if (forwardMostMove < 2) return;
16049         currentMove = forwardMostMove = forwardMostMove - 2;
16050         whiteTimeRemaining = timeRemaining[0][currentMove];
16051         blackTimeRemaining = timeRemaining[1][currentMove];
16052         DisplayBothClocks();
16053         DisplayMove(currentMove - 1);
16054         ClearHighlights();/*!! could figure this out*/
16055         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16056         SendToProgram("remove\n", &first);
16057         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16058         break;
16059
16060       case BeginningOfGame:
16061       default:
16062         break;
16063
16064       case IcsPlayingWhite:
16065       case IcsPlayingBlack:
16066         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16067             SendToICS(ics_prefix);
16068             SendToICS("takeback 2\n");
16069         } else {
16070             SendToICS(ics_prefix);
16071             SendToICS("takeback 1\n");
16072         }
16073         break;
16074     }
16075 }
16076
16077 void
16078 MoveNowEvent ()
16079 {
16080     ChessProgramState *cps;
16081
16082     switch (gameMode) {
16083       case MachinePlaysWhite:
16084         if (!WhiteOnMove(forwardMostMove)) {
16085             DisplayError(_("It is your turn"), 0);
16086             return;
16087         }
16088         cps = &first;
16089         break;
16090       case MachinePlaysBlack:
16091         if (WhiteOnMove(forwardMostMove)) {
16092             DisplayError(_("It is your turn"), 0);
16093             return;
16094         }
16095         cps = &first;
16096         break;
16097       case TwoMachinesPlay:
16098         if (WhiteOnMove(forwardMostMove) ==
16099             (first.twoMachinesColor[0] == 'w')) {
16100             cps = &first;
16101         } else {
16102             cps = &second;
16103         }
16104         break;
16105       case BeginningOfGame:
16106       default:
16107         return;
16108     }
16109     SendToProgram("?\n", cps);
16110 }
16111
16112 void
16113 TruncateGameEvent ()
16114 {
16115     EditGameEvent();
16116     if (gameMode != EditGame) return;
16117     TruncateGame();
16118 }
16119
16120 void
16121 TruncateGame ()
16122 {
16123     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16124     if (forwardMostMove > currentMove) {
16125         if (gameInfo.resultDetails != NULL) {
16126             free(gameInfo.resultDetails);
16127             gameInfo.resultDetails = NULL;
16128             gameInfo.result = GameUnfinished;
16129         }
16130         forwardMostMove = currentMove;
16131         HistorySet(parseList, backwardMostMove, forwardMostMove,
16132                    currentMove-1);
16133     }
16134 }
16135
16136 void
16137 HintEvent ()
16138 {
16139     if (appData.noChessProgram) return;
16140     switch (gameMode) {
16141       case MachinePlaysWhite:
16142         if (WhiteOnMove(forwardMostMove)) {
16143             DisplayError(_("Wait until your turn."), 0);
16144             return;
16145         }
16146         break;
16147       case BeginningOfGame:
16148       case MachinePlaysBlack:
16149         if (!WhiteOnMove(forwardMostMove)) {
16150             DisplayError(_("Wait until your turn."), 0);
16151             return;
16152         }
16153         break;
16154       default:
16155         DisplayError(_("No hint available"), 0);
16156         return;
16157     }
16158     SendToProgram("hint\n", &first);
16159     hintRequested = TRUE;
16160 }
16161
16162 int
16163 SaveSelected (FILE *g, int dummy, char *dummy2)
16164 {
16165     ListGame * lg = (ListGame *) gameList.head;
16166     int nItem, cnt=0;
16167     FILE *f;
16168
16169     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16170         DisplayError(_("Game list not loaded or empty"), 0);
16171         return 0;
16172     }
16173
16174     creatingBook = TRUE; // suppresses stuff during load game
16175
16176     /* Get list size */
16177     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16178         if(lg->position >= 0) { // selected?
16179             LoadGame(f, nItem, "", TRUE);
16180             SaveGamePGN2(g); // leaves g open
16181             cnt++; DoEvents();
16182         }
16183         lg = (ListGame *) lg->node.succ;
16184     }
16185
16186     fclose(g);
16187     creatingBook = FALSE;
16188
16189     return cnt;
16190 }
16191
16192 void
16193 CreateBookEvent ()
16194 {
16195     ListGame * lg = (ListGame *) gameList.head;
16196     FILE *f, *g;
16197     int nItem;
16198     static int secondTime = FALSE;
16199
16200     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16201         DisplayError(_("Game list not loaded or empty"), 0);
16202         return;
16203     }
16204
16205     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16206         fclose(g);
16207         secondTime++;
16208         DisplayNote(_("Book file exists! Try again for overwrite."));
16209         return;
16210     }
16211
16212     creatingBook = TRUE;
16213     secondTime = FALSE;
16214
16215     /* Get list size */
16216     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16217         if(lg->position >= 0) {
16218             LoadGame(f, nItem, "", TRUE);
16219             AddGameToBook(TRUE);
16220             DoEvents();
16221         }
16222         lg = (ListGame *) lg->node.succ;
16223     }
16224
16225     creatingBook = FALSE;
16226     FlushBook();
16227 }
16228
16229 void
16230 BookEvent ()
16231 {
16232     if (appData.noChessProgram) return;
16233     switch (gameMode) {
16234       case MachinePlaysWhite:
16235         if (WhiteOnMove(forwardMostMove)) {
16236             DisplayError(_("Wait until your turn."), 0);
16237             return;
16238         }
16239         break;
16240       case BeginningOfGame:
16241       case MachinePlaysBlack:
16242         if (!WhiteOnMove(forwardMostMove)) {
16243             DisplayError(_("Wait until your turn."), 0);
16244             return;
16245         }
16246         break;
16247       case EditPosition:
16248         EditPositionDone(TRUE);
16249         break;
16250       case TwoMachinesPlay:
16251         return;
16252       default:
16253         break;
16254     }
16255     SendToProgram("bk\n", &first);
16256     bookOutput[0] = NULLCHAR;
16257     bookRequested = TRUE;
16258 }
16259
16260 void
16261 AboutGameEvent ()
16262 {
16263     char *tags = PGNTags(&gameInfo);
16264     TagsPopUp(tags, CmailMsg());
16265     free(tags);
16266 }
16267
16268 /* end button procedures */
16269
16270 void
16271 PrintPosition (FILE *fp, int move)
16272 {
16273     int i, j;
16274
16275     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16276         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16277             char c = PieceToChar(boards[move][i][j]);
16278             fputc(c == 'x' ? '.' : c, fp);
16279             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16280         }
16281     }
16282     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16283       fprintf(fp, "white to play\n");
16284     else
16285       fprintf(fp, "black to play\n");
16286 }
16287
16288 void
16289 PrintOpponents (FILE *fp)
16290 {
16291     if (gameInfo.white != NULL) {
16292         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16293     } else {
16294         fprintf(fp, "\n");
16295     }
16296 }
16297
16298 /* Find last component of program's own name, using some heuristics */
16299 void
16300 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16301 {
16302     char *p, *q, c;
16303     int local = (strcmp(host, "localhost") == 0);
16304     while (!local && (p = strchr(prog, ';')) != NULL) {
16305         p++;
16306         while (*p == ' ') p++;
16307         prog = p;
16308     }
16309     if (*prog == '"' || *prog == '\'') {
16310         q = strchr(prog + 1, *prog);
16311     } else {
16312         q = strchr(prog, ' ');
16313     }
16314     if (q == NULL) q = prog + strlen(prog);
16315     p = q;
16316     while (p >= prog && *p != '/' && *p != '\\') p--;
16317     p++;
16318     if(p == prog && *p == '"') p++;
16319     c = *q; *q = 0;
16320     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16321     memcpy(buf, p, q - p);
16322     buf[q - p] = NULLCHAR;
16323     if (!local) {
16324         strcat(buf, "@");
16325         strcat(buf, host);
16326     }
16327 }
16328
16329 char *
16330 TimeControlTagValue ()
16331 {
16332     char buf[MSG_SIZ];
16333     if (!appData.clockMode) {
16334       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16335     } else if (movesPerSession > 0) {
16336       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16337     } else if (timeIncrement == 0) {
16338       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16339     } else {
16340       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16341     }
16342     return StrSave(buf);
16343 }
16344
16345 void
16346 SetGameInfo ()
16347 {
16348     /* This routine is used only for certain modes */
16349     VariantClass v = gameInfo.variant;
16350     ChessMove r = GameUnfinished;
16351     char *p = NULL;
16352
16353     if(keepInfo) return;
16354
16355     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16356         r = gameInfo.result;
16357         p = gameInfo.resultDetails;
16358         gameInfo.resultDetails = NULL;
16359     }
16360     ClearGameInfo(&gameInfo);
16361     gameInfo.variant = v;
16362
16363     switch (gameMode) {
16364       case MachinePlaysWhite:
16365         gameInfo.event = StrSave( appData.pgnEventHeader );
16366         gameInfo.site = StrSave(HostName());
16367         gameInfo.date = PGNDate();
16368         gameInfo.round = StrSave("-");
16369         gameInfo.white = StrSave(first.tidy);
16370         gameInfo.black = StrSave(UserName());
16371         gameInfo.timeControl = TimeControlTagValue();
16372         break;
16373
16374       case MachinePlaysBlack:
16375         gameInfo.event = StrSave( appData.pgnEventHeader );
16376         gameInfo.site = StrSave(HostName());
16377         gameInfo.date = PGNDate();
16378         gameInfo.round = StrSave("-");
16379         gameInfo.white = StrSave(UserName());
16380         gameInfo.black = StrSave(first.tidy);
16381         gameInfo.timeControl = TimeControlTagValue();
16382         break;
16383
16384       case TwoMachinesPlay:
16385         gameInfo.event = StrSave( appData.pgnEventHeader );
16386         gameInfo.site = StrSave(HostName());
16387         gameInfo.date = PGNDate();
16388         if (roundNr > 0) {
16389             char buf[MSG_SIZ];
16390             snprintf(buf, MSG_SIZ, "%d", roundNr);
16391             gameInfo.round = StrSave(buf);
16392         } else {
16393             gameInfo.round = StrSave("-");
16394         }
16395         if (first.twoMachinesColor[0] == 'w') {
16396             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16397             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16398         } else {
16399             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16400             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16401         }
16402         gameInfo.timeControl = TimeControlTagValue();
16403         break;
16404
16405       case EditGame:
16406         gameInfo.event = StrSave("Edited game");
16407         gameInfo.site = StrSave(HostName());
16408         gameInfo.date = PGNDate();
16409         gameInfo.round = StrSave("-");
16410         gameInfo.white = StrSave("-");
16411         gameInfo.black = StrSave("-");
16412         gameInfo.result = r;
16413         gameInfo.resultDetails = p;
16414         break;
16415
16416       case EditPosition:
16417         gameInfo.event = StrSave("Edited position");
16418         gameInfo.site = StrSave(HostName());
16419         gameInfo.date = PGNDate();
16420         gameInfo.round = StrSave("-");
16421         gameInfo.white = StrSave("-");
16422         gameInfo.black = StrSave("-");
16423         break;
16424
16425       case IcsPlayingWhite:
16426       case IcsPlayingBlack:
16427       case IcsObserving:
16428       case IcsExamining:
16429         break;
16430
16431       case PlayFromGameFile:
16432         gameInfo.event = StrSave("Game from non-PGN file");
16433         gameInfo.site = StrSave(HostName());
16434         gameInfo.date = PGNDate();
16435         gameInfo.round = StrSave("-");
16436         gameInfo.white = StrSave("?");
16437         gameInfo.black = StrSave("?");
16438         break;
16439
16440       default:
16441         break;
16442     }
16443 }
16444
16445 void
16446 ReplaceComment (int index, char *text)
16447 {
16448     int len;
16449     char *p;
16450     float score;
16451
16452     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16453        pvInfoList[index-1].depth == len &&
16454        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16455        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16456     while (*text == '\n') text++;
16457     len = strlen(text);
16458     while (len > 0 && text[len - 1] == '\n') len--;
16459
16460     if (commentList[index] != NULL)
16461       free(commentList[index]);
16462
16463     if (len == 0) {
16464         commentList[index] = NULL;
16465         return;
16466     }
16467   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16468       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16469       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16470     commentList[index] = (char *) malloc(len + 2);
16471     strncpy(commentList[index], text, len);
16472     commentList[index][len] = '\n';
16473     commentList[index][len + 1] = NULLCHAR;
16474   } else {
16475     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16476     char *p;
16477     commentList[index] = (char *) malloc(len + 7);
16478     safeStrCpy(commentList[index], "{\n", 3);
16479     safeStrCpy(commentList[index]+2, text, len+1);
16480     commentList[index][len+2] = NULLCHAR;
16481     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16482     strcat(commentList[index], "\n}\n");
16483   }
16484 }
16485
16486 void
16487 CrushCRs (char *text)
16488 {
16489   char *p = text;
16490   char *q = text;
16491   char ch;
16492
16493   do {
16494     ch = *p++;
16495     if (ch == '\r') continue;
16496     *q++ = ch;
16497   } while (ch != '\0');
16498 }
16499
16500 void
16501 AppendComment (int index, char *text, Boolean addBraces)
16502 /* addBraces  tells if we should add {} */
16503 {
16504     int oldlen, len;
16505     char *old;
16506
16507 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16508     if(addBraces == 3) addBraces = 0; else // force appending literally
16509     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16510
16511     CrushCRs(text);
16512     while (*text == '\n') text++;
16513     len = strlen(text);
16514     while (len > 0 && text[len - 1] == '\n') len--;
16515     text[len] = NULLCHAR;
16516
16517     if (len == 0) return;
16518
16519     if (commentList[index] != NULL) {
16520       Boolean addClosingBrace = addBraces;
16521         old = commentList[index];
16522         oldlen = strlen(old);
16523         while(commentList[index][oldlen-1] ==  '\n')
16524           commentList[index][--oldlen] = NULLCHAR;
16525         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16526         safeStrCpy(commentList[index], old, oldlen + len + 6);
16527         free(old);
16528         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16529         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16530           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16531           while (*text == '\n') { text++; len--; }
16532           commentList[index][--oldlen] = NULLCHAR;
16533       }
16534         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16535         else          strcat(commentList[index], "\n");
16536         strcat(commentList[index], text);
16537         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16538         else          strcat(commentList[index], "\n");
16539     } else {
16540         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16541         if(addBraces)
16542           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16543         else commentList[index][0] = NULLCHAR;
16544         strcat(commentList[index], text);
16545         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16546         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16547     }
16548 }
16549
16550 static char *
16551 FindStr (char * text, char * sub_text)
16552 {
16553     char * result = strstr( text, sub_text );
16554
16555     if( result != NULL ) {
16556         result += strlen( sub_text );
16557     }
16558
16559     return result;
16560 }
16561
16562 /* [AS] Try to extract PV info from PGN comment */
16563 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16564 char *
16565 GetInfoFromComment (int index, char * text)
16566 {
16567     char * sep = text, *p;
16568
16569     if( text != NULL && index > 0 ) {
16570         int score = 0;
16571         int depth = 0;
16572         int time = -1, sec = 0, deci;
16573         char * s_eval = FindStr( text, "[%eval " );
16574         char * s_emt = FindStr( text, "[%emt " );
16575 #if 0
16576         if( s_eval != NULL || s_emt != NULL ) {
16577 #else
16578         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16579 #endif
16580             /* New style */
16581             char delim;
16582
16583             if( s_eval != NULL ) {
16584                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16585                     return text;
16586                 }
16587
16588                 if( delim != ']' ) {
16589                     return text;
16590                 }
16591             }
16592
16593             if( s_emt != NULL ) {
16594             }
16595                 return text;
16596         }
16597         else {
16598             /* We expect something like: [+|-]nnn.nn/dd */
16599             int score_lo = 0;
16600
16601             if(*text != '{') return text; // [HGM] braces: must be normal comment
16602
16603             sep = strchr( text, '/' );
16604             if( sep == NULL || sep < (text+4) ) {
16605                 return text;
16606             }
16607
16608             p = text;
16609             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16610             if(p[1] == '(') { // comment starts with PV
16611                p = strchr(p, ')'); // locate end of PV
16612                if(p == NULL || sep < p+5) return text;
16613                // at this point we have something like "{(.*) +0.23/6 ..."
16614                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16615                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16616                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16617             }
16618             time = -1; sec = -1; deci = -1;
16619             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16620                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16621                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16622                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16623                 return text;
16624             }
16625
16626             if( score_lo < 0 || score_lo >= 100 ) {
16627                 return text;
16628             }
16629
16630             if(sec >= 0) time = 600*time + 10*sec; else
16631             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16632
16633             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16634
16635             /* [HGM] PV time: now locate end of PV info */
16636             while( *++sep >= '0' && *sep <= '9'); // strip depth
16637             if(time >= 0)
16638             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16639             if(sec >= 0)
16640             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16641             if(deci >= 0)
16642             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16643             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16644         }
16645
16646         if( depth <= 0 ) {
16647             return text;
16648         }
16649
16650         if( time < 0 ) {
16651             time = -1;
16652         }
16653
16654         pvInfoList[index-1].depth = depth;
16655         pvInfoList[index-1].score = score;
16656         pvInfoList[index-1].time  = 10*time; // centi-sec
16657         if(*sep == '}') *sep = 0; else *--sep = '{';
16658         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16659     }
16660     return sep;
16661 }
16662
16663 void
16664 SendToProgram (char *message, ChessProgramState *cps)
16665 {
16666     int count, outCount, error;
16667     char buf[MSG_SIZ];
16668
16669     if (cps->pr == NoProc) return;
16670     Attention(cps);
16671
16672     if (appData.debugMode) {
16673         TimeMark now;
16674         GetTimeMark(&now);
16675         fprintf(debugFP, "%ld >%-6s: %s",
16676                 SubtractTimeMarks(&now, &programStartTime),
16677                 cps->which, message);
16678         if(serverFP)
16679             fprintf(serverFP, "%ld >%-6s: %s",
16680                 SubtractTimeMarks(&now, &programStartTime),
16681                 cps->which, message), fflush(serverFP);
16682     }
16683
16684     count = strlen(message);
16685     outCount = OutputToProcess(cps->pr, message, count, &error);
16686     if (outCount < count && !exiting
16687                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16688       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16689       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16690         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16691             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16692                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16693                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16694                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16695             } else {
16696                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16697                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16698                 gameInfo.result = res;
16699             }
16700             gameInfo.resultDetails = StrSave(buf);
16701         }
16702         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16703         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16704     }
16705 }
16706
16707 void
16708 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16709 {
16710     char *end_str;
16711     char buf[MSG_SIZ];
16712     ChessProgramState *cps = (ChessProgramState *)closure;
16713
16714     if (isr != cps->isr) return; /* Killed intentionally */
16715     if (count <= 0) {
16716         if (count == 0) {
16717             RemoveInputSource(cps->isr);
16718             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16719                     _(cps->which), cps->program);
16720             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16721             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16722                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16723                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16724                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16725                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16726                 } else {
16727                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16728                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16729                     gameInfo.result = res;
16730                 }
16731                 gameInfo.resultDetails = StrSave(buf);
16732             }
16733             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16734             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16735         } else {
16736             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16737                     _(cps->which), cps->program);
16738             RemoveInputSource(cps->isr);
16739
16740             /* [AS] Program is misbehaving badly... kill it */
16741             if( count == -2 ) {
16742                 DestroyChildProcess( cps->pr, 9 );
16743                 cps->pr = NoProc;
16744             }
16745
16746             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16747         }
16748         return;
16749     }
16750
16751     if ((end_str = strchr(message, '\r')) != NULL)
16752       *end_str = NULLCHAR;
16753     if ((end_str = strchr(message, '\n')) != NULL)
16754       *end_str = NULLCHAR;
16755
16756     if (appData.debugMode) {
16757         TimeMark now; int print = 1;
16758         char *quote = ""; char c; int i;
16759
16760         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16761                 char start = message[0];
16762                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16763                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16764                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16765                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16766                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16767                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16768                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16769                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16770                    sscanf(message, "hint: %c", &c)!=1 &&
16771                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16772                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16773                     print = (appData.engineComments >= 2);
16774                 }
16775                 message[0] = start; // restore original message
16776         }
16777         if(print) {
16778                 GetTimeMark(&now);
16779                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16780                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16781                         quote,
16782                         message);
16783                 if(serverFP)
16784                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16785                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16786                         quote,
16787                         message), fflush(serverFP);
16788         }
16789     }
16790
16791     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16792     if (appData.icsEngineAnalyze) {
16793         if (strstr(message, "whisper") != NULL ||
16794              strstr(message, "kibitz") != NULL ||
16795             strstr(message, "tellics") != NULL) return;
16796     }
16797
16798     HandleMachineMove(message, cps);
16799 }
16800
16801
16802 void
16803 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16804 {
16805     char buf[MSG_SIZ];
16806     int seconds;
16807
16808     if( timeControl_2 > 0 ) {
16809         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16810             tc = timeControl_2;
16811         }
16812     }
16813     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16814     inc /= cps->timeOdds;
16815     st  /= cps->timeOdds;
16816
16817     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16818
16819     if (st > 0) {
16820       /* Set exact time per move, normally using st command */
16821       if (cps->stKludge) {
16822         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16823         seconds = st % 60;
16824         if (seconds == 0) {
16825           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16826         } else {
16827           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16828         }
16829       } else {
16830         snprintf(buf, MSG_SIZ, "st %d\n", st);
16831       }
16832     } else {
16833       /* Set conventional or incremental time control, using level command */
16834       if (seconds == 0) {
16835         /* Note old gnuchess bug -- minutes:seconds used to not work.
16836            Fixed in later versions, but still avoid :seconds
16837            when seconds is 0. */
16838         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16839       } else {
16840         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16841                  seconds, inc/1000.);
16842       }
16843     }
16844     SendToProgram(buf, cps);
16845
16846     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16847     /* Orthogonally, limit search to given depth */
16848     if (sd > 0) {
16849       if (cps->sdKludge) {
16850         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16851       } else {
16852         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16853       }
16854       SendToProgram(buf, cps);
16855     }
16856
16857     if(cps->nps >= 0) { /* [HGM] nps */
16858         if(cps->supportsNPS == FALSE)
16859           cps->nps = -1; // don't use if engine explicitly says not supported!
16860         else {
16861           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16862           SendToProgram(buf, cps);
16863         }
16864     }
16865 }
16866
16867 ChessProgramState *
16868 WhitePlayer ()
16869 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16870 {
16871     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16872        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16873         return &second;
16874     return &first;
16875 }
16876
16877 void
16878 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16879 {
16880     char message[MSG_SIZ];
16881     long time, otime;
16882
16883     /* Note: this routine must be called when the clocks are stopped
16884        or when they have *just* been set or switched; otherwise
16885        it will be off by the time since the current tick started.
16886     */
16887     if (machineWhite) {
16888         time = whiteTimeRemaining / 10;
16889         otime = blackTimeRemaining / 10;
16890     } else {
16891         time = blackTimeRemaining / 10;
16892         otime = whiteTimeRemaining / 10;
16893     }
16894     /* [HGM] translate opponent's time by time-odds factor */
16895     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16896
16897     if (time <= 0) time = 1;
16898     if (otime <= 0) otime = 1;
16899
16900     snprintf(message, MSG_SIZ, "time %ld\n", time);
16901     SendToProgram(message, cps);
16902
16903     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16904     SendToProgram(message, cps);
16905 }
16906
16907 char *
16908 EngineDefinedVariant (ChessProgramState *cps, int n)
16909 {   // return name of n-th unknown variant that engine supports
16910     static char buf[MSG_SIZ];
16911     char *p, *s = cps->variants;
16912     if(!s) return NULL;
16913     do { // parse string from variants feature
16914       VariantClass v;
16915         p = strchr(s, ',');
16916         if(p) *p = NULLCHAR;
16917       v = StringToVariant(s);
16918       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16919         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16920             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16921                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16922                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16923                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16924             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16925         }
16926         if(p) *p++ = ',';
16927         if(n < 0) return buf;
16928     } while(s = p);
16929     return NULL;
16930 }
16931
16932 int
16933 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16934 {
16935   char buf[MSG_SIZ];
16936   int len = strlen(name);
16937   int val;
16938
16939   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16940     (*p) += len + 1;
16941     sscanf(*p, "%d", &val);
16942     *loc = (val != 0);
16943     while (**p && **p != ' ')
16944       (*p)++;
16945     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16946     SendToProgram(buf, cps);
16947     return TRUE;
16948   }
16949   return FALSE;
16950 }
16951
16952 int
16953 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16954 {
16955   char buf[MSG_SIZ];
16956   int len = strlen(name);
16957   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16958     (*p) += len + 1;
16959     sscanf(*p, "%d", loc);
16960     while (**p && **p != ' ') (*p)++;
16961     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16962     SendToProgram(buf, cps);
16963     return TRUE;
16964   }
16965   return FALSE;
16966 }
16967
16968 int
16969 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16970 {
16971   char buf[MSG_SIZ];
16972   int len = strlen(name);
16973   if (strncmp((*p), name, len) == 0
16974       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16975     (*p) += len + 2;
16976     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16977     sscanf(*p, "%[^\"]", *loc);
16978     while (**p && **p != '\"') (*p)++;
16979     if (**p == '\"') (*p)++;
16980     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16981     SendToProgram(buf, cps);
16982     return TRUE;
16983   }
16984   return FALSE;
16985 }
16986
16987 int
16988 ParseOption (Option *opt, ChessProgramState *cps)
16989 // [HGM] options: process the string that defines an engine option, and determine
16990 // name, type, default value, and allowed value range
16991 {
16992         char *p, *q, buf[MSG_SIZ];
16993         int n, min = (-1)<<31, max = 1<<31, def;
16994
16995         opt->target = &opt->value;   // OK for spin/slider and checkbox
16996         if(p = strstr(opt->name, " -spin ")) {
16997             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16998             if(max < min) max = min; // enforce consistency
16999             if(def < min) def = min;
17000             if(def > max) def = max;
17001             opt->value = def;
17002             opt->min = min;
17003             opt->max = max;
17004             opt->type = Spin;
17005         } else if((p = strstr(opt->name, " -slider "))) {
17006             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17007             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17008             if(max < min) max = min; // enforce consistency
17009             if(def < min) def = min;
17010             if(def > max) def = max;
17011             opt->value = def;
17012             opt->min = min;
17013             opt->max = max;
17014             opt->type = Spin; // Slider;
17015         } else if((p = strstr(opt->name, " -string "))) {
17016             opt->textValue = p+9;
17017             opt->type = TextBox;
17018             opt->target = &opt->textValue;
17019         } else if((p = strstr(opt->name, " -file "))) {
17020             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17021             opt->target = opt->textValue = p+7;
17022             opt->type = FileName; // FileName;
17023             opt->target = &opt->textValue;
17024         } else if((p = strstr(opt->name, " -path "))) {
17025             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17026             opt->target = opt->textValue = p+7;
17027             opt->type = PathName; // PathName;
17028             opt->target = &opt->textValue;
17029         } else if(p = strstr(opt->name, " -check ")) {
17030             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17031             opt->value = (def != 0);
17032             opt->type = CheckBox;
17033         } else if(p = strstr(opt->name, " -combo ")) {
17034             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17035             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17036             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17037             opt->value = n = 0;
17038             while(q = StrStr(q, " /// ")) {
17039                 n++; *q = 0;    // count choices, and null-terminate each of them
17040                 q += 5;
17041                 if(*q == '*') { // remember default, which is marked with * prefix
17042                     q++;
17043                     opt->value = n;
17044                 }
17045                 cps->comboList[cps->comboCnt++] = q;
17046             }
17047             cps->comboList[cps->comboCnt++] = NULL;
17048             opt->max = n + 1;
17049             opt->type = ComboBox;
17050         } else if(p = strstr(opt->name, " -button")) {
17051             opt->type = Button;
17052         } else if(p = strstr(opt->name, " -save")) {
17053             opt->type = SaveButton;
17054         } else return FALSE;
17055         *p = 0; // terminate option name
17056         // now look if the command-line options define a setting for this engine option.
17057         if(cps->optionSettings && cps->optionSettings[0])
17058             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17059         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17060           snprintf(buf, MSG_SIZ, "option %s", p);
17061                 if(p = strstr(buf, ",")) *p = 0;
17062                 if(q = strchr(buf, '=')) switch(opt->type) {
17063                     case ComboBox:
17064                         for(n=0; n<opt->max; n++)
17065                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17066                         break;
17067                     case TextBox:
17068                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17069                         break;
17070                     case Spin:
17071                     case CheckBox:
17072                         opt->value = atoi(q+1);
17073                     default:
17074                         break;
17075                 }
17076                 strcat(buf, "\n");
17077                 SendToProgram(buf, cps);
17078         }
17079         return TRUE;
17080 }
17081
17082 void
17083 FeatureDone (ChessProgramState *cps, int val)
17084 {
17085   DelayedEventCallback cb = GetDelayedEvent();
17086   if ((cb == InitBackEnd3 && cps == &first) ||
17087       (cb == SettingsMenuIfReady && cps == &second) ||
17088       (cb == LoadEngine) ||
17089       (cb == TwoMachinesEventIfReady)) {
17090     CancelDelayedEvent();
17091     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17092   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17093   cps->initDone = val;
17094   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17095 }
17096
17097 /* Parse feature command from engine */
17098 void
17099 ParseFeatures (char *args, ChessProgramState *cps)
17100 {
17101   char *p = args;
17102   char *q = NULL;
17103   int val;
17104   char buf[MSG_SIZ];
17105
17106   for (;;) {
17107     while (*p == ' ') p++;
17108     if (*p == NULLCHAR) return;
17109
17110     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17111     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17112     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17113     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17114     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17115     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17116     if (BoolFeature(&p, "reuse", &val, cps)) {
17117       /* Engine can disable reuse, but can't enable it if user said no */
17118       if (!val) cps->reuse = FALSE;
17119       continue;
17120     }
17121     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17122     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17123       if (gameMode == TwoMachinesPlay) {
17124         DisplayTwoMachinesTitle();
17125       } else {
17126         DisplayTitle("");
17127       }
17128       continue;
17129     }
17130     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17131     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17132     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17133     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17134     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17135     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17136     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17137     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17138     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17139     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17140     if (IntFeature(&p, "done", &val, cps)) {
17141       FeatureDone(cps, val);
17142       continue;
17143     }
17144     /* Added by Tord: */
17145     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17146     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17147     /* End of additions by Tord */
17148
17149     /* [HGM] added features: */
17150     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17151     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17152     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17153     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17154     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17155     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17156     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17157     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17158         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17159         FREE(cps->option[cps->nrOptions].name);
17160         cps->option[cps->nrOptions].name = q; q = NULL;
17161         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17162           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17163             SendToProgram(buf, cps);
17164             continue;
17165         }
17166         if(cps->nrOptions >= MAX_OPTIONS) {
17167             cps->nrOptions--;
17168             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17169             DisplayError(buf, 0);
17170         }
17171         continue;
17172     }
17173     /* End of additions by HGM */
17174
17175     /* unknown feature: complain and skip */
17176     q = p;
17177     while (*q && *q != '=') q++;
17178     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17179     SendToProgram(buf, cps);
17180     p = q;
17181     if (*p == '=') {
17182       p++;
17183       if (*p == '\"') {
17184         p++;
17185         while (*p && *p != '\"') p++;
17186         if (*p == '\"') p++;
17187       } else {
17188         while (*p && *p != ' ') p++;
17189       }
17190     }
17191   }
17192
17193 }
17194
17195 void
17196 PeriodicUpdatesEvent (int newState)
17197 {
17198     if (newState == appData.periodicUpdates)
17199       return;
17200
17201     appData.periodicUpdates=newState;
17202
17203     /* Display type changes, so update it now */
17204 //    DisplayAnalysis();
17205
17206     /* Get the ball rolling again... */
17207     if (newState) {
17208         AnalysisPeriodicEvent(1);
17209         StartAnalysisClock();
17210     }
17211 }
17212
17213 void
17214 PonderNextMoveEvent (int newState)
17215 {
17216     if (newState == appData.ponderNextMove) return;
17217     if (gameMode == EditPosition) EditPositionDone(TRUE);
17218     if (newState) {
17219         SendToProgram("hard\n", &first);
17220         if (gameMode == TwoMachinesPlay) {
17221             SendToProgram("hard\n", &second);
17222         }
17223     } else {
17224         SendToProgram("easy\n", &first);
17225         thinkOutput[0] = NULLCHAR;
17226         if (gameMode == TwoMachinesPlay) {
17227             SendToProgram("easy\n", &second);
17228         }
17229     }
17230     appData.ponderNextMove = newState;
17231 }
17232
17233 void
17234 NewSettingEvent (int option, int *feature, char *command, int value)
17235 {
17236     char buf[MSG_SIZ];
17237
17238     if (gameMode == EditPosition) EditPositionDone(TRUE);
17239     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17240     if(feature == NULL || *feature) SendToProgram(buf, &first);
17241     if (gameMode == TwoMachinesPlay) {
17242         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17243     }
17244 }
17245
17246 void
17247 ShowThinkingEvent ()
17248 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17249 {
17250     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17251     int newState = appData.showThinking
17252         // [HGM] thinking: other features now need thinking output as well
17253         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17254
17255     if (oldState == newState) return;
17256     oldState = newState;
17257     if (gameMode == EditPosition) EditPositionDone(TRUE);
17258     if (oldState) {
17259         SendToProgram("post\n", &first);
17260         if (gameMode == TwoMachinesPlay) {
17261             SendToProgram("post\n", &second);
17262         }
17263     } else {
17264         SendToProgram("nopost\n", &first);
17265         thinkOutput[0] = NULLCHAR;
17266         if (gameMode == TwoMachinesPlay) {
17267             SendToProgram("nopost\n", &second);
17268         }
17269     }
17270 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17271 }
17272
17273 void
17274 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17275 {
17276   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17277   if (pr == NoProc) return;
17278   AskQuestion(title, question, replyPrefix, pr);
17279 }
17280
17281 void
17282 TypeInEvent (char firstChar)
17283 {
17284     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17285         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17286         gameMode == AnalyzeMode || gameMode == EditGame ||
17287         gameMode == EditPosition || gameMode == IcsExamining ||
17288         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17289         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17290                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17291                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17292         gameMode == Training) PopUpMoveDialog(firstChar);
17293 }
17294
17295 void
17296 TypeInDoneEvent (char *move)
17297 {
17298         Board board;
17299         int n, fromX, fromY, toX, toY;
17300         char promoChar;
17301         ChessMove moveType;
17302
17303         // [HGM] FENedit
17304         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17305                 EditPositionPasteFEN(move);
17306                 return;
17307         }
17308         // [HGM] movenum: allow move number to be typed in any mode
17309         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17310           ToNrEvent(2*n-1);
17311           return;
17312         }
17313         // undocumented kludge: allow command-line option to be typed in!
17314         // (potentially fatal, and does not implement the effect of the option.)
17315         // should only be used for options that are values on which future decisions will be made,
17316         // and definitely not on options that would be used during initialization.
17317         if(strstr(move, "!!! -") == move) {
17318             ParseArgsFromString(move+4);
17319             return;
17320         }
17321
17322       if (gameMode != EditGame && currentMove != forwardMostMove &&
17323         gameMode != Training) {
17324         DisplayMoveError(_("Displayed move is not current"));
17325       } else {
17326         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17327           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17328         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17329         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17330           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17331           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17332         } else {
17333           DisplayMoveError(_("Could not parse move"));
17334         }
17335       }
17336 }
17337
17338 void
17339 DisplayMove (int moveNumber)
17340 {
17341     char message[MSG_SIZ];
17342     char res[MSG_SIZ];
17343     char cpThinkOutput[MSG_SIZ];
17344
17345     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17346
17347     if (moveNumber == forwardMostMove - 1 ||
17348         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17349
17350         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17351
17352         if (strchr(cpThinkOutput, '\n')) {
17353             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17354         }
17355     } else {
17356         *cpThinkOutput = NULLCHAR;
17357     }
17358
17359     /* [AS] Hide thinking from human user */
17360     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17361         *cpThinkOutput = NULLCHAR;
17362         if( thinkOutput[0] != NULLCHAR ) {
17363             int i;
17364
17365             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17366                 cpThinkOutput[i] = '.';
17367             }
17368             cpThinkOutput[i] = NULLCHAR;
17369             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17370         }
17371     }
17372
17373     if (moveNumber == forwardMostMove - 1 &&
17374         gameInfo.resultDetails != NULL) {
17375         if (gameInfo.resultDetails[0] == NULLCHAR) {
17376           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17377         } else {
17378           snprintf(res, MSG_SIZ, " {%s} %s",
17379                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17380         }
17381     } else {
17382         res[0] = NULLCHAR;
17383     }
17384
17385     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17386         DisplayMessage(res, cpThinkOutput);
17387     } else {
17388       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17389                 WhiteOnMove(moveNumber) ? " " : ".. ",
17390                 parseList[moveNumber], res);
17391         DisplayMessage(message, cpThinkOutput);
17392     }
17393 }
17394
17395 void
17396 DisplayComment (int moveNumber, char *text)
17397 {
17398     char title[MSG_SIZ];
17399
17400     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17401       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17402     } else {
17403       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17404               WhiteOnMove(moveNumber) ? " " : ".. ",
17405               parseList[moveNumber]);
17406     }
17407     if (text != NULL && (appData.autoDisplayComment || commentUp))
17408         CommentPopUp(title, text);
17409 }
17410
17411 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17412  * might be busy thinking or pondering.  It can be omitted if your
17413  * gnuchess is configured to stop thinking immediately on any user
17414  * input.  However, that gnuchess feature depends on the FIONREAD
17415  * ioctl, which does not work properly on some flavors of Unix.
17416  */
17417 void
17418 Attention (ChessProgramState *cps)
17419 {
17420 #if ATTENTION
17421     if (!cps->useSigint) return;
17422     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17423     switch (gameMode) {
17424       case MachinePlaysWhite:
17425       case MachinePlaysBlack:
17426       case TwoMachinesPlay:
17427       case IcsPlayingWhite:
17428       case IcsPlayingBlack:
17429       case AnalyzeMode:
17430       case AnalyzeFile:
17431         /* Skip if we know it isn't thinking */
17432         if (!cps->maybeThinking) return;
17433         if (appData.debugMode)
17434           fprintf(debugFP, "Interrupting %s\n", cps->which);
17435         InterruptChildProcess(cps->pr);
17436         cps->maybeThinking = FALSE;
17437         break;
17438       default:
17439         break;
17440     }
17441 #endif /*ATTENTION*/
17442 }
17443
17444 int
17445 CheckFlags ()
17446 {
17447     if (whiteTimeRemaining <= 0) {
17448         if (!whiteFlag) {
17449             whiteFlag = TRUE;
17450             if (appData.icsActive) {
17451                 if (appData.autoCallFlag &&
17452                     gameMode == IcsPlayingBlack && !blackFlag) {
17453                   SendToICS(ics_prefix);
17454                   SendToICS("flag\n");
17455                 }
17456             } else {
17457                 if (blackFlag) {
17458                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17459                 } else {
17460                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17461                     if (appData.autoCallFlag) {
17462                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17463                         return TRUE;
17464                     }
17465                 }
17466             }
17467         }
17468     }
17469     if (blackTimeRemaining <= 0) {
17470         if (!blackFlag) {
17471             blackFlag = TRUE;
17472             if (appData.icsActive) {
17473                 if (appData.autoCallFlag &&
17474                     gameMode == IcsPlayingWhite && !whiteFlag) {
17475                   SendToICS(ics_prefix);
17476                   SendToICS("flag\n");
17477                 }
17478             } else {
17479                 if (whiteFlag) {
17480                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17481                 } else {
17482                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17483                     if (appData.autoCallFlag) {
17484                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17485                         return TRUE;
17486                     }
17487                 }
17488             }
17489         }
17490     }
17491     return FALSE;
17492 }
17493
17494 void
17495 CheckTimeControl ()
17496 {
17497     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17498         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17499
17500     /*
17501      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17502      */
17503     if ( !WhiteOnMove(forwardMostMove) ) {
17504         /* White made time control */
17505         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17506         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17507         /* [HGM] time odds: correct new time quota for time odds! */
17508                                             / WhitePlayer()->timeOdds;
17509         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17510     } else {
17511         lastBlack -= blackTimeRemaining;
17512         /* Black made time control */
17513         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17514                                             / WhitePlayer()->other->timeOdds;
17515         lastWhite = whiteTimeRemaining;
17516     }
17517 }
17518
17519 void
17520 DisplayBothClocks ()
17521 {
17522     int wom = gameMode == EditPosition ?
17523       !blackPlaysFirst : WhiteOnMove(currentMove);
17524     DisplayWhiteClock(whiteTimeRemaining, wom);
17525     DisplayBlackClock(blackTimeRemaining, !wom);
17526 }
17527
17528
17529 /* Timekeeping seems to be a portability nightmare.  I think everyone
17530    has ftime(), but I'm really not sure, so I'm including some ifdefs
17531    to use other calls if you don't.  Clocks will be less accurate if
17532    you have neither ftime nor gettimeofday.
17533 */
17534
17535 /* VS 2008 requires the #include outside of the function */
17536 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17537 #include <sys/timeb.h>
17538 #endif
17539
17540 /* Get the current time as a TimeMark */
17541 void
17542 GetTimeMark (TimeMark *tm)
17543 {
17544 #if HAVE_GETTIMEOFDAY
17545
17546     struct timeval timeVal;
17547     struct timezone timeZone;
17548
17549     gettimeofday(&timeVal, &timeZone);
17550     tm->sec = (long) timeVal.tv_sec;
17551     tm->ms = (int) (timeVal.tv_usec / 1000L);
17552
17553 #else /*!HAVE_GETTIMEOFDAY*/
17554 #if HAVE_FTIME
17555
17556 // include <sys/timeb.h> / moved to just above start of function
17557     struct timeb timeB;
17558
17559     ftime(&timeB);
17560     tm->sec = (long) timeB.time;
17561     tm->ms = (int) timeB.millitm;
17562
17563 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17564     tm->sec = (long) time(NULL);
17565     tm->ms = 0;
17566 #endif
17567 #endif
17568 }
17569
17570 /* Return the difference in milliseconds between two
17571    time marks.  We assume the difference will fit in a long!
17572 */
17573 long
17574 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17575 {
17576     return 1000L*(tm2->sec - tm1->sec) +
17577            (long) (tm2->ms - tm1->ms);
17578 }
17579
17580
17581 /*
17582  * Code to manage the game clocks.
17583  *
17584  * In tournament play, black starts the clock and then white makes a move.
17585  * We give the human user a slight advantage if he is playing white---the
17586  * clocks don't run until he makes his first move, so it takes zero time.
17587  * Also, we don't account for network lag, so we could get out of sync
17588  * with GNU Chess's clock -- but then, referees are always right.
17589  */
17590
17591 static TimeMark tickStartTM;
17592 static long intendedTickLength;
17593
17594 long
17595 NextTickLength (long timeRemaining)
17596 {
17597     long nominalTickLength, nextTickLength;
17598
17599     if (timeRemaining > 0L && timeRemaining <= 10000L)
17600       nominalTickLength = 100L;
17601     else
17602       nominalTickLength = 1000L;
17603     nextTickLength = timeRemaining % nominalTickLength;
17604     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17605
17606     return nextTickLength;
17607 }
17608
17609 /* Adjust clock one minute up or down */
17610 void
17611 AdjustClock (Boolean which, int dir)
17612 {
17613     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17614     if(which) blackTimeRemaining += 60000*dir;
17615     else      whiteTimeRemaining += 60000*dir;
17616     DisplayBothClocks();
17617     adjustedClock = TRUE;
17618 }
17619
17620 /* Stop clocks and reset to a fresh time control */
17621 void
17622 ResetClocks ()
17623 {
17624     (void) StopClockTimer();
17625     if (appData.icsActive) {
17626         whiteTimeRemaining = blackTimeRemaining = 0;
17627     } else if (searchTime) {
17628         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17629         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17630     } else { /* [HGM] correct new time quote for time odds */
17631         whiteTC = blackTC = fullTimeControlString;
17632         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17633         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17634     }
17635     if (whiteFlag || blackFlag) {
17636         DisplayTitle("");
17637         whiteFlag = blackFlag = FALSE;
17638     }
17639     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17640     DisplayBothClocks();
17641     adjustedClock = FALSE;
17642 }
17643
17644 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17645
17646 /* Decrement running clock by amount of time that has passed */
17647 void
17648 DecrementClocks ()
17649 {
17650     long timeRemaining;
17651     long lastTickLength, fudge;
17652     TimeMark now;
17653
17654     if (!appData.clockMode) return;
17655     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17656
17657     GetTimeMark(&now);
17658
17659     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17660
17661     /* Fudge if we woke up a little too soon */
17662     fudge = intendedTickLength - lastTickLength;
17663     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17664
17665     if (WhiteOnMove(forwardMostMove)) {
17666         if(whiteNPS >= 0) lastTickLength = 0;
17667         timeRemaining = whiteTimeRemaining -= lastTickLength;
17668         if(timeRemaining < 0 && !appData.icsActive) {
17669             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17670             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17671                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17672                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17673             }
17674         }
17675         DisplayWhiteClock(whiteTimeRemaining - fudge,
17676                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17677     } else {
17678         if(blackNPS >= 0) lastTickLength = 0;
17679         timeRemaining = blackTimeRemaining -= lastTickLength;
17680         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17681             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17682             if(suddenDeath) {
17683                 blackStartMove = forwardMostMove;
17684                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17685             }
17686         }
17687         DisplayBlackClock(blackTimeRemaining - fudge,
17688                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17689     }
17690     if (CheckFlags()) return;
17691
17692     if(twoBoards) { // count down secondary board's clocks as well
17693         activePartnerTime -= lastTickLength;
17694         partnerUp = 1;
17695         if(activePartner == 'W')
17696             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17697         else
17698             DisplayBlackClock(activePartnerTime, TRUE);
17699         partnerUp = 0;
17700     }
17701
17702     tickStartTM = now;
17703     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17704     StartClockTimer(intendedTickLength);
17705
17706     /* if the time remaining has fallen below the alarm threshold, sound the
17707      * alarm. if the alarm has sounded and (due to a takeback or time control
17708      * with increment) the time remaining has increased to a level above the
17709      * threshold, reset the alarm so it can sound again.
17710      */
17711
17712     if (appData.icsActive && appData.icsAlarm) {
17713
17714         /* make sure we are dealing with the user's clock */
17715         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17716                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17717            )) return;
17718
17719         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17720             alarmSounded = FALSE;
17721         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17722             PlayAlarmSound();
17723             alarmSounded = TRUE;
17724         }
17725     }
17726 }
17727
17728
17729 /* A player has just moved, so stop the previously running
17730    clock and (if in clock mode) start the other one.
17731    We redisplay both clocks in case we're in ICS mode, because
17732    ICS gives us an update to both clocks after every move.
17733    Note that this routine is called *after* forwardMostMove
17734    is updated, so the last fractional tick must be subtracted
17735    from the color that is *not* on move now.
17736 */
17737 void
17738 SwitchClocks (int newMoveNr)
17739 {
17740     long lastTickLength;
17741     TimeMark now;
17742     int flagged = FALSE;
17743
17744     GetTimeMark(&now);
17745
17746     if (StopClockTimer() && appData.clockMode) {
17747         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17748         if (!WhiteOnMove(forwardMostMove)) {
17749             if(blackNPS >= 0) lastTickLength = 0;
17750             blackTimeRemaining -= lastTickLength;
17751            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17752 //         if(pvInfoList[forwardMostMove].time == -1)
17753                  pvInfoList[forwardMostMove].time =               // use GUI time
17754                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17755         } else {
17756            if(whiteNPS >= 0) lastTickLength = 0;
17757            whiteTimeRemaining -= lastTickLength;
17758            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17759 //         if(pvInfoList[forwardMostMove].time == -1)
17760                  pvInfoList[forwardMostMove].time =
17761                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17762         }
17763         flagged = CheckFlags();
17764     }
17765     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17766     CheckTimeControl();
17767
17768     if (flagged || !appData.clockMode) return;
17769
17770     switch (gameMode) {
17771       case MachinePlaysBlack:
17772       case MachinePlaysWhite:
17773       case BeginningOfGame:
17774         if (pausing) return;
17775         break;
17776
17777       case EditGame:
17778       case PlayFromGameFile:
17779       case IcsExamining:
17780         return;
17781
17782       default:
17783         break;
17784     }
17785
17786     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17787         if(WhiteOnMove(forwardMostMove))
17788              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17789         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17790     }
17791
17792     tickStartTM = now;
17793     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17794       whiteTimeRemaining : blackTimeRemaining);
17795     StartClockTimer(intendedTickLength);
17796 }
17797
17798
17799 /* Stop both clocks */
17800 void
17801 StopClocks ()
17802 {
17803     long lastTickLength;
17804     TimeMark now;
17805
17806     if (!StopClockTimer()) return;
17807     if (!appData.clockMode) return;
17808
17809     GetTimeMark(&now);
17810
17811     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17812     if (WhiteOnMove(forwardMostMove)) {
17813         if(whiteNPS >= 0) lastTickLength = 0;
17814         whiteTimeRemaining -= lastTickLength;
17815         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17816     } else {
17817         if(blackNPS >= 0) lastTickLength = 0;
17818         blackTimeRemaining -= lastTickLength;
17819         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17820     }
17821     CheckFlags();
17822 }
17823
17824 /* Start clock of player on move.  Time may have been reset, so
17825    if clock is already running, stop and restart it. */
17826 void
17827 StartClocks ()
17828 {
17829     (void) StopClockTimer(); /* in case it was running already */
17830     DisplayBothClocks();
17831     if (CheckFlags()) return;
17832
17833     if (!appData.clockMode) return;
17834     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17835
17836     GetTimeMark(&tickStartTM);
17837     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17838       whiteTimeRemaining : blackTimeRemaining);
17839
17840    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17841     whiteNPS = blackNPS = -1;
17842     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17843        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17844         whiteNPS = first.nps;
17845     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17846        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17847         blackNPS = first.nps;
17848     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17849         whiteNPS = second.nps;
17850     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17851         blackNPS = second.nps;
17852     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17853
17854     StartClockTimer(intendedTickLength);
17855 }
17856
17857 char *
17858 TimeString (long ms)
17859 {
17860     long second, minute, hour, day;
17861     char *sign = "";
17862     static char buf[32];
17863
17864     if (ms > 0 && ms <= 9900) {
17865       /* convert milliseconds to tenths, rounding up */
17866       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17867
17868       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17869       return buf;
17870     }
17871
17872     /* convert milliseconds to seconds, rounding up */
17873     /* use floating point to avoid strangeness of integer division
17874        with negative dividends on many machines */
17875     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17876
17877     if (second < 0) {
17878         sign = "-";
17879         second = -second;
17880     }
17881
17882     day = second / (60 * 60 * 24);
17883     second = second % (60 * 60 * 24);
17884     hour = second / (60 * 60);
17885     second = second % (60 * 60);
17886     minute = second / 60;
17887     second = second % 60;
17888
17889     if (day > 0)
17890       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17891               sign, day, hour, minute, second);
17892     else if (hour > 0)
17893       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17894     else
17895       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17896
17897     return buf;
17898 }
17899
17900
17901 /*
17902  * This is necessary because some C libraries aren't ANSI C compliant yet.
17903  */
17904 char *
17905 StrStr (char *string, char *match)
17906 {
17907     int i, length;
17908
17909     length = strlen(match);
17910
17911     for (i = strlen(string) - length; i >= 0; i--, string++)
17912       if (!strncmp(match, string, length))
17913         return string;
17914
17915     return NULL;
17916 }
17917
17918 char *
17919 StrCaseStr (char *string, char *match)
17920 {
17921     int i, j, length;
17922
17923     length = strlen(match);
17924
17925     for (i = strlen(string) - length; i >= 0; i--, string++) {
17926         for (j = 0; j < length; j++) {
17927             if (ToLower(match[j]) != ToLower(string[j]))
17928               break;
17929         }
17930         if (j == length) return string;
17931     }
17932
17933     return NULL;
17934 }
17935
17936 #ifndef _amigados
17937 int
17938 StrCaseCmp (char *s1, char *s2)
17939 {
17940     char c1, c2;
17941
17942     for (;;) {
17943         c1 = ToLower(*s1++);
17944         c2 = ToLower(*s2++);
17945         if (c1 > c2) return 1;
17946         if (c1 < c2) return -1;
17947         if (c1 == NULLCHAR) return 0;
17948     }
17949 }
17950
17951
17952 int
17953 ToLower (int c)
17954 {
17955     return isupper(c) ? tolower(c) : c;
17956 }
17957
17958
17959 int
17960 ToUpper (int c)
17961 {
17962     return islower(c) ? toupper(c) : c;
17963 }
17964 #endif /* !_amigados    */
17965
17966 char *
17967 StrSave (char *s)
17968 {
17969   char *ret;
17970
17971   if ((ret = (char *) malloc(strlen(s) + 1)))
17972     {
17973       safeStrCpy(ret, s, strlen(s)+1);
17974     }
17975   return ret;
17976 }
17977
17978 char *
17979 StrSavePtr (char *s, char **savePtr)
17980 {
17981     if (*savePtr) {
17982         free(*savePtr);
17983     }
17984     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17985       safeStrCpy(*savePtr, s, strlen(s)+1);
17986     }
17987     return(*savePtr);
17988 }
17989
17990 char *
17991 PGNDate ()
17992 {
17993     time_t clock;
17994     struct tm *tm;
17995     char buf[MSG_SIZ];
17996
17997     clock = time((time_t *)NULL);
17998     tm = localtime(&clock);
17999     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18000             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18001     return StrSave(buf);
18002 }
18003
18004
18005 char *
18006 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18007 {
18008     int i, j, fromX, fromY, toX, toY;
18009     int whiteToPlay, haveRights = nrCastlingRights;
18010     char buf[MSG_SIZ];
18011     char *p, *q;
18012     int emptycount;
18013     ChessSquare piece;
18014
18015     whiteToPlay = (gameMode == EditPosition) ?
18016       !blackPlaysFirst : (move % 2 == 0);
18017     p = buf;
18018
18019     /* Piece placement data */
18020     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18021         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18022         emptycount = 0;
18023         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18024             if (boards[move][i][j] == EmptySquare) {
18025                 emptycount++;
18026             } else { ChessSquare piece = boards[move][i][j];
18027                 if (emptycount > 0) {
18028                     if(emptycount<10) /* [HGM] can be >= 10 */
18029                         *p++ = '0' + emptycount;
18030                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18031                     emptycount = 0;
18032                 }
18033                 if(PieceToChar(piece) == '+') {
18034                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18035                     *p++ = '+';
18036                     piece = (ChessSquare)(CHUDEMOTED(piece));
18037                 }
18038                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18039                 if(*p = PieceSuffix(piece)) p++;
18040                 if(p[-1] == '~') {
18041                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18042                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18043                     *p++ = '~';
18044                 }
18045             }
18046         }
18047         if (emptycount > 0) {
18048             if(emptycount<10) /* [HGM] can be >= 10 */
18049                 *p++ = '0' + emptycount;
18050             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18051             emptycount = 0;
18052         }
18053         *p++ = '/';
18054     }
18055     *(p - 1) = ' ';
18056
18057     /* [HGM] print Crazyhouse or Shogi holdings */
18058     if( gameInfo.holdingsWidth ) {
18059         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18060         q = p;
18061         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18062             piece = boards[move][i][BOARD_WIDTH-1];
18063             if( piece != EmptySquare )
18064               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18065                   *p++ = PieceToChar(piece);
18066         }
18067         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18068             piece = boards[move][BOARD_HEIGHT-i-1][0];
18069             if( piece != EmptySquare )
18070               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18071                   *p++ = PieceToChar(piece);
18072         }
18073
18074         if( q == p ) *p++ = '-';
18075         *p++ = ']';
18076         *p++ = ' ';
18077     }
18078
18079     /* Active color */
18080     *p++ = whiteToPlay ? 'w' : 'b';
18081     *p++ = ' ';
18082
18083   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18084     haveRights = 0; q = p;
18085     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18086       piece = boards[move][0][i];
18087       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18088         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18089       }
18090     }
18091     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18092       piece = boards[move][BOARD_HEIGHT-1][i];
18093       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18094         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18095       }
18096     }
18097     if(p == q) *p++ = '-';
18098     *p++ = ' ';
18099   }
18100
18101   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18102     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18103   } else {
18104   if(haveRights) {
18105      int handW=0, handB=0;
18106      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18107         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18108         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18109      }
18110      q = p;
18111      if(appData.fischerCastling) {
18112         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18113            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18114                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18115         } else {
18116        /* [HGM] write directly from rights */
18117            if(boards[move][CASTLING][2] != NoRights &&
18118               boards[move][CASTLING][0] != NoRights   )
18119                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18120            if(boards[move][CASTLING][2] != NoRights &&
18121               boards[move][CASTLING][1] != NoRights   )
18122                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18123         }
18124         if(handB) {
18125            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18126                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18127         } else {
18128            if(boards[move][CASTLING][5] != NoRights &&
18129               boards[move][CASTLING][3] != NoRights   )
18130                 *p++ = boards[move][CASTLING][3] + AAA;
18131            if(boards[move][CASTLING][5] != NoRights &&
18132               boards[move][CASTLING][4] != NoRights   )
18133                 *p++ = boards[move][CASTLING][4] + AAA;
18134         }
18135      } else {
18136
18137         /* [HGM] write true castling rights */
18138         if( nrCastlingRights == 6 ) {
18139             int q, k=0;
18140             if(boards[move][CASTLING][0] != NoRights &&
18141                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18142             q = (boards[move][CASTLING][1] != NoRights &&
18143                  boards[move][CASTLING][2] != NoRights  );
18144             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18145                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18146                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18147                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18148             }
18149             if(q) *p++ = 'Q';
18150             k = 0;
18151             if(boards[move][CASTLING][3] != NoRights &&
18152                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18153             q = (boards[move][CASTLING][4] != NoRights &&
18154                  boards[move][CASTLING][5] != NoRights  );
18155             if(handB) {
18156                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18157                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18158                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18159             }
18160             if(q) *p++ = 'q';
18161         }
18162      }
18163      if (q == p) *p++ = '-'; /* No castling rights */
18164      *p++ = ' ';
18165   }
18166
18167   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18168      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18169      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18170     /* En passant target square */
18171     if (move > backwardMostMove) {
18172         fromX = moveList[move - 1][0] - AAA;
18173         fromY = moveList[move - 1][1] - ONE;
18174         toX = moveList[move - 1][2] - AAA;
18175         toY = moveList[move - 1][3] - ONE;
18176         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18177             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18178             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18179             fromX == toX) {
18180             /* 2-square pawn move just happened */
18181             *p++ = toX + AAA;
18182             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18183         } else {
18184             *p++ = '-';
18185         }
18186     } else if(move == backwardMostMove) {
18187         // [HGM] perhaps we should always do it like this, and forget the above?
18188         if((signed char)boards[move][EP_STATUS] >= 0) {
18189             *p++ = boards[move][EP_STATUS] + AAA;
18190             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18191         } else {
18192             *p++ = '-';
18193         }
18194     } else {
18195         *p++ = '-';
18196     }
18197     *p++ = ' ';
18198   }
18199   }
18200
18201     if(moveCounts)
18202     {   int i = 0, j=move;
18203
18204         /* [HGM] find reversible plies */
18205         if (appData.debugMode) { int k;
18206             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18207             for(k=backwardMostMove; k<=forwardMostMove; k++)
18208                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18209
18210         }
18211
18212         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18213         if( j == backwardMostMove ) i += initialRulePlies;
18214         sprintf(p, "%d ", i);
18215         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18216
18217         /* Fullmove number */
18218         sprintf(p, "%d", (move / 2) + 1);
18219     } else *--p = NULLCHAR;
18220
18221     return StrSave(buf);
18222 }
18223
18224 Boolean
18225 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18226 {
18227     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18228     char *p, c;
18229     int emptycount, virgin[BOARD_FILES];
18230     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18231
18232     p = fen;
18233
18234     /* Piece placement data */
18235     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18236         j = 0;
18237         for (;;) {
18238             if (*p == '/' || *p == ' ' || *p == '[' ) {
18239                 if(j > w) w = j;
18240                 emptycount = gameInfo.boardWidth - j;
18241                 while (emptycount--)
18242                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18243                 if (*p == '/') p++;
18244                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18245                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18246                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18247                     }
18248                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18249                 }
18250                 break;
18251 #if(BOARD_FILES >= 10)*0
18252             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18253                 p++; emptycount=10;
18254                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18255                 while (emptycount--)
18256                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18257 #endif
18258             } else if (*p == '*') {
18259                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18260             } else if (isdigit(*p)) {
18261                 emptycount = *p++ - '0';
18262                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18263                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18264                 while (emptycount--)
18265                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18266             } else if (*p == '<') {
18267                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18268                 else if (i != 0 || !shuffle) return FALSE;
18269                 p++;
18270             } else if (shuffle && *p == '>') {
18271                 p++; // for now ignore closing shuffle range, and assume rank-end
18272             } else if (*p == '?') {
18273                 if (j >= gameInfo.boardWidth) return FALSE;
18274                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18275                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18276             } else if (*p == '+' || isalpha(*p)) {
18277                 char *q, *s = SUFFIXES;
18278                 if (j >= gameInfo.boardWidth) return FALSE;
18279                 if(*p=='+') {
18280                     char c = *++p;
18281                     if(q = strchr(s, p[1])) p++;
18282                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18283                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18284                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18285                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18286                 } else {
18287                     char c = *p++;
18288                     if(q = strchr(s, *p)) p++;
18289                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18290                 }
18291
18292                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18293                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18294                     piece = (ChessSquare) (PROMOTED(piece));
18295                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18296                     p++;
18297                 }
18298                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18299                 if(piece == king) wKingRank = i;
18300                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18301             } else {
18302                 return FALSE;
18303             }
18304         }
18305     }
18306     while (*p == '/' || *p == ' ') p++;
18307
18308     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18309
18310     /* [HGM] by default clear Crazyhouse holdings, if present */
18311     if(gameInfo.holdingsWidth) {
18312        for(i=0; i<BOARD_HEIGHT; i++) {
18313            board[i][0]             = EmptySquare; /* black holdings */
18314            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18315            board[i][1]             = (ChessSquare) 0; /* black counts */
18316            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18317        }
18318     }
18319
18320     /* [HGM] look for Crazyhouse holdings here */
18321     while(*p==' ') p++;
18322     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18323         int swap=0, wcnt=0, bcnt=0;
18324         if(*p == '[') p++;
18325         if(*p == '<') swap++, p++;
18326         if(*p == '-' ) p++; /* empty holdings */ else {
18327             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18328             /* if we would allow FEN reading to set board size, we would   */
18329             /* have to add holdings and shift the board read so far here   */
18330             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18331                 p++;
18332                 if((int) piece >= (int) BlackPawn ) {
18333                     i = (int)piece - (int)BlackPawn;
18334                     i = PieceToNumber((ChessSquare)i);
18335                     if( i >= gameInfo.holdingsSize ) return FALSE;
18336                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18337                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18338                     bcnt++;
18339                 } else {
18340                     i = (int)piece - (int)WhitePawn;
18341                     i = PieceToNumber((ChessSquare)i);
18342                     if( i >= gameInfo.holdingsSize ) return FALSE;
18343                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18344                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18345                     wcnt++;
18346                 }
18347             }
18348             if(subst) { // substitute back-rank question marks by holdings pieces
18349                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18350                     int k, m, n = bcnt + 1;
18351                     if(board[0][j] == ClearBoard) {
18352                         if(!wcnt) return FALSE;
18353                         n = rand() % wcnt;
18354                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18355                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18356                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18357                             break;
18358                         }
18359                     }
18360                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18361                         if(!bcnt) return FALSE;
18362                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18363                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18364                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18365                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18366                             break;
18367                         }
18368                     }
18369                 }
18370                 subst = 0;
18371             }
18372         }
18373         if(*p == ']') p++;
18374     }
18375
18376     if(subst) return FALSE; // substitution requested, but no holdings
18377
18378     while(*p == ' ') p++;
18379
18380     /* Active color */
18381     c = *p++;
18382     if(appData.colorNickNames) {
18383       if( c == appData.colorNickNames[0] ) c = 'w'; else
18384       if( c == appData.colorNickNames[1] ) c = 'b';
18385     }
18386     switch (c) {
18387       case 'w':
18388         *blackPlaysFirst = FALSE;
18389         break;
18390       case 'b':
18391         *blackPlaysFirst = TRUE;
18392         break;
18393       default:
18394         return FALSE;
18395     }
18396
18397     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18398     /* return the extra info in global variiables             */
18399
18400     while(*p==' ') p++;
18401
18402     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18403         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18404         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18405     }
18406
18407     /* set defaults in case FEN is incomplete */
18408     board[EP_STATUS] = EP_UNKNOWN;
18409     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18410     for(i=0; i<nrCastlingRights; i++ ) {
18411         board[CASTLING][i] =
18412             appData.fischerCastling ? NoRights : initialRights[i];
18413     }   /* assume possible unless obviously impossible */
18414     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18415     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18416     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18417                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18418     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18419     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18420     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18421                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18422     FENrulePlies = 0;
18423
18424     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18425       char *q = p;
18426       int w=0, b=0;
18427       while(isalpha(*p)) {
18428         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18429         if(islower(*p)) b |= 1 << (*p++ - 'a');
18430       }
18431       if(*p == '-') p++;
18432       if(p != q) {
18433         board[TOUCHED_W] = ~w;
18434         board[TOUCHED_B] = ~b;
18435         while(*p == ' ') p++;
18436       }
18437     } else
18438
18439     if(nrCastlingRights) {
18440       int fischer = 0;
18441       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18442       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18443           /* castling indicator present, so default becomes no castlings */
18444           for(i=0; i<nrCastlingRights; i++ ) {
18445                  board[CASTLING][i] = NoRights;
18446           }
18447       }
18448       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18449              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18450              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18451              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18452         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18453
18454         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18455             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18456             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18457         }
18458         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18459             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18460         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18461                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18462         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18463                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18464         switch(c) {
18465           case'K':
18466               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18467               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18468               board[CASTLING][2] = whiteKingFile;
18469               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18470               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18471               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18472               break;
18473           case'Q':
18474               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18475               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18476               board[CASTLING][2] = whiteKingFile;
18477               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18478               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18479               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18480               break;
18481           case'k':
18482               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18483               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18484               board[CASTLING][5] = blackKingFile;
18485               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18486               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18487               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18488               break;
18489           case'q':
18490               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18491               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18492               board[CASTLING][5] = blackKingFile;
18493               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18494               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18495               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18496           case '-':
18497               break;
18498           default: /* FRC castlings */
18499               if(c >= 'a') { /* black rights */
18500                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18501                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18502                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18503                   if(i == BOARD_RGHT) break;
18504                   board[CASTLING][5] = i;
18505                   c -= AAA;
18506                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18507                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18508                   if(c > i)
18509                       board[CASTLING][3] = c;
18510                   else
18511                       board[CASTLING][4] = c;
18512               } else { /* white rights */
18513                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18514                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18515                     if(board[0][i] == WhiteKing) break;
18516                   if(i == BOARD_RGHT) break;
18517                   board[CASTLING][2] = i;
18518                   c -= AAA - 'a' + 'A';
18519                   if(board[0][c] >= WhiteKing) break;
18520                   if(c > i)
18521                       board[CASTLING][0] = c;
18522                   else
18523                       board[CASTLING][1] = c;
18524               }
18525         }
18526       }
18527       for(i=0; i<nrCastlingRights; i++)
18528         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18529       if(gameInfo.variant == VariantSChess)
18530         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18531       if(fischer && shuffle) appData.fischerCastling = TRUE;
18532     if (appData.debugMode) {
18533         fprintf(debugFP, "FEN castling rights:");
18534         for(i=0; i<nrCastlingRights; i++)
18535         fprintf(debugFP, " %d", board[CASTLING][i]);
18536         fprintf(debugFP, "\n");
18537     }
18538
18539       while(*p==' ') p++;
18540     }
18541
18542     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18543
18544     /* read e.p. field in games that know e.p. capture */
18545     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18546        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18547        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18548       if(*p=='-') {
18549         p++; board[EP_STATUS] = EP_NONE;
18550       } else {
18551          char c = *p++ - AAA;
18552
18553          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18554          if(*p >= '0' && *p <='9') p++;
18555          board[EP_STATUS] = c;
18556       }
18557     }
18558
18559
18560     if(sscanf(p, "%d", &i) == 1) {
18561         FENrulePlies = i; /* 50-move ply counter */
18562         /* (The move number is still ignored)    */
18563     }
18564
18565     return TRUE;
18566 }
18567
18568 void
18569 EditPositionPasteFEN (char *fen)
18570 {
18571   if (fen != NULL) {
18572     Board initial_position;
18573
18574     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18575       DisplayError(_("Bad FEN position in clipboard"), 0);
18576       return ;
18577     } else {
18578       int savedBlackPlaysFirst = blackPlaysFirst;
18579       EditPositionEvent();
18580       blackPlaysFirst = savedBlackPlaysFirst;
18581       CopyBoard(boards[0], initial_position);
18582       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18583       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18584       DisplayBothClocks();
18585       DrawPosition(FALSE, boards[currentMove]);
18586     }
18587   }
18588 }
18589
18590 static char cseq[12] = "\\   ";
18591
18592 Boolean
18593 set_cont_sequence (char *new_seq)
18594 {
18595     int len;
18596     Boolean ret;
18597
18598     // handle bad attempts to set the sequence
18599         if (!new_seq)
18600                 return 0; // acceptable error - no debug
18601
18602     len = strlen(new_seq);
18603     ret = (len > 0) && (len < sizeof(cseq));
18604     if (ret)
18605       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18606     else if (appData.debugMode)
18607       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18608     return ret;
18609 }
18610
18611 /*
18612     reformat a source message so words don't cross the width boundary.  internal
18613     newlines are not removed.  returns the wrapped size (no null character unless
18614     included in source message).  If dest is NULL, only calculate the size required
18615     for the dest buffer.  lp argument indicats line position upon entry, and it's
18616     passed back upon exit.
18617 */
18618 int
18619 wrap (char *dest, char *src, int count, int width, int *lp)
18620 {
18621     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18622
18623     cseq_len = strlen(cseq);
18624     old_line = line = *lp;
18625     ansi = len = clen = 0;
18626
18627     for (i=0; i < count; i++)
18628     {
18629         if (src[i] == '\033')
18630             ansi = 1;
18631
18632         // if we hit the width, back up
18633         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18634         {
18635             // store i & len in case the word is too long
18636             old_i = i, old_len = len;
18637
18638             // find the end of the last word
18639             while (i && src[i] != ' ' && src[i] != '\n')
18640             {
18641                 i--;
18642                 len--;
18643             }
18644
18645             // word too long?  restore i & len before splitting it
18646             if ((old_i-i+clen) >= width)
18647             {
18648                 i = old_i;
18649                 len = old_len;
18650             }
18651
18652             // extra space?
18653             if (i && src[i-1] == ' ')
18654                 len--;
18655
18656             if (src[i] != ' ' && src[i] != '\n')
18657             {
18658                 i--;
18659                 if (len)
18660                     len--;
18661             }
18662
18663             // now append the newline and continuation sequence
18664             if (dest)
18665                 dest[len] = '\n';
18666             len++;
18667             if (dest)
18668                 strncpy(dest+len, cseq, cseq_len);
18669             len += cseq_len;
18670             line = cseq_len;
18671             clen = cseq_len;
18672             continue;
18673         }
18674
18675         if (dest)
18676             dest[len] = src[i];
18677         len++;
18678         if (!ansi)
18679             line++;
18680         if (src[i] == '\n')
18681             line = 0;
18682         if (src[i] == 'm')
18683             ansi = 0;
18684     }
18685     if (dest && appData.debugMode)
18686     {
18687         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18688             count, width, line, len, *lp);
18689         show_bytes(debugFP, src, count);
18690         fprintf(debugFP, "\ndest: ");
18691         show_bytes(debugFP, dest, len);
18692         fprintf(debugFP, "\n");
18693     }
18694     *lp = dest ? line : old_line;
18695
18696     return len;
18697 }
18698
18699 // [HGM] vari: routines for shelving variations
18700 Boolean modeRestore = FALSE;
18701
18702 void
18703 PushInner (int firstMove, int lastMove)
18704 {
18705         int i, j, nrMoves = lastMove - firstMove;
18706
18707         // push current tail of game on stack
18708         savedResult[storedGames] = gameInfo.result;
18709         savedDetails[storedGames] = gameInfo.resultDetails;
18710         gameInfo.resultDetails = NULL;
18711         savedFirst[storedGames] = firstMove;
18712         savedLast [storedGames] = lastMove;
18713         savedFramePtr[storedGames] = framePtr;
18714         framePtr -= nrMoves; // reserve space for the boards
18715         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18716             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18717             for(j=0; j<MOVE_LEN; j++)
18718                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18719             for(j=0; j<2*MOVE_LEN; j++)
18720                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18721             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18722             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18723             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18724             pvInfoList[firstMove+i-1].depth = 0;
18725             commentList[framePtr+i] = commentList[firstMove+i];
18726             commentList[firstMove+i] = NULL;
18727         }
18728
18729         storedGames++;
18730         forwardMostMove = firstMove; // truncate game so we can start variation
18731 }
18732
18733 void
18734 PushTail (int firstMove, int lastMove)
18735 {
18736         if(appData.icsActive) { // only in local mode
18737                 forwardMostMove = currentMove; // mimic old ICS behavior
18738                 return;
18739         }
18740         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18741
18742         PushInner(firstMove, lastMove);
18743         if(storedGames == 1) GreyRevert(FALSE);
18744         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18745 }
18746
18747 void
18748 PopInner (Boolean annotate)
18749 {
18750         int i, j, nrMoves;
18751         char buf[8000], moveBuf[20];
18752
18753         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18754         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18755         nrMoves = savedLast[storedGames] - currentMove;
18756         if(annotate) {
18757                 int cnt = 10;
18758                 if(!WhiteOnMove(currentMove))
18759                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18760                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18761                 for(i=currentMove; i<forwardMostMove; i++) {
18762                         if(WhiteOnMove(i))
18763                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18764                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18765                         strcat(buf, moveBuf);
18766                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18767                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18768                 }
18769                 strcat(buf, ")");
18770         }
18771         for(i=1; i<=nrMoves; i++) { // copy last variation back
18772             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18773             for(j=0; j<MOVE_LEN; j++)
18774                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18775             for(j=0; j<2*MOVE_LEN; j++)
18776                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18777             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18778             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18779             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18780             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18781             commentList[currentMove+i] = commentList[framePtr+i];
18782             commentList[framePtr+i] = NULL;
18783         }
18784         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18785         framePtr = savedFramePtr[storedGames];
18786         gameInfo.result = savedResult[storedGames];
18787         if(gameInfo.resultDetails != NULL) {
18788             free(gameInfo.resultDetails);
18789       }
18790         gameInfo.resultDetails = savedDetails[storedGames];
18791         forwardMostMove = currentMove + nrMoves;
18792 }
18793
18794 Boolean
18795 PopTail (Boolean annotate)
18796 {
18797         if(appData.icsActive) return FALSE; // only in local mode
18798         if(!storedGames) return FALSE; // sanity
18799         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18800
18801         PopInner(annotate);
18802         if(currentMove < forwardMostMove) ForwardEvent(); else
18803         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18804
18805         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18806         return TRUE;
18807 }
18808
18809 void
18810 CleanupTail ()
18811 {       // remove all shelved variations
18812         int i;
18813         for(i=0; i<storedGames; i++) {
18814             if(savedDetails[i])
18815                 free(savedDetails[i]);
18816             savedDetails[i] = NULL;
18817         }
18818         for(i=framePtr; i<MAX_MOVES; i++) {
18819                 if(commentList[i]) free(commentList[i]);
18820                 commentList[i] = NULL;
18821         }
18822         framePtr = MAX_MOVES-1;
18823         storedGames = 0;
18824 }
18825
18826 void
18827 LoadVariation (int index, char *text)
18828 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18829         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18830         int level = 0, move;
18831
18832         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18833         // first find outermost bracketing variation
18834         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18835             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18836                 if(*p == '{') wait = '}'; else
18837                 if(*p == '[') wait = ']'; else
18838                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18839                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18840             }
18841             if(*p == wait) wait = NULLCHAR; // closing ]} found
18842             p++;
18843         }
18844         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18845         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18846         end[1] = NULLCHAR; // clip off comment beyond variation
18847         ToNrEvent(currentMove-1);
18848         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18849         // kludge: use ParsePV() to append variation to game
18850         move = currentMove;
18851         ParsePV(start, TRUE, TRUE);
18852         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18853         ClearPremoveHighlights();
18854         CommentPopDown();
18855         ToNrEvent(currentMove+1);
18856 }
18857
18858 void
18859 LoadTheme ()
18860 {
18861     char *p, *q, buf[MSG_SIZ];
18862     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18863         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18864         ParseArgsFromString(buf);
18865         ActivateTheme(TRUE); // also redo colors
18866         return;
18867     }
18868     p = nickName;
18869     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18870     {
18871         int len;
18872         q = appData.themeNames;
18873         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18874       if(appData.useBitmaps) {
18875         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18876                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18877                 appData.liteBackTextureMode,
18878                 appData.darkBackTextureMode );
18879       } else {
18880         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18881                 Col2Text(2),   // lightSquareColor
18882                 Col2Text(3) ); // darkSquareColor
18883       }
18884       if(appData.useBorder) {
18885         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18886                 appData.border);
18887       } else {
18888         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18889       }
18890       if(appData.useFont) {
18891         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18892                 appData.renderPiecesWithFont,
18893                 appData.fontToPieceTable,
18894                 Col2Text(9),    // appData.fontBackColorWhite
18895                 Col2Text(10) ); // appData.fontForeColorBlack
18896       } else {
18897         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18898                 appData.pieceDirectory);
18899         if(!appData.pieceDirectory[0])
18900           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18901                 Col2Text(0),   // whitePieceColor
18902                 Col2Text(1) ); // blackPieceColor
18903       }
18904       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18905                 Col2Text(4),   // highlightSquareColor
18906                 Col2Text(5) ); // premoveHighlightColor
18907         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18908         if(insert != q) insert[-1] = NULLCHAR;
18909         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18910         if(q)   free(q);
18911     }
18912     ActivateTheme(FALSE);
18913 }