Fix sweep promotions to Tokin
[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 SendToProgram P((char *message, ChessProgramState *cps));
196 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
197 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
198                            char *buf, int count, int error));
199 void SendTimeControl P((ChessProgramState *cps,
200                         int mps, long tc, int inc, int sd, int st));
201 char *TimeControlTagValue P((void));
202 void Attention P((ChessProgramState *cps));
203 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
204 int ResurrectChessProgram P((void));
205 void DisplayComment P((int moveNumber, char *text));
206 void DisplayMove P((int moveNumber));
207
208 void ParseGameHistory P((char *game));
209 void ParseBoard12 P((char *string));
210 void KeepAlive P((void));
211 void StartClocks P((void));
212 void SwitchClocks P((int nr));
213 void StopClocks P((void));
214 void ResetClocks P((void));
215 char *PGNDate P((void));
216 void SetGameInfo P((void));
217 int RegisterMove P((void));
218 void MakeRegisteredMove P((void));
219 void TruncateGame P((void));
220 int looking_at P((char *, int *, char *));
221 void CopyPlayerNameIntoFileName P((char **, char *));
222 char *SavePart P((char *));
223 int SaveGameOldStyle P((FILE *));
224 int SaveGamePGN P((FILE *));
225 int CheckFlags P((void));
226 long NextTickLength P((long));
227 void CheckTimeControl P((void));
228 void show_bytes P((FILE *, char *, int));
229 int string_to_rating P((char *str));
230 void ParseFeatures P((char* args, ChessProgramState *cps));
231 void InitBackEnd3 P((void));
232 void FeatureDone P((ChessProgramState* cps, int val));
233 void InitChessProgram P((ChessProgramState *cps, int setup));
234 void OutputKibitz(int window, char *text);
235 int PerpetualChase(int first, int last);
236 int EngineOutputIsUp();
237 void InitDrawingSizes(int x, int y);
238 void NextMatchGame P((void));
239 int NextTourneyGame P((int nr, int *swap));
240 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
241 FILE *WriteTourneyFile P((char *results, FILE *f));
242 void DisplayTwoMachinesTitle P(());
243 static void ExcludeClick P((int index));
244 void ToggleSecond P((void));
245 void PauseEngine P((ChessProgramState *cps));
246 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
247
248 #ifdef WIN32
249        extern void ConsoleCreate();
250 #endif
251
252 ChessProgramState *WhitePlayer();
253 int VerifyDisplayMode P(());
254
255 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
256 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
257 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
258 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
259 void ics_update_width P((int new_width));
260 extern char installDir[MSG_SIZ];
261 VariantClass startVariant; /* [HGM] nicks: initial variant */
262 Boolean abortMatch;
263
264 extern int tinyLayout, smallLayout;
265 ChessProgramStats programStats;
266 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
267 int endPV = -1;
268 static int exiting = 0; /* [HGM] moved to top */
269 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
270 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
271 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
272 int partnerHighlight[2];
273 Boolean partnerBoardValid = 0;
274 char partnerStatus[MSG_SIZ];
275 Boolean partnerUp;
276 Boolean originalFlip;
277 Boolean twoBoards = 0;
278 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
279 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
280 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
281 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
282 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
283 int opponentKibitzes;
284 int lastSavedGame; /* [HGM] save: ID of game */
285 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
286 extern int chatCount;
287 int chattingPartner;
288 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
289 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
290 char lastMsg[MSG_SIZ];
291 char lastTalker[MSG_SIZ];
292 ChessSquare pieceSweep = EmptySquare;
293 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
294 int promoDefaultAltered;
295 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
296 static int initPing = -1;
297 int border;       /* [HGM] width of board rim, needed to size seek graph  */
298 char bestMove[MSG_SIZ];
299 int solvingTime, totalTime;
300
301 /* States for ics_getting_history */
302 #define H_FALSE 0
303 #define H_REQUESTED 1
304 #define H_GOT_REQ_HEADER 2
305 #define H_GOT_UNREQ_HEADER 3
306 #define H_GETTING_MOVES 4
307 #define H_GOT_UNWANTED_HEADER 5
308
309 /* whosays values for GameEnds */
310 #define GE_ICS 0
311 #define GE_ENGINE 1
312 #define GE_PLAYER 2
313 #define GE_FILE 3
314 #define GE_XBOARD 4
315 #define GE_ENGINE1 5
316 #define GE_ENGINE2 6
317
318 /* Maximum number of games in a cmail message */
319 #define CMAIL_MAX_GAMES 20
320
321 /* Different types of move when calling RegisterMove */
322 #define CMAIL_MOVE   0
323 #define CMAIL_RESIGN 1
324 #define CMAIL_DRAW   2
325 #define CMAIL_ACCEPT 3
326
327 /* Different types of result to remember for each game */
328 #define CMAIL_NOT_RESULT 0
329 #define CMAIL_OLD_RESULT 1
330 #define CMAIL_NEW_RESULT 2
331
332 /* Telnet protocol constants */
333 #define TN_WILL 0373
334 #define TN_WONT 0374
335 #define TN_DO   0375
336 #define TN_DONT 0376
337 #define TN_IAC  0377
338 #define TN_ECHO 0001
339 #define TN_SGA  0003
340 #define TN_PORT 23
341
342 char*
343 safeStrCpy (char *dst, const char *src, size_t count)
344 { // [HGM] made safe
345   int i;
346   assert( dst != NULL );
347   assert( src != NULL );
348   assert( count > 0 );
349
350   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
351   if(  i == count && dst[count-1] != NULLCHAR)
352     {
353       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
354       if(appData.debugMode)
355         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
356     }
357
358   return dst;
359 }
360
361 /* Some compiler can't cast u64 to double
362  * This function do the job for us:
363
364  * We use the highest bit for cast, this only
365  * works if the highest bit is not
366  * in use (This should not happen)
367  *
368  * We used this for all compiler
369  */
370 double
371 u64ToDouble (u64 value)
372 {
373   double r;
374   u64 tmp = value & u64Const(0x7fffffffffffffff);
375   r = (double)(s64)tmp;
376   if (value & u64Const(0x8000000000000000))
377        r +=  9.2233720368547758080e18; /* 2^63 */
378  return r;
379 }
380
381 /* Fake up flags for now, as we aren't keeping track of castling
382    availability yet. [HGM] Change of logic: the flag now only
383    indicates the type of castlings allowed by the rule of the game.
384    The actual rights themselves are maintained in the array
385    castlingRights, as part of the game history, and are not probed
386    by this function.
387  */
388 int
389 PosFlags (index)
390 {
391   int flags = F_ALL_CASTLE_OK;
392   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
393   switch (gameInfo.variant) {
394   case VariantSuicide:
395     flags &= ~F_ALL_CASTLE_OK;
396   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
397     flags |= F_IGNORE_CHECK;
398   case VariantLosers:
399     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
400     break;
401   case VariantAtomic:
402     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
403     break;
404   case VariantKriegspiel:
405     flags |= F_KRIEGSPIEL_CAPTURE;
406     break;
407   case VariantCapaRandom:
408   case VariantFischeRandom:
409     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
410   case VariantNoCastle:
411   case VariantShatranj:
412   case VariantCourier:
413   case VariantMakruk:
414   case VariantASEAN:
415   case VariantGrand:
416     flags &= ~F_ALL_CASTLE_OK;
417     break;
418   case VariantChu:
419   case VariantChuChess:
420   case VariantLion:
421     flags |= F_NULL_MOVE;
422     break;
423   default:
424     break;
425   }
426   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
427   return flags;
428 }
429
430 FILE *gameFileFP, *debugFP, *serverFP;
431 char *currentDebugFile; // [HGM] debug split: to remember name
432
433 /*
434     [AS] Note: sometimes, the sscanf() function is used to parse the input
435     into a fixed-size buffer. Because of this, we must be prepared to
436     receive strings as long as the size of the input buffer, which is currently
437     set to 4K for Windows and 8K for the rest.
438     So, we must either allocate sufficiently large buffers here, or
439     reduce the size of the input buffer in the input reading part.
440 */
441
442 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
443 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
444 char thinkOutput1[MSG_SIZ*10];
445 char promoRestrict[MSG_SIZ];
446
447 ChessProgramState first, second, pairing;
448
449 /* premove variables */
450 int premoveToX = 0;
451 int premoveToY = 0;
452 int premoveFromX = 0;
453 int premoveFromY = 0;
454 int premovePromoChar = 0;
455 int gotPremove = 0;
456 Boolean alarmSounded;
457 /* end premove variables */
458
459 char *ics_prefix = "$";
460 enum ICS_TYPE ics_type = ICS_GENERIC;
461
462 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
463 int pauseExamForwardMostMove = 0;
464 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
465 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
466 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
467 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
468 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
469 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
470 int whiteFlag = FALSE, blackFlag = FALSE;
471 int userOfferedDraw = FALSE;
472 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
473 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
474 int cmailMoveType[CMAIL_MAX_GAMES];
475 long ics_clock_paused = 0;
476 ProcRef icsPR = NoProc, cmailPR = NoProc;
477 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
478 GameMode gameMode = BeginningOfGame;
479 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
480 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
481 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
482 int hiddenThinkOutputState = 0; /* [AS] */
483 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
484 int adjudicateLossPlies = 6;
485 char white_holding[64], black_holding[64];
486 TimeMark lastNodeCountTime;
487 long lastNodeCount=0;
488 int shiftKey, controlKey; // [HGM] set by mouse handler
489
490 int have_sent_ICS_logon = 0;
491 int movesPerSession;
492 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
493 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
494 Boolean adjustedClock;
495 long timeControl_2; /* [AS] Allow separate time controls */
496 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
497 long timeRemaining[2][MAX_MOVES];
498 int matchGame = 0, nextGame = 0, roundNr = 0;
499 Boolean waitingForGame = FALSE, startingEngine = FALSE;
500 TimeMark programStartTime, pauseStart;
501 char ics_handle[MSG_SIZ];
502 int have_set_title = 0;
503
504 /* animateTraining preserves the state of appData.animate
505  * when Training mode is activated. This allows the
506  * response to be animated when appData.animate == TRUE and
507  * appData.animateDragging == TRUE.
508  */
509 Boolean animateTraining;
510
511 GameInfo gameInfo;
512
513 AppData appData;
514
515 Board boards[MAX_MOVES];
516 /* [HGM] Following 7 needed for accurate legality tests: */
517 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
518 unsigned char initialRights[BOARD_FILES];
519 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
520 int   initialRulePlies, FENrulePlies;
521 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
522 int loadFlag = 0;
523 Boolean shuffleOpenings;
524 int mute; // mute all sounds
525
526 // [HGM] vari: next 12 to save and restore variations
527 #define MAX_VARIATIONS 10
528 int framePtr = MAX_MOVES-1; // points to free stack entry
529 int storedGames = 0;
530 int savedFirst[MAX_VARIATIONS];
531 int savedLast[MAX_VARIATIONS];
532 int savedFramePtr[MAX_VARIATIONS];
533 char *savedDetails[MAX_VARIATIONS];
534 ChessMove savedResult[MAX_VARIATIONS];
535
536 void PushTail P((int firstMove, int lastMove));
537 Boolean PopTail P((Boolean annotate));
538 void PushInner P((int firstMove, int lastMove));
539 void PopInner P((Boolean annotate));
540 void CleanupTail P((void));
541
542 ChessSquare  FIDEArray[2][BOARD_FILES] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
546         BlackKing, BlackBishop, BlackKnight, BlackRook }
547 };
548
549 ChessSquare twoKingsArray[2][BOARD_FILES] = {
550     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
551         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
553         BlackKing, BlackKing, BlackKnight, BlackRook }
554 };
555
556 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
558         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
559     { BlackRook, BlackMan, BlackBishop, BlackQueen,
560         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
561 };
562
563 ChessSquare SpartanArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
566     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
567         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
568 };
569
570 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
571     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
574         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
575 };
576
577 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
578     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
579         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
581         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 };
583
584 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
585     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
586         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackMan, BlackFerz,
588         BlackKing, BlackMan, BlackKnight, BlackRook }
589 };
590
591 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
592     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
593         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackMan, BlackFerz,
595         BlackKing, BlackMan, BlackKnight, BlackRook }
596 };
597
598 ChessSquare  lionArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
600         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackLion, BlackBishop, BlackQueen,
602         BlackKing, BlackBishop, BlackKnight, BlackRook }
603 };
604
605
606 #if (BOARD_FILES>=10)
607 ChessSquare ShogiArray[2][BOARD_FILES] = {
608     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
609         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
610     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
611         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
612 };
613
614 ChessSquare XiangqiArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
616         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
618         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
619 };
620
621 ChessSquare CapablancaArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
623         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
625         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
626 };
627
628 ChessSquare GreatArray[2][BOARD_FILES] = {
629     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
630         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
631     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
632         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
633 };
634
635 ChessSquare JanusArray[2][BOARD_FILES] = {
636     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
637         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
638     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
639         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
640 };
641
642 ChessSquare GrandArray[2][BOARD_FILES] = {
643     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
644         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
645     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
646         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
647 };
648
649 ChessSquare ChuChessArray[2][BOARD_FILES] = {
650     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
651         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
652     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
653         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
654 };
655
656 #ifdef GOTHIC
657 ChessSquare GothicArray[2][BOARD_FILES] = {
658     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
659         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
660     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
661         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
662 };
663 #else // !GOTHIC
664 #define GothicArray CapablancaArray
665 #endif // !GOTHIC
666
667 #ifdef FALCON
668 ChessSquare FalconArray[2][BOARD_FILES] = {
669     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
670         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
671     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
672         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
673 };
674 #else // !FALCON
675 #define FalconArray CapablancaArray
676 #endif // !FALCON
677
678 #else // !(BOARD_FILES>=10)
679 #define XiangqiPosition FIDEArray
680 #define CapablancaArray FIDEArray
681 #define GothicArray FIDEArray
682 #define GreatArray FIDEArray
683 #endif // !(BOARD_FILES>=10)
684
685 #if (BOARD_FILES>=12)
686 ChessSquare CourierArray[2][BOARD_FILES] = {
687     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
688         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
689     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
690         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
691 };
692 ChessSquare ChuArray[6][BOARD_FILES] = {
693     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
694       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
695     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
696       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
697     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
698       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
699     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
700       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
701     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
702       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
703     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
704       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
705 };
706 #else // !(BOARD_FILES>=12)
707 #define CourierArray CapablancaArray
708 #define ChuArray CapablancaArray
709 #endif // !(BOARD_FILES>=12)
710
711
712 Board initialPosition;
713
714
715 /* Convert str to a rating. Checks for special cases of "----",
716
717    "++++", etc. Also strips ()'s */
718 int
719 string_to_rating (char *str)
720 {
721   while(*str && !isdigit(*str)) ++str;
722   if (!*str)
723     return 0;   /* One of the special "no rating" cases */
724   else
725     return atoi(str);
726 }
727
728 void
729 ClearProgramStats ()
730 {
731     /* Init programStats */
732     programStats.movelist[0] = 0;
733     programStats.depth = 0;
734     programStats.nr_moves = 0;
735     programStats.moves_left = 0;
736     programStats.nodes = 0;
737     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
738     programStats.score = 0;
739     programStats.got_only_move = 0;
740     programStats.got_fail = 0;
741     programStats.line_is_book = 0;
742 }
743
744 void
745 CommonEngineInit ()
746 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754
755     first.other = &second;
756     second.other = &first;
757
758     { float norm = 1;
759         if(appData.timeOddsMode) {
760             norm = appData.timeOdds[0];
761             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
762         }
763         first.timeOdds  = appData.timeOdds[0]/norm;
764         second.timeOdds = appData.timeOdds[1]/norm;
765     }
766
767     if(programVersion) free(programVersion);
768     if (appData.noChessProgram) {
769         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
770         sprintf(programVersion, "%s", PACKAGE_STRING);
771     } else {
772       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
773       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
774       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
775     }
776 }
777
778 void
779 UnloadEngine (ChessProgramState *cps)
780 {
781         /* Kill off first chess program */
782         if (cps->isr != NULL)
783           RemoveInputSource(cps->isr);
784         cps->isr = NULL;
785
786         if (cps->pr != NoProc) {
787             ExitAnalyzeMode();
788             DoSleep( appData.delayBeforeQuit );
789             SendToProgram("quit\n", cps);
790             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
791         }
792         cps->pr = NoProc;
793         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
794 }
795
796 void
797 ClearOptions (ChessProgramState *cps)
798 {
799     int i;
800     cps->nrOptions = cps->comboCnt = 0;
801     for(i=0; i<MAX_OPTIONS; i++) {
802         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
803         cps->option[i].textValue = 0;
804     }
805 }
806
807 char *engineNames[] = {
808   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
809      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
810 N_("first"),
811   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
812      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 N_("second")
814 };
815
816 void
817 InitEngine (ChessProgramState *cps, int n)
818 {   // [HGM] all engine initialiation put in a function that does one engine
819
820     ClearOptions(cps);
821
822     cps->which = engineNames[n];
823     cps->maybeThinking = FALSE;
824     cps->pr = NoProc;
825     cps->isr = NULL;
826     cps->sendTime = 2;
827     cps->sendDrawOffers = 1;
828
829     cps->program = appData.chessProgram[n];
830     cps->host = appData.host[n];
831     cps->dir = appData.directory[n];
832     cps->initString = appData.engInitString[n];
833     cps->computerString = appData.computerString[n];
834     cps->useSigint  = TRUE;
835     cps->useSigterm = TRUE;
836     cps->reuse = appData.reuse[n];
837     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
838     cps->useSetboard = FALSE;
839     cps->useSAN = FALSE;
840     cps->usePing = FALSE;
841     cps->lastPing = 0;
842     cps->lastPong = 0;
843     cps->usePlayother = FALSE;
844     cps->useColors = TRUE;
845     cps->useUsermove = FALSE;
846     cps->sendICS = FALSE;
847     cps->sendName = appData.icsActive;
848     cps->sdKludge = FALSE;
849     cps->stKludge = FALSE;
850     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
851     TidyProgramName(cps->program, cps->host, cps->tidy);
852     cps->matchWins = 0;
853     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
854     cps->analysisSupport = 2; /* detect */
855     cps->analyzing = FALSE;
856     cps->initDone = FALSE;
857     cps->reload = FALSE;
858     cps->pseudo = appData.pseudo[n];
859
860     /* New features added by Tord: */
861     cps->useFEN960 = FALSE;
862     cps->useOOCastle = TRUE;
863     /* End of new features added by Tord. */
864     cps->fenOverride  = appData.fenOverride[n];
865
866     /* [HGM] time odds: set factor for each machine */
867     cps->timeOdds  = appData.timeOdds[n];
868
869     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
870     cps->accumulateTC = appData.accumulateTC[n];
871     cps->maxNrOfSessions = 1;
872
873     /* [HGM] debug */
874     cps->debug = FALSE;
875
876     cps->drawDepth = appData.drawDepth[n];
877     cps->supportsNPS = UNKNOWN;
878     cps->memSize = FALSE;
879     cps->maxCores = FALSE;
880     ASSIGN(cps->egtFormats, "");
881
882     /* [HGM] options */
883     cps->optionSettings  = appData.engOptions[n];
884
885     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
886     cps->isUCI = appData.isUCI[n]; /* [AS] */
887     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
888     cps->highlight = 0;
889
890     if (appData.protocolVersion[n] > PROTOVER
891         || appData.protocolVersion[n] < 1)
892       {
893         char buf[MSG_SIZ];
894         int len;
895
896         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
897                        appData.protocolVersion[n]);
898         if( (len >= MSG_SIZ) && appData.debugMode )
899           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
900
901         DisplayFatalError(buf, 0, 2);
902       }
903     else
904       {
905         cps->protocolVersion = appData.protocolVersion[n];
906       }
907
908     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
909     ParseFeatures(appData.featureDefaults, cps);
910 }
911
912 ChessProgramState *savCps;
913
914 GameMode oldMode;
915
916 void
917 LoadEngine ()
918 {
919     int i;
920     if(WaitForEngine(savCps, LoadEngine)) return;
921     CommonEngineInit(); // recalculate time odds
922     if(gameInfo.variant != StringToVariant(appData.variant)) {
923         // we changed variant when loading the engine; this forces us to reset
924         Reset(TRUE, savCps != &first);
925         oldMode = BeginningOfGame; // to prevent restoring old mode
926     }
927     InitChessProgram(savCps, FALSE);
928     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
929     DisplayMessage("", "");
930     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
931     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
932     ThawUI();
933     SetGNUMode();
934     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
935 }
936
937 void
938 ReplaceEngine (ChessProgramState *cps, int n)
939 {
940     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
941     keepInfo = 1;
942     if(oldMode != BeginningOfGame) EditGameEvent();
943     keepInfo = 0;
944     UnloadEngine(cps);
945     appData.noChessProgram = FALSE;
946     appData.clockMode = TRUE;
947     InitEngine(cps, n);
948     UpdateLogos(TRUE);
949     if(n) return; // only startup first engine immediately; second can wait
950     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
951     LoadEngine();
952 }
953
954 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
955 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
956
957 static char resetOptions[] =
958         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
959         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
960         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
961         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
962
963 void
964 FloatToFront(char **list, char *engineLine)
965 {
966     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
967     int i=0;
968     if(appData.recentEngines <= 0) return;
969     TidyProgramName(engineLine, "localhost", tidy+1);
970     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
971     strncpy(buf+1, *list, MSG_SIZ-50);
972     if(p = strstr(buf, tidy)) { // tidy name appears in list
973         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
974         while(*p++ = *++q); // squeeze out
975     }
976     strcat(tidy, buf+1); // put list behind tidy name
977     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
978     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
979     ASSIGN(*list, tidy+1);
980 }
981
982 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
983
984 void
985 Load (ChessProgramState *cps, int i)
986 {
987     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
988     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
989         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
990         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
991         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
992         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
993         appData.firstProtocolVersion = PROTOVER;
994         ParseArgsFromString(buf);
995         SwapEngines(i);
996         ReplaceEngine(cps, i);
997         FloatToFront(&appData.recentEngineList, engineLine);
998         return;
999     }
1000     p = engineName;
1001     while(q = strchr(p, SLASH)) p = q+1;
1002     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1003     if(engineDir[0] != NULLCHAR) {
1004         ASSIGN(appData.directory[i], engineDir); p = engineName;
1005     } else if(p != engineName) { // derive directory from engine path, when not given
1006         p[-1] = 0;
1007         ASSIGN(appData.directory[i], engineName);
1008         p[-1] = SLASH;
1009         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1010     } else { ASSIGN(appData.directory[i], "."); }
1011     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1012     if(params[0]) {
1013         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1014         snprintf(command, MSG_SIZ, "%s %s", p, params);
1015         p = command;
1016     }
1017     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1018     ASSIGN(appData.chessProgram[i], p);
1019     appData.isUCI[i] = isUCI;
1020     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1021     appData.hasOwnBookUCI[i] = hasBook;
1022     if(!nickName[0]) useNick = FALSE;
1023     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1024     if(addToList) {
1025         int len;
1026         char quote;
1027         q = firstChessProgramNames;
1028         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1029         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1030         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1031                         quote, p, quote, appData.directory[i],
1032                         useNick ? " -fn \"" : "",
1033                         useNick ? nickName : "",
1034                         useNick ? "\"" : "",
1035                         v1 ? " -firstProtocolVersion 1" : "",
1036                         hasBook ? "" : " -fNoOwnBookUCI",
1037                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1038                         storeVariant ? " -variant " : "",
1039                         storeVariant ? VariantName(gameInfo.variant) : "");
1040         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1041         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1042         if(insert != q) insert[-1] = NULLCHAR;
1043         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1044         if(q)   free(q);
1045         FloatToFront(&appData.recentEngineList, buf);
1046     }
1047     ReplaceEngine(cps, i);
1048 }
1049
1050 void
1051 InitTimeControls ()
1052 {
1053     int matched, min, sec;
1054     /*
1055      * Parse timeControl resource
1056      */
1057     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1058                           appData.movesPerSession)) {
1059         char buf[MSG_SIZ];
1060         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1061         DisplayFatalError(buf, 0, 2);
1062     }
1063
1064     /*
1065      * Parse searchTime resource
1066      */
1067     if (*appData.searchTime != NULLCHAR) {
1068         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1069         if (matched == 1) {
1070             searchTime = min * 60;
1071         } else if (matched == 2) {
1072             searchTime = min * 60 + sec;
1073         } else {
1074             char buf[MSG_SIZ];
1075             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1076             DisplayFatalError(buf, 0, 2);
1077         }
1078     }
1079 }
1080
1081 void
1082 InitBackEnd1 ()
1083 {
1084
1085     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1086     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1087
1088     GetTimeMark(&programStartTime);
1089     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1090     appData.seedBase = random() + (random()<<15);
1091     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1092
1093     ClearProgramStats();
1094     programStats.ok_to_send = 1;
1095     programStats.seen_stat = 0;
1096
1097     /*
1098      * Initialize game list
1099      */
1100     ListNew(&gameList);
1101
1102
1103     /*
1104      * Internet chess server status
1105      */
1106     if (appData.icsActive) {
1107         appData.matchMode = FALSE;
1108         appData.matchGames = 0;
1109 #if ZIPPY
1110         appData.noChessProgram = !appData.zippyPlay;
1111 #else
1112         appData.zippyPlay = FALSE;
1113         appData.zippyTalk = FALSE;
1114         appData.noChessProgram = TRUE;
1115 #endif
1116         if (*appData.icsHelper != NULLCHAR) {
1117             appData.useTelnet = TRUE;
1118             appData.telnetProgram = appData.icsHelper;
1119         }
1120     } else {
1121         appData.zippyTalk = appData.zippyPlay = FALSE;
1122     }
1123
1124     /* [AS] Initialize pv info list [HGM] and game state */
1125     {
1126         int i, j;
1127
1128         for( i=0; i<=framePtr; i++ ) {
1129             pvInfoList[i].depth = -1;
1130             boards[i][EP_STATUS] = EP_NONE;
1131             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1132         }
1133     }
1134
1135     InitTimeControls();
1136
1137     /* [AS] Adjudication threshold */
1138     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1139
1140     InitEngine(&first, 0);
1141     InitEngine(&second, 1);
1142     CommonEngineInit();
1143
1144     pairing.which = "pairing"; // pairing engine
1145     pairing.pr = NoProc;
1146     pairing.isr = NULL;
1147     pairing.program = appData.pairingEngine;
1148     pairing.host = "localhost";
1149     pairing.dir = ".";
1150
1151     if (appData.icsActive) {
1152         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1153     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1154         appData.clockMode = FALSE;
1155         first.sendTime = second.sendTime = 0;
1156     }
1157
1158 #if ZIPPY
1159     /* Override some settings from environment variables, for backward
1160        compatibility.  Unfortunately it's not feasible to have the env
1161        vars just set defaults, at least in xboard.  Ugh.
1162     */
1163     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1164       ZippyInit();
1165     }
1166 #endif
1167
1168     if (!appData.icsActive) {
1169       char buf[MSG_SIZ];
1170       int len;
1171
1172       /* Check for variants that are supported only in ICS mode,
1173          or not at all.  Some that are accepted here nevertheless
1174          have bugs; see comments below.
1175       */
1176       VariantClass variant = StringToVariant(appData.variant);
1177       switch (variant) {
1178       case VariantBughouse:     /* need four players and two boards */
1179       case VariantKriegspiel:   /* need to hide pieces and move details */
1180         /* case VariantFischeRandom: (Fabien: moved below) */
1181         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1182         if( (len >= MSG_SIZ) && appData.debugMode )
1183           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1184
1185         DisplayFatalError(buf, 0, 2);
1186         return;
1187
1188       case VariantUnknown:
1189       case VariantLoadable:
1190       case Variant29:
1191       case Variant30:
1192       case Variant31:
1193       case Variant32:
1194       case Variant33:
1195       case Variant34:
1196       case Variant35:
1197       case Variant36:
1198       default:
1199         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1200         if( (len >= MSG_SIZ) && appData.debugMode )
1201           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1202
1203         DisplayFatalError(buf, 0, 2);
1204         return;
1205
1206       case VariantNormal:     /* definitely works! */
1207         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1208           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1209           return;
1210         }
1211       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1212       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1213       case VariantGothic:     /* [HGM] should work */
1214       case VariantCapablanca: /* [HGM] should work */
1215       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1216       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1217       case VariantChu:        /* [HGM] experimental */
1218       case VariantKnightmate: /* [HGM] should work */
1219       case VariantCylinder:   /* [HGM] untested */
1220       case VariantFalcon:     /* [HGM] untested */
1221       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1222                                  offboard interposition not understood */
1223       case VariantWildCastle: /* pieces not automatically shuffled */
1224       case VariantNoCastle:   /* pieces not automatically shuffled */
1225       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1226       case VariantLosers:     /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantSuicide:    /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantGiveaway:   /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantTwoKings:   /* should work */
1233       case VariantAtomic:     /* should work except for win condition */
1234       case Variant3Check:     /* should work except for win condition */
1235       case VariantShatranj:   /* should work except for all win conditions */
1236       case VariantMakruk:     /* should work except for draw countdown */
1237       case VariantASEAN :     /* should work except for draw countdown */
1238       case VariantBerolina:   /* might work if TestLegality is off */
1239       case VariantCapaRandom: /* should work */
1240       case VariantJanus:      /* should work */
1241       case VariantSuper:      /* experimental */
1242       case VariantGreat:      /* experimental, requires legality testing to be off */
1243       case VariantSChess:     /* S-Chess, should work */
1244       case VariantGrand:      /* should work */
1245       case VariantSpartan:    /* should work */
1246       case VariantLion:       /* should work */
1247       case VariantChuChess:   /* should work */
1248         break;
1249       }
1250     }
1251
1252 }
1253
1254 int
1255 NextIntegerFromString (char ** str, long * value)
1256 {
1257     int result = -1;
1258     char * s = *str;
1259
1260     while( *s == ' ' || *s == '\t' ) {
1261         s++;
1262     }
1263
1264     *value = 0;
1265
1266     if( *s >= '0' && *s <= '9' ) {
1267         while( *s >= '0' && *s <= '9' ) {
1268             *value = *value * 10 + (*s - '0');
1269             s++;
1270         }
1271
1272         result = 0;
1273     }
1274
1275     *str = s;
1276
1277     return result;
1278 }
1279
1280 int
1281 NextTimeControlFromString (char ** str, long * value)
1282 {
1283     long temp;
1284     int result = NextIntegerFromString( str, &temp );
1285
1286     if( result == 0 ) {
1287         *value = temp * 60; /* Minutes */
1288         if( **str == ':' ) {
1289             (*str)++;
1290             result = NextIntegerFromString( str, &temp );
1291             *value += temp; /* Seconds */
1292         }
1293     }
1294
1295     return result;
1296 }
1297
1298 int
1299 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1300 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1301     int result = -1, type = 0; long temp, temp2;
1302
1303     if(**str != ':') return -1; // old params remain in force!
1304     (*str)++;
1305     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1306     if( NextIntegerFromString( str, &temp ) ) return -1;
1307     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1308
1309     if(**str != '/') {
1310         /* time only: incremental or sudden-death time control */
1311         if(**str == '+') { /* increment follows; read it */
1312             (*str)++;
1313             if(**str == '!') type = *(*str)++; // Bronstein TC
1314             if(result = NextIntegerFromString( str, &temp2)) return -1;
1315             *inc = temp2 * 1000;
1316             if(**str == '.') { // read fraction of increment
1317                 char *start = ++(*str);
1318                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1319                 temp2 *= 1000;
1320                 while(start++ < *str) temp2 /= 10;
1321                 *inc += temp2;
1322             }
1323         } else *inc = 0;
1324         *moves = 0; *tc = temp * 1000; *incType = type;
1325         return 0;
1326     }
1327
1328     (*str)++; /* classical time control */
1329     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1330
1331     if(result == 0) {
1332         *moves = temp;
1333         *tc    = temp2 * 1000;
1334         *inc   = 0;
1335         *incType = type;
1336     }
1337     return result;
1338 }
1339
1340 int
1341 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1342 {   /* [HGM] get time to add from the multi-session time-control string */
1343     int incType, moves=1; /* kludge to force reading of first session */
1344     long time, increment;
1345     char *s = tcString;
1346
1347     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1348     do {
1349         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1350         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1351         if(movenr == -1) return time;    /* last move before new session     */
1352         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1353         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1354         if(!moves) return increment;     /* current session is incremental   */
1355         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1356     } while(movenr >= -1);               /* try again for next session       */
1357
1358     return 0; // no new time quota on this move
1359 }
1360
1361 int
1362 ParseTimeControl (char *tc, float ti, int mps)
1363 {
1364   long tc1;
1365   long tc2;
1366   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1367   int min, sec=0;
1368
1369   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1370   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1371       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1372   if(ti > 0) {
1373
1374     if(mps)
1375       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1376     else
1377       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1378   } else {
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s", mytc);
1383   }
1384   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1385
1386   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1387     return FALSE;
1388   }
1389
1390   if( *tc == '/' ) {
1391     /* Parse second time control */
1392     tc++;
1393
1394     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1395       return FALSE;
1396     }
1397
1398     if( tc2 == 0 ) {
1399       return FALSE;
1400     }
1401
1402     timeControl_2 = tc2 * 1000;
1403   }
1404   else {
1405     timeControl_2 = 0;
1406   }
1407
1408   if( tc1 == 0 ) {
1409     return FALSE;
1410   }
1411
1412   timeControl = tc1 * 1000;
1413
1414   if (ti >= 0) {
1415     timeIncrement = ti * 1000;  /* convert to ms */
1416     movesPerSession = 0;
1417   } else {
1418     timeIncrement = 0;
1419     movesPerSession = mps;
1420   }
1421   return TRUE;
1422 }
1423
1424 void
1425 InitBackEnd2 ()
1426 {
1427     if (appData.debugMode) {
1428 #    ifdef __GIT_VERSION
1429       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1430 #    else
1431       fprintf(debugFP, "Version: %s\n", programVersion);
1432 #    endif
1433     }
1434     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1435
1436     set_cont_sequence(appData.wrapContSeq);
1437     if (appData.matchGames > 0) {
1438         appData.matchMode = TRUE;
1439     } else if (appData.matchMode) {
1440         appData.matchGames = 1;
1441     }
1442     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1443         appData.matchGames = appData.sameColorGames;
1444     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1445         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1446         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1447     }
1448     Reset(TRUE, FALSE);
1449     if (appData.noChessProgram || first.protocolVersion == 1) {
1450       InitBackEnd3();
1451     } else {
1452       /* kludge: allow timeout for initial "feature" commands */
1453       FreezeUI();
1454       DisplayMessage("", _("Starting chess program"));
1455       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1456     }
1457 }
1458
1459 int
1460 CalculateIndex (int index, int gameNr)
1461 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1462     int res;
1463     if(index > 0) return index; // fixed nmber
1464     if(index == 0) return 1;
1465     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1466     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1467     return res;
1468 }
1469
1470 int
1471 LoadGameOrPosition (int gameNr)
1472 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1473     if (*appData.loadGameFile != NULLCHAR) {
1474         if (!LoadGameFromFile(appData.loadGameFile,
1475                 CalculateIndex(appData.loadGameIndex, gameNr),
1476                               appData.loadGameFile, FALSE)) {
1477             DisplayFatalError(_("Bad game file"), 0, 1);
1478             return 0;
1479         }
1480     } else if (*appData.loadPositionFile != NULLCHAR) {
1481         if (!LoadPositionFromFile(appData.loadPositionFile,
1482                 CalculateIndex(appData.loadPositionIndex, gameNr),
1483                                   appData.loadPositionFile)) {
1484             DisplayFatalError(_("Bad position file"), 0, 1);
1485             return 0;
1486         }
1487     }
1488     return 1;
1489 }
1490
1491 void
1492 ReserveGame (int gameNr, char resChar)
1493 {
1494     FILE *tf = fopen(appData.tourneyFile, "r+");
1495     char *p, *q, c, buf[MSG_SIZ];
1496     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1497     safeStrCpy(buf, lastMsg, MSG_SIZ);
1498     DisplayMessage(_("Pick new game"), "");
1499     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1500     ParseArgsFromFile(tf);
1501     p = q = appData.results;
1502     if(appData.debugMode) {
1503       char *r = appData.participants;
1504       fprintf(debugFP, "results = '%s'\n", p);
1505       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1506       fprintf(debugFP, "\n");
1507     }
1508     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1509     nextGame = q - p;
1510     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1511     safeStrCpy(q, p, strlen(p) + 2);
1512     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1513     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1514     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1515         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1516         q[nextGame] = '*';
1517     }
1518     fseek(tf, -(strlen(p)+4), SEEK_END);
1519     c = fgetc(tf);
1520     if(c != '"') // depending on DOS or Unix line endings we can be one off
1521          fseek(tf, -(strlen(p)+2), SEEK_END);
1522     else fseek(tf, -(strlen(p)+3), SEEK_END);
1523     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1524     DisplayMessage(buf, "");
1525     free(p); appData.results = q;
1526     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1527        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1528       int round = appData.defaultMatchGames * appData.tourneyType;
1529       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1530          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1531         UnloadEngine(&first);  // next game belongs to other pairing;
1532         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1533     }
1534     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1535 }
1536
1537 void
1538 MatchEvent (int mode)
1539 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1540         int dummy;
1541         if(matchMode) { // already in match mode: switch it off
1542             abortMatch = TRUE;
1543             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1544             return;
1545         }
1546 //      if(gameMode != BeginningOfGame) {
1547 //          DisplayError(_("You can only start a match from the initial position."), 0);
1548 //          return;
1549 //      }
1550         abortMatch = FALSE;
1551         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1552         /* Set up machine vs. machine match */
1553         nextGame = 0;
1554         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1555         if(appData.tourneyFile[0]) {
1556             ReserveGame(-1, 0);
1557             if(nextGame > appData.matchGames) {
1558                 char buf[MSG_SIZ];
1559                 if(strchr(appData.results, '*') == NULL) {
1560                     FILE *f;
1561                     appData.tourneyCycles++;
1562                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1563                         fclose(f);
1564                         NextTourneyGame(-1, &dummy);
1565                         ReserveGame(-1, 0);
1566                         if(nextGame <= appData.matchGames) {
1567                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1568                             matchMode = mode;
1569                             ScheduleDelayedEvent(NextMatchGame, 10000);
1570                             return;
1571                         }
1572                     }
1573                 }
1574                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1575                 DisplayError(buf, 0);
1576                 appData.tourneyFile[0] = 0;
1577                 return;
1578             }
1579         } else
1580         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1581             DisplayFatalError(_("Can't have a match with no chess programs"),
1582                               0, 2);
1583             return;
1584         }
1585         matchMode = mode;
1586         matchGame = roundNr = 1;
1587         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1588         NextMatchGame();
1589 }
1590
1591 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1592
1593 void
1594 InitBackEnd3 P((void))
1595 {
1596     GameMode initialMode;
1597     char buf[MSG_SIZ];
1598     int err, len;
1599
1600     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1601        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1602         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1603        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1604        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1605         char c, *q = first.variants, *p = strchr(q, ',');
1606         if(p) *p = NULLCHAR;
1607         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1608             int w, h, s;
1609             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1610                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1611             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1612             Reset(TRUE, FALSE);         // and re-initialize
1613         }
1614         if(p) *p = ',';
1615     }
1616
1617     InitChessProgram(&first, startedFromSetupPosition);
1618
1619     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1620         free(programVersion);
1621         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1622         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1623         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1624     }
1625
1626     if (appData.icsActive) {
1627 #ifdef WIN32
1628         /* [DM] Make a console window if needed [HGM] merged ifs */
1629         ConsoleCreate();
1630 #endif
1631         err = establish();
1632         if (err != 0)
1633           {
1634             if (*appData.icsCommPort != NULLCHAR)
1635               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1636                              appData.icsCommPort);
1637             else
1638               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1639                         appData.icsHost, appData.icsPort);
1640
1641             if( (len >= MSG_SIZ) && appData.debugMode )
1642               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1643
1644             DisplayFatalError(buf, err, 1);
1645             return;
1646         }
1647         SetICSMode();
1648         telnetISR =
1649           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1650         fromUserISR =
1651           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1652         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1653             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1654     } else if (appData.noChessProgram) {
1655         SetNCPMode();
1656     } else {
1657         SetGNUMode();
1658     }
1659
1660     if (*appData.cmailGameName != NULLCHAR) {
1661         SetCmailMode();
1662         OpenLoopback(&cmailPR);
1663         cmailISR =
1664           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1665     }
1666
1667     ThawUI();
1668     DisplayMessage("", "");
1669     if (StrCaseCmp(appData.initialMode, "") == 0) {
1670       initialMode = BeginningOfGame;
1671       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1672         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1673         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1674         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1675         ModeHighlight();
1676       }
1677     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1678       initialMode = TwoMachinesPlay;
1679     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1680       initialMode = AnalyzeFile;
1681     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1682       initialMode = AnalyzeMode;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1684       initialMode = MachinePlaysWhite;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1686       initialMode = MachinePlaysBlack;
1687     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1688       initialMode = EditGame;
1689     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1690       initialMode = EditPosition;
1691     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1692       initialMode = Training;
1693     } else {
1694       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1695       if( (len >= MSG_SIZ) && appData.debugMode )
1696         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1697
1698       DisplayFatalError(buf, 0, 2);
1699       return;
1700     }
1701
1702     if (appData.matchMode) {
1703         if(appData.tourneyFile[0]) { // start tourney from command line
1704             FILE *f;
1705             if(f = fopen(appData.tourneyFile, "r")) {
1706                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1707                 fclose(f);
1708                 appData.clockMode = TRUE;
1709                 SetGNUMode();
1710             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1711         }
1712         MatchEvent(TRUE);
1713     } else if (*appData.cmailGameName != NULLCHAR) {
1714         /* Set up cmail mode */
1715         ReloadCmailMsgEvent(TRUE);
1716     } else {
1717         /* Set up other modes */
1718         if (initialMode == AnalyzeFile) {
1719           if (*appData.loadGameFile == NULLCHAR) {
1720             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1721             return;
1722           }
1723         }
1724         if (*appData.loadGameFile != NULLCHAR) {
1725             (void) LoadGameFromFile(appData.loadGameFile,
1726                                     appData.loadGameIndex,
1727                                     appData.loadGameFile, TRUE);
1728         } else if (*appData.loadPositionFile != NULLCHAR) {
1729             (void) LoadPositionFromFile(appData.loadPositionFile,
1730                                         appData.loadPositionIndex,
1731                                         appData.loadPositionFile);
1732             /* [HGM] try to make self-starting even after FEN load */
1733             /* to allow automatic setup of fairy variants with wtm */
1734             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1735                 gameMode = BeginningOfGame;
1736                 setboardSpoiledMachineBlack = 1;
1737             }
1738             /* [HGM] loadPos: make that every new game uses the setup */
1739             /* from file as long as we do not switch variant          */
1740             if(!blackPlaysFirst) {
1741                 startedFromPositionFile = TRUE;
1742                 CopyBoard(filePosition, boards[0]);
1743                 CopyBoard(initialPosition, boards[0]);
1744             }
1745         }
1746         if (initialMode == AnalyzeMode) {
1747           if (appData.noChessProgram) {
1748             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1749             return;
1750           }
1751           if (appData.icsActive) {
1752             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1753             return;
1754           }
1755           AnalyzeModeEvent();
1756         } else if (initialMode == AnalyzeFile) {
1757           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1758           ShowThinkingEvent();
1759           AnalyzeFileEvent();
1760           AnalysisPeriodicEvent(1);
1761         } else if (initialMode == MachinePlaysWhite) {
1762           if (appData.noChessProgram) {
1763             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1764                               0, 2);
1765             return;
1766           }
1767           if (appData.icsActive) {
1768             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1769                               0, 2);
1770             return;
1771           }
1772           MachineWhiteEvent();
1773         } else if (initialMode == MachinePlaysBlack) {
1774           if (appData.noChessProgram) {
1775             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1776                               0, 2);
1777             return;
1778           }
1779           if (appData.icsActive) {
1780             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1781                               0, 2);
1782             return;
1783           }
1784           MachineBlackEvent();
1785         } else if (initialMode == TwoMachinesPlay) {
1786           if (appData.noChessProgram) {
1787             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1788                               0, 2);
1789             return;
1790           }
1791           if (appData.icsActive) {
1792             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1793                               0, 2);
1794             return;
1795           }
1796           TwoMachinesEvent();
1797         } else if (initialMode == EditGame) {
1798           EditGameEvent();
1799         } else if (initialMode == EditPosition) {
1800           EditPositionEvent();
1801         } else if (initialMode == Training) {
1802           if (*appData.loadGameFile == NULLCHAR) {
1803             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1804             return;
1805           }
1806           TrainingEvent();
1807         }
1808     }
1809 }
1810
1811 void
1812 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1813 {
1814     DisplayBook(current+1);
1815
1816     MoveHistorySet( movelist, first, last, current, pvInfoList );
1817
1818     EvalGraphSet( first, last, current, pvInfoList );
1819
1820     MakeEngineOutputTitle();
1821 }
1822
1823 /*
1824  * Establish will establish a contact to a remote host.port.
1825  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1826  *  used to talk to the host.
1827  * Returns 0 if okay, error code if not.
1828  */
1829 int
1830 establish ()
1831 {
1832     char buf[MSG_SIZ];
1833
1834     if (*appData.icsCommPort != NULLCHAR) {
1835         /* Talk to the host through a serial comm port */
1836         return OpenCommPort(appData.icsCommPort, &icsPR);
1837
1838     } else if (*appData.gateway != NULLCHAR) {
1839         if (*appData.remoteShell == NULLCHAR) {
1840             /* Use the rcmd protocol to run telnet program on a gateway host */
1841             snprintf(buf, sizeof(buf), "%s %s %s",
1842                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1843             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1844
1845         } else {
1846             /* Use the rsh program to run telnet program on a gateway host */
1847             if (*appData.remoteUser == NULLCHAR) {
1848                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1849                         appData.gateway, appData.telnetProgram,
1850                         appData.icsHost, appData.icsPort);
1851             } else {
1852                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1853                         appData.remoteShell, appData.gateway,
1854                         appData.remoteUser, appData.telnetProgram,
1855                         appData.icsHost, appData.icsPort);
1856             }
1857             return StartChildProcess(buf, "", &icsPR);
1858
1859         }
1860     } else if (appData.useTelnet) {
1861         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1862
1863     } else {
1864         /* TCP socket interface differs somewhat between
1865            Unix and NT; handle details in the front end.
1866            */
1867         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1868     }
1869 }
1870
1871 void
1872 EscapeExpand (char *p, char *q)
1873 {       // [HGM] initstring: routine to shape up string arguments
1874         while(*p++ = *q++) if(p[-1] == '\\')
1875             switch(*q++) {
1876                 case 'n': p[-1] = '\n'; break;
1877                 case 'r': p[-1] = '\r'; break;
1878                 case 't': p[-1] = '\t'; break;
1879                 case '\\': p[-1] = '\\'; break;
1880                 case 0: *p = 0; return;
1881                 default: p[-1] = q[-1]; break;
1882             }
1883 }
1884
1885 void
1886 show_bytes (FILE *fp, char *buf, int count)
1887 {
1888     while (count--) {
1889         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1890             fprintf(fp, "\\%03o", *buf & 0xff);
1891         } else {
1892             putc(*buf, fp);
1893         }
1894         buf++;
1895     }
1896     fflush(fp);
1897 }
1898
1899 /* Returns an errno value */
1900 int
1901 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1902 {
1903     char buf[8192], *p, *q, *buflim;
1904     int left, newcount, outcount;
1905
1906     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1907         *appData.gateway != NULLCHAR) {
1908         if (appData.debugMode) {
1909             fprintf(debugFP, ">ICS: ");
1910             show_bytes(debugFP, message, count);
1911             fprintf(debugFP, "\n");
1912         }
1913         return OutputToProcess(pr, message, count, outError);
1914     }
1915
1916     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1917     p = message;
1918     q = buf;
1919     left = count;
1920     newcount = 0;
1921     while (left) {
1922         if (q >= buflim) {
1923             if (appData.debugMode) {
1924                 fprintf(debugFP, ">ICS: ");
1925                 show_bytes(debugFP, buf, newcount);
1926                 fprintf(debugFP, "\n");
1927             }
1928             outcount = OutputToProcess(pr, buf, newcount, outError);
1929             if (outcount < newcount) return -1; /* to be sure */
1930             q = buf;
1931             newcount = 0;
1932         }
1933         if (*p == '\n') {
1934             *q++ = '\r';
1935             newcount++;
1936         } else if (((unsigned char) *p) == TN_IAC) {
1937             *q++ = (char) TN_IAC;
1938             newcount ++;
1939         }
1940         *q++ = *p++;
1941         newcount++;
1942         left--;
1943     }
1944     if (appData.debugMode) {
1945         fprintf(debugFP, ">ICS: ");
1946         show_bytes(debugFP, buf, newcount);
1947         fprintf(debugFP, "\n");
1948     }
1949     outcount = OutputToProcess(pr, buf, newcount, outError);
1950     if (outcount < newcount) return -1; /* to be sure */
1951     return count;
1952 }
1953
1954 void
1955 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1956 {
1957     int outError, outCount;
1958     static int gotEof = 0;
1959     static FILE *ini;
1960
1961     /* Pass data read from player on to ICS */
1962     if (count > 0) {
1963         gotEof = 0;
1964         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1965         if (outCount < count) {
1966             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1967         }
1968         if(have_sent_ICS_logon == 2) {
1969           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1970             fprintf(ini, "%s", message);
1971             have_sent_ICS_logon = 3;
1972           } else
1973             have_sent_ICS_logon = 1;
1974         } else if(have_sent_ICS_logon == 3) {
1975             fprintf(ini, "%s", message);
1976             fclose(ini);
1977           have_sent_ICS_logon = 1;
1978         }
1979     } else if (count < 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1982     } else if (gotEof++ > 0) {
1983         RemoveInputSource(isr);
1984         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1985     }
1986 }
1987
1988 void
1989 KeepAlive ()
1990 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1991     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1992     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1993     SendToICS("date\n");
1994     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1995 }
1996
1997 /* added routine for printf style output to ics */
1998 void
1999 ics_printf (char *format, ...)
2000 {
2001     char buffer[MSG_SIZ];
2002     va_list args;
2003
2004     va_start(args, format);
2005     vsnprintf(buffer, sizeof(buffer), format, args);
2006     buffer[sizeof(buffer)-1] = '\0';
2007     SendToICS(buffer);
2008     va_end(args);
2009 }
2010
2011 void
2012 SendToICS (char *s)
2013 {
2014     int count, outCount, outError;
2015
2016     if (icsPR == NoProc) return;
2017
2018     count = strlen(s);
2019     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2020     if (outCount < count) {
2021         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2022     }
2023 }
2024
2025 /* This is used for sending logon scripts to the ICS. Sending
2026    without a delay causes problems when using timestamp on ICC
2027    (at least on my machine). */
2028 void
2029 SendToICSDelayed (char *s, long msdelay)
2030 {
2031     int count, outCount, outError;
2032
2033     if (icsPR == NoProc) return;
2034
2035     count = strlen(s);
2036     if (appData.debugMode) {
2037         fprintf(debugFP, ">ICS: ");
2038         show_bytes(debugFP, s, count);
2039         fprintf(debugFP, "\n");
2040     }
2041     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2042                                       msdelay);
2043     if (outCount < count) {
2044         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2045     }
2046 }
2047
2048
2049 /* Remove all highlighting escape sequences in s
2050    Also deletes any suffix starting with '('
2051    */
2052 char *
2053 StripHighlightAndTitle (char *s)
2054 {
2055     static char retbuf[MSG_SIZ];
2056     char *p = retbuf;
2057
2058     while (*s != NULLCHAR) {
2059         while (*s == '\033') {
2060             while (*s != NULLCHAR && !isalpha(*s)) s++;
2061             if (*s != NULLCHAR) s++;
2062         }
2063         while (*s != NULLCHAR && *s != '\033') {
2064             if (*s == '(' || *s == '[') {
2065                 *p = NULLCHAR;
2066                 return retbuf;
2067             }
2068             *p++ = *s++;
2069         }
2070     }
2071     *p = NULLCHAR;
2072     return retbuf;
2073 }
2074
2075 /* Remove all highlighting escape sequences in s */
2076 char *
2077 StripHighlight (char *s)
2078 {
2079     static char retbuf[MSG_SIZ];
2080     char *p = retbuf;
2081
2082     while (*s != NULLCHAR) {
2083         while (*s == '\033') {
2084             while (*s != NULLCHAR && !isalpha(*s)) s++;
2085             if (*s != NULLCHAR) s++;
2086         }
2087         while (*s != NULLCHAR && *s != '\033') {
2088             *p++ = *s++;
2089         }
2090     }
2091     *p = NULLCHAR;
2092     return retbuf;
2093 }
2094
2095 char engineVariant[MSG_SIZ];
2096 char *variantNames[] = VARIANT_NAMES;
2097 char *
2098 VariantName (VariantClass v)
2099 {
2100     if(v == VariantUnknown || *engineVariant) return engineVariant;
2101     return variantNames[v];
2102 }
2103
2104
2105 /* Identify a variant from the strings the chess servers use or the
2106    PGN Variant tag names we use. */
2107 VariantClass
2108 StringToVariant (char *e)
2109 {
2110     char *p;
2111     int wnum = -1;
2112     VariantClass v = VariantNormal;
2113     int i, found = FALSE;
2114     char buf[MSG_SIZ], c;
2115     int len;
2116
2117     if (!e) return v;
2118
2119     /* [HGM] skip over optional board-size prefixes */
2120     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2121         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2122         while( *e++ != '_');
2123     }
2124
2125     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2126         v = VariantNormal;
2127         found = TRUE;
2128     } else
2129     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2130       if (p = StrCaseStr(e, variantNames[i])) {
2131         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2132         v = (VariantClass) i;
2133         found = TRUE;
2134         break;
2135       }
2136     }
2137
2138     if (!found) {
2139       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2140           || StrCaseStr(e, "wild/fr")
2141           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2142         v = VariantFischeRandom;
2143       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2144                  (i = 1, p = StrCaseStr(e, "w"))) {
2145         p += i;
2146         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2147         if (isdigit(*p)) {
2148           wnum = atoi(p);
2149         } else {
2150           wnum = -1;
2151         }
2152         switch (wnum) {
2153         case 0: /* FICS only, actually */
2154         case 1:
2155           /* Castling legal even if K starts on d-file */
2156           v = VariantWildCastle;
2157           break;
2158         case 2:
2159         case 3:
2160         case 4:
2161           /* Castling illegal even if K & R happen to start in
2162              normal positions. */
2163           v = VariantNoCastle;
2164           break;
2165         case 5:
2166         case 7:
2167         case 8:
2168         case 10:
2169         case 11:
2170         case 12:
2171         case 13:
2172         case 14:
2173         case 15:
2174         case 18:
2175         case 19:
2176           /* Castling legal iff K & R start in normal positions */
2177           v = VariantNormal;
2178           break;
2179         case 6:
2180         case 20:
2181         case 21:
2182           /* Special wilds for position setup; unclear what to do here */
2183           v = VariantLoadable;
2184           break;
2185         case 9:
2186           /* Bizarre ICC game */
2187           v = VariantTwoKings;
2188           break;
2189         case 16:
2190           v = VariantKriegspiel;
2191           break;
2192         case 17:
2193           v = VariantLosers;
2194           break;
2195         case 22:
2196           v = VariantFischeRandom;
2197           break;
2198         case 23:
2199           v = VariantCrazyhouse;
2200           break;
2201         case 24:
2202           v = VariantBughouse;
2203           break;
2204         case 25:
2205           v = Variant3Check;
2206           break;
2207         case 26:
2208           /* Not quite the same as FICS suicide! */
2209           v = VariantGiveaway;
2210           break;
2211         case 27:
2212           v = VariantAtomic;
2213           break;
2214         case 28:
2215           v = VariantShatranj;
2216           break;
2217
2218         /* Temporary names for future ICC types.  The name *will* change in
2219            the next xboard/WinBoard release after ICC defines it. */
2220         case 29:
2221           v = Variant29;
2222           break;
2223         case 30:
2224           v = Variant30;
2225           break;
2226         case 31:
2227           v = Variant31;
2228           break;
2229         case 32:
2230           v = Variant32;
2231           break;
2232         case 33:
2233           v = Variant33;
2234           break;
2235         case 34:
2236           v = Variant34;
2237           break;
2238         case 35:
2239           v = Variant35;
2240           break;
2241         case 36:
2242           v = Variant36;
2243           break;
2244         case 37:
2245           v = VariantShogi;
2246           break;
2247         case 38:
2248           v = VariantXiangqi;
2249           break;
2250         case 39:
2251           v = VariantCourier;
2252           break;
2253         case 40:
2254           v = VariantGothic;
2255           break;
2256         case 41:
2257           v = VariantCapablanca;
2258           break;
2259         case 42:
2260           v = VariantKnightmate;
2261           break;
2262         case 43:
2263           v = VariantFairy;
2264           break;
2265         case 44:
2266           v = VariantCylinder;
2267           break;
2268         case 45:
2269           v = VariantFalcon;
2270           break;
2271         case 46:
2272           v = VariantCapaRandom;
2273           break;
2274         case 47:
2275           v = VariantBerolina;
2276           break;
2277         case 48:
2278           v = VariantJanus;
2279           break;
2280         case 49:
2281           v = VariantSuper;
2282           break;
2283         case 50:
2284           v = VariantGreat;
2285           break;
2286         case -1:
2287           /* Found "wild" or "w" in the string but no number;
2288              must assume it's normal chess. */
2289           v = VariantNormal;
2290           break;
2291         default:
2292           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2293           if( (len >= MSG_SIZ) && appData.debugMode )
2294             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2295
2296           DisplayError(buf, 0);
2297           v = VariantUnknown;
2298           break;
2299         }
2300       }
2301     }
2302     if (appData.debugMode) {
2303       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2304               e, wnum, VariantName(v));
2305     }
2306     return v;
2307 }
2308
2309 static int leftover_start = 0, leftover_len = 0;
2310 char star_match[STAR_MATCH_N][MSG_SIZ];
2311
2312 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2313    advance *index beyond it, and set leftover_start to the new value of
2314    *index; else return FALSE.  If pattern contains the character '*', it
2315    matches any sequence of characters not containing '\r', '\n', or the
2316    character following the '*' (if any), and the matched sequence(s) are
2317    copied into star_match.
2318    */
2319 int
2320 looking_at ( char *buf, int *index, char *pattern)
2321 {
2322     char *bufp = &buf[*index], *patternp = pattern;
2323     int star_count = 0;
2324     char *matchp = star_match[0];
2325
2326     for (;;) {
2327         if (*patternp == NULLCHAR) {
2328             *index = leftover_start = bufp - buf;
2329             *matchp = NULLCHAR;
2330             return TRUE;
2331         }
2332         if (*bufp == NULLCHAR) return FALSE;
2333         if (*patternp == '*') {
2334             if (*bufp == *(patternp + 1)) {
2335                 *matchp = NULLCHAR;
2336                 matchp = star_match[++star_count];
2337                 patternp += 2;
2338                 bufp++;
2339                 continue;
2340             } else if (*bufp == '\n' || *bufp == '\r') {
2341                 patternp++;
2342                 if (*patternp == NULLCHAR)
2343                   continue;
2344                 else
2345                   return FALSE;
2346             } else {
2347                 *matchp++ = *bufp++;
2348                 continue;
2349             }
2350         }
2351         if (*patternp != *bufp) return FALSE;
2352         patternp++;
2353         bufp++;
2354     }
2355 }
2356
2357 void
2358 SendToPlayer (char *data, int length)
2359 {
2360     int error, outCount;
2361     outCount = OutputToProcess(NoProc, data, length, &error);
2362     if (outCount < length) {
2363         DisplayFatalError(_("Error writing to display"), error, 1);
2364     }
2365 }
2366
2367 void
2368 PackHolding (char packed[], char *holding)
2369 {
2370     char *p = holding;
2371     char *q = packed;
2372     int runlength = 0;
2373     int curr = 9999;
2374     do {
2375         if (*p == curr) {
2376             runlength++;
2377         } else {
2378             switch (runlength) {
2379               case 0:
2380                 break;
2381               case 1:
2382                 *q++ = curr;
2383                 break;
2384               case 2:
2385                 *q++ = curr;
2386                 *q++ = curr;
2387                 break;
2388               default:
2389                 sprintf(q, "%d", runlength);
2390                 while (*q) q++;
2391                 *q++ = curr;
2392                 break;
2393             }
2394             runlength = 1;
2395             curr = *p;
2396         }
2397     } while (*p++);
2398     *q = NULLCHAR;
2399 }
2400
2401 /* Telnet protocol requests from the front end */
2402 void
2403 TelnetRequest (unsigned char ddww, unsigned char option)
2404 {
2405     unsigned char msg[3];
2406     int outCount, outError;
2407
2408     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2409
2410     if (appData.debugMode) {
2411         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2412         switch (ddww) {
2413           case TN_DO:
2414             ddwwStr = "DO";
2415             break;
2416           case TN_DONT:
2417             ddwwStr = "DONT";
2418             break;
2419           case TN_WILL:
2420             ddwwStr = "WILL";
2421             break;
2422           case TN_WONT:
2423             ddwwStr = "WONT";
2424             break;
2425           default:
2426             ddwwStr = buf1;
2427             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2428             break;
2429         }
2430         switch (option) {
2431           case TN_ECHO:
2432             optionStr = "ECHO";
2433             break;
2434           default:
2435             optionStr = buf2;
2436             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2437             break;
2438         }
2439         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2440     }
2441     msg[0] = TN_IAC;
2442     msg[1] = ddww;
2443     msg[2] = option;
2444     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2445     if (outCount < 3) {
2446         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2447     }
2448 }
2449
2450 void
2451 DoEcho ()
2452 {
2453     if (!appData.icsActive) return;
2454     TelnetRequest(TN_DO, TN_ECHO);
2455 }
2456
2457 void
2458 DontEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DONT, TN_ECHO);
2462 }
2463
2464 void
2465 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2466 {
2467     /* put the holdings sent to us by the server on the board holdings area */
2468     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2469     char p;
2470     ChessSquare piece;
2471
2472     if(gameInfo.holdingsWidth < 2)  return;
2473     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2474         return; // prevent overwriting by pre-board holdings
2475
2476     if( (int)lowestPiece >= BlackPawn ) {
2477         holdingsColumn = 0;
2478         countsColumn = 1;
2479         holdingsStartRow = BOARD_HEIGHT-1;
2480         direction = -1;
2481     } else {
2482         holdingsColumn = BOARD_WIDTH-1;
2483         countsColumn = BOARD_WIDTH-2;
2484         holdingsStartRow = 0;
2485         direction = 1;
2486     }
2487
2488     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2489         board[i][holdingsColumn] = EmptySquare;
2490         board[i][countsColumn]   = (ChessSquare) 0;
2491     }
2492     while( (p=*holdings++) != NULLCHAR ) {
2493         piece = CharToPiece( ToUpper(p) );
2494         if(piece == EmptySquare) continue;
2495         /*j = (int) piece - (int) WhitePawn;*/
2496         j = PieceToNumber(piece);
2497         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2498         if(j < 0) continue;               /* should not happen */
2499         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2500         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2501         board[holdingsStartRow+j*direction][countsColumn]++;
2502     }
2503 }
2504
2505
2506 void
2507 VariantSwitch (Board board, VariantClass newVariant)
2508 {
2509    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2510    static Board oldBoard;
2511
2512    startedFromPositionFile = FALSE;
2513    if(gameInfo.variant == newVariant) return;
2514
2515    /* [HGM] This routine is called each time an assignment is made to
2516     * gameInfo.variant during a game, to make sure the board sizes
2517     * are set to match the new variant. If that means adding or deleting
2518     * holdings, we shift the playing board accordingly
2519     * This kludge is needed because in ICS observe mode, we get boards
2520     * of an ongoing game without knowing the variant, and learn about the
2521     * latter only later. This can be because of the move list we requested,
2522     * in which case the game history is refilled from the beginning anyway,
2523     * but also when receiving holdings of a crazyhouse game. In the latter
2524     * case we want to add those holdings to the already received position.
2525     */
2526
2527
2528    if (appData.debugMode) {
2529      fprintf(debugFP, "Switch board from %s to %s\n",
2530              VariantName(gameInfo.variant), VariantName(newVariant));
2531      setbuf(debugFP, NULL);
2532    }
2533    shuffleOpenings = 0;       /* [HGM] shuffle */
2534    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2535    switch(newVariant)
2536      {
2537      case VariantShogi:
2538        newWidth = 9;  newHeight = 9;
2539        gameInfo.holdingsSize = 7;
2540      case VariantBughouse:
2541      case VariantCrazyhouse:
2542        newHoldingsWidth = 2; break;
2543      case VariantGreat:
2544        newWidth = 10;
2545      case VariantSuper:
2546        newHoldingsWidth = 2;
2547        gameInfo.holdingsSize = 8;
2548        break;
2549      case VariantGothic:
2550      case VariantCapablanca:
2551      case VariantCapaRandom:
2552        newWidth = 10;
2553      default:
2554        newHoldingsWidth = gameInfo.holdingsSize = 0;
2555      };
2556
2557    if(newWidth  != gameInfo.boardWidth  ||
2558       newHeight != gameInfo.boardHeight ||
2559       newHoldingsWidth != gameInfo.holdingsWidth ) {
2560
2561      /* shift position to new playing area, if needed */
2562      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2563        for(i=0; i<BOARD_HEIGHT; i++)
2564          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2565            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2566              board[i][j];
2567        for(i=0; i<newHeight; i++) {
2568          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2569          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2570        }
2571      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2572        for(i=0; i<BOARD_HEIGHT; i++)
2573          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2574            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575              board[i][j];
2576      }
2577      board[HOLDINGS_SET] = 0;
2578      gameInfo.boardWidth  = newWidth;
2579      gameInfo.boardHeight = newHeight;
2580      gameInfo.holdingsWidth = newHoldingsWidth;
2581      gameInfo.variant = newVariant;
2582      InitDrawingSizes(-2, 0);
2583    } else gameInfo.variant = newVariant;
2584    CopyBoard(oldBoard, board);   // remember correctly formatted board
2585      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2586    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2587 }
2588
2589 static int loggedOn = FALSE;
2590
2591 /*-- Game start info cache: --*/
2592 int gs_gamenum;
2593 char gs_kind[MSG_SIZ];
2594 static char player1Name[128] = "";
2595 static char player2Name[128] = "";
2596 static char cont_seq[] = "\n\\   ";
2597 static int player1Rating = -1;
2598 static int player2Rating = -1;
2599 /*----------------------------*/
2600
2601 ColorClass curColor = ColorNormal;
2602 int suppressKibitz = 0;
2603
2604 // [HGM] seekgraph
2605 Boolean soughtPending = FALSE;
2606 Boolean seekGraphUp;
2607 #define MAX_SEEK_ADS 200
2608 #define SQUARE 0x80
2609 char *seekAdList[MAX_SEEK_ADS];
2610 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2611 float tcList[MAX_SEEK_ADS];
2612 char colorList[MAX_SEEK_ADS];
2613 int nrOfSeekAds = 0;
2614 int minRating = 1010, maxRating = 2800;
2615 int hMargin = 10, vMargin = 20, h, w;
2616 extern int squareSize, lineGap;
2617
2618 void
2619 PlotSeekAd (int i)
2620 {
2621         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2622         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2623         if(r < minRating+100 && r >=0 ) r = minRating+100;
2624         if(r > maxRating) r = maxRating;
2625         if(tc < 1.f) tc = 1.f;
2626         if(tc > 95.f) tc = 95.f;
2627         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2628         y = ((double)r - minRating)/(maxRating - minRating)
2629             * (h-vMargin-squareSize/8-1) + vMargin;
2630         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2631         if(strstr(seekAdList[i], " u ")) color = 1;
2632         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2633            !strstr(seekAdList[i], "bullet") &&
2634            !strstr(seekAdList[i], "blitz") &&
2635            !strstr(seekAdList[i], "standard") ) color = 2;
2636         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2637         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2638 }
2639
2640 void
2641 PlotSingleSeekAd (int i)
2642 {
2643         PlotSeekAd(i);
2644 }
2645
2646 void
2647 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2648 {
2649         char buf[MSG_SIZ], *ext = "";
2650         VariantClass v = StringToVariant(type);
2651         if(strstr(type, "wild")) {
2652             ext = type + 4; // append wild number
2653             if(v == VariantFischeRandom) type = "chess960"; else
2654             if(v == VariantLoadable) type = "setup"; else
2655             type = VariantName(v);
2656         }
2657         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2658         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2659             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2660             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2661             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2662             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2663             seekNrList[nrOfSeekAds] = nr;
2664             zList[nrOfSeekAds] = 0;
2665             seekAdList[nrOfSeekAds++] = StrSave(buf);
2666             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2667         }
2668 }
2669
2670 void
2671 EraseSeekDot (int i)
2672 {
2673     int x = xList[i], y = yList[i], d=squareSize/4, k;
2674     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2675     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2676     // now replot every dot that overlapped
2677     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2678         int xx = xList[k], yy = yList[k];
2679         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2680             DrawSeekDot(xx, yy, colorList[k]);
2681     }
2682 }
2683
2684 void
2685 RemoveSeekAd (int nr)
2686 {
2687         int i;
2688         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2689             EraseSeekDot(i);
2690             if(seekAdList[i]) free(seekAdList[i]);
2691             seekAdList[i] = seekAdList[--nrOfSeekAds];
2692             seekNrList[i] = seekNrList[nrOfSeekAds];
2693             ratingList[i] = ratingList[nrOfSeekAds];
2694             colorList[i]  = colorList[nrOfSeekAds];
2695             tcList[i] = tcList[nrOfSeekAds];
2696             xList[i]  = xList[nrOfSeekAds];
2697             yList[i]  = yList[nrOfSeekAds];
2698             zList[i]  = zList[nrOfSeekAds];
2699             seekAdList[nrOfSeekAds] = NULL;
2700             break;
2701         }
2702 }
2703
2704 Boolean
2705 MatchSoughtLine (char *line)
2706 {
2707     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2708     int nr, base, inc, u=0; char dummy;
2709
2710     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2712        (u=1) &&
2713        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2714         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2715         // match: compact and save the line
2716         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2717         return TRUE;
2718     }
2719     return FALSE;
2720 }
2721
2722 int
2723 DrawSeekGraph ()
2724 {
2725     int i;
2726     if(!seekGraphUp) return FALSE;
2727     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2728     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2729
2730     DrawSeekBackground(0, 0, w, h);
2731     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2732     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2733     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2734         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2735         yy = h-1-yy;
2736         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2737         if(i%500 == 0) {
2738             char buf[MSG_SIZ];
2739             snprintf(buf, MSG_SIZ, "%d", i);
2740             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2741         }
2742     }
2743     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2744     for(i=1; i<100; i+=(i<10?1:5)) {
2745         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2746         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2747         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2748             char buf[MSG_SIZ];
2749             snprintf(buf, MSG_SIZ, "%d", i);
2750             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2751         }
2752     }
2753     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2754     return TRUE;
2755 }
2756
2757 int
2758 SeekGraphClick (ClickType click, int x, int y, int moving)
2759 {
2760     static int lastDown = 0, displayed = 0, lastSecond;
2761     if(y < 0) return FALSE;
2762     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2763         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2764         if(!seekGraphUp) return FALSE;
2765         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2766         DrawPosition(TRUE, NULL);
2767         return TRUE;
2768     }
2769     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2770         if(click == Release || moving) return FALSE;
2771         nrOfSeekAds = 0;
2772         soughtPending = TRUE;
2773         SendToICS(ics_prefix);
2774         SendToICS("sought\n"); // should this be "sought all"?
2775     } else { // issue challenge based on clicked ad
2776         int dist = 10000; int i, closest = 0, second = 0;
2777         for(i=0; i<nrOfSeekAds; i++) {
2778             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2779             if(d < dist) { dist = d; closest = i; }
2780             second += (d - zList[i] < 120); // count in-range ads
2781             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2782         }
2783         if(dist < 120) {
2784             char buf[MSG_SIZ];
2785             second = (second > 1);
2786             if(displayed != closest || second != lastSecond) {
2787                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2788                 lastSecond = second; displayed = closest;
2789             }
2790             if(click == Press) {
2791                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2792                 lastDown = closest;
2793                 return TRUE;
2794             } // on press 'hit', only show info
2795             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2796             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2797             SendToICS(ics_prefix);
2798             SendToICS(buf);
2799             return TRUE; // let incoming board of started game pop down the graph
2800         } else if(click == Release) { // release 'miss' is ignored
2801             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2802             if(moving == 2) { // right up-click
2803                 nrOfSeekAds = 0; // refresh graph
2804                 soughtPending = TRUE;
2805                 SendToICS(ics_prefix);
2806                 SendToICS("sought\n"); // should this be "sought all"?
2807             }
2808             return TRUE;
2809         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2810         // press miss or release hit 'pop down' seek graph
2811         seekGraphUp = FALSE;
2812         DrawPosition(TRUE, NULL);
2813     }
2814     return TRUE;
2815 }
2816
2817 void
2818 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2819 {
2820 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2821 #define STARTED_NONE 0
2822 #define STARTED_MOVES 1
2823 #define STARTED_BOARD 2
2824 #define STARTED_OBSERVE 3
2825 #define STARTED_HOLDINGS 4
2826 #define STARTED_CHATTER 5
2827 #define STARTED_COMMENT 6
2828 #define STARTED_MOVES_NOHIDE 7
2829
2830     static int started = STARTED_NONE;
2831     static char parse[20000];
2832     static int parse_pos = 0;
2833     static char buf[BUF_SIZE + 1];
2834     static int firstTime = TRUE, intfSet = FALSE;
2835     static ColorClass prevColor = ColorNormal;
2836     static int savingComment = FALSE;
2837     static int cmatch = 0; // continuation sequence match
2838     char *bp;
2839     char str[MSG_SIZ];
2840     int i, oldi;
2841     int buf_len;
2842     int next_out;
2843     int tkind;
2844     int backup;    /* [DM] For zippy color lines */
2845     char *p;
2846     char talker[MSG_SIZ]; // [HGM] chat
2847     int channel, collective=0;
2848
2849     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2850
2851     if (appData.debugMode) {
2852       if (!error) {
2853         fprintf(debugFP, "<ICS: ");
2854         show_bytes(debugFP, data, count);
2855         fprintf(debugFP, "\n");
2856       }
2857     }
2858
2859     if (appData.debugMode) { int f = forwardMostMove;
2860         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2861                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2862                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2863     }
2864     if (count > 0) {
2865         /* If last read ended with a partial line that we couldn't parse,
2866            prepend it to the new read and try again. */
2867         if (leftover_len > 0) {
2868             for (i=0; i<leftover_len; i++)
2869               buf[i] = buf[leftover_start + i];
2870         }
2871
2872     /* copy new characters into the buffer */
2873     bp = buf + leftover_len;
2874     buf_len=leftover_len;
2875     for (i=0; i<count; i++)
2876     {
2877         // ignore these
2878         if (data[i] == '\r')
2879             continue;
2880
2881         // join lines split by ICS?
2882         if (!appData.noJoin)
2883         {
2884             /*
2885                 Joining just consists of finding matches against the
2886                 continuation sequence, and discarding that sequence
2887                 if found instead of copying it.  So, until a match
2888                 fails, there's nothing to do since it might be the
2889                 complete sequence, and thus, something we don't want
2890                 copied.
2891             */
2892             if (data[i] == cont_seq[cmatch])
2893             {
2894                 cmatch++;
2895                 if (cmatch == strlen(cont_seq))
2896                 {
2897                     cmatch = 0; // complete match.  just reset the counter
2898
2899                     /*
2900                         it's possible for the ICS to not include the space
2901                         at the end of the last word, making our [correct]
2902                         join operation fuse two separate words.  the server
2903                         does this when the space occurs at the width setting.
2904                     */
2905                     if (!buf_len || buf[buf_len-1] != ' ')
2906                     {
2907                         *bp++ = ' ';
2908                         buf_len++;
2909                     }
2910                 }
2911                 continue;
2912             }
2913             else if (cmatch)
2914             {
2915                 /*
2916                     match failed, so we have to copy what matched before
2917                     falling through and copying this character.  In reality,
2918                     this will only ever be just the newline character, but
2919                     it doesn't hurt to be precise.
2920                 */
2921                 strncpy(bp, cont_seq, cmatch);
2922                 bp += cmatch;
2923                 buf_len += cmatch;
2924                 cmatch = 0;
2925             }
2926         }
2927
2928         // copy this char
2929         *bp++ = data[i];
2930         buf_len++;
2931     }
2932
2933         buf[buf_len] = NULLCHAR;
2934 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2935         next_out = 0;
2936         leftover_start = 0;
2937
2938         i = 0;
2939         while (i < buf_len) {
2940             /* Deal with part of the TELNET option negotiation
2941                protocol.  We refuse to do anything beyond the
2942                defaults, except that we allow the WILL ECHO option,
2943                which ICS uses to turn off password echoing when we are
2944                directly connected to it.  We reject this option
2945                if localLineEditing mode is on (always on in xboard)
2946                and we are talking to port 23, which might be a real
2947                telnet server that will try to keep WILL ECHO on permanently.
2948              */
2949             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2950                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2951                 unsigned char option;
2952                 oldi = i;
2953                 switch ((unsigned char) buf[++i]) {
2954                   case TN_WILL:
2955                     if (appData.debugMode)
2956                       fprintf(debugFP, "\n<WILL ");
2957                     switch (option = (unsigned char) buf[++i]) {
2958                       case TN_ECHO:
2959                         if (appData.debugMode)
2960                           fprintf(debugFP, "ECHO ");
2961                         /* Reply only if this is a change, according
2962                            to the protocol rules. */
2963                         if (remoteEchoOption) break;
2964                         if (appData.localLineEditing &&
2965                             atoi(appData.icsPort) == TN_PORT) {
2966                             TelnetRequest(TN_DONT, TN_ECHO);
2967                         } else {
2968                             EchoOff();
2969                             TelnetRequest(TN_DO, TN_ECHO);
2970                             remoteEchoOption = TRUE;
2971                         }
2972                         break;
2973                       default:
2974                         if (appData.debugMode)
2975                           fprintf(debugFP, "%d ", option);
2976                         /* Whatever this is, we don't want it. */
2977                         TelnetRequest(TN_DONT, option);
2978                         break;
2979                     }
2980                     break;
2981                   case TN_WONT:
2982                     if (appData.debugMode)
2983                       fprintf(debugFP, "\n<WONT ");
2984                     switch (option = (unsigned char) buf[++i]) {
2985                       case TN_ECHO:
2986                         if (appData.debugMode)
2987                           fprintf(debugFP, "ECHO ");
2988                         /* Reply only if this is a change, according
2989                            to the protocol rules. */
2990                         if (!remoteEchoOption) break;
2991                         EchoOn();
2992                         TelnetRequest(TN_DONT, TN_ECHO);
2993                         remoteEchoOption = FALSE;
2994                         break;
2995                       default:
2996                         if (appData.debugMode)
2997                           fprintf(debugFP, "%d ", (unsigned char) option);
2998                         /* Whatever this is, it must already be turned
2999                            off, because we never agree to turn on
3000                            anything non-default, so according to the
3001                            protocol rules, we don't reply. */
3002                         break;
3003                     }
3004                     break;
3005                   case TN_DO:
3006                     if (appData.debugMode)
3007                       fprintf(debugFP, "\n<DO ");
3008                     switch (option = (unsigned char) buf[++i]) {
3009                       default:
3010                         /* Whatever this is, we refuse to do it. */
3011                         if (appData.debugMode)
3012                           fprintf(debugFP, "%d ", option);
3013                         TelnetRequest(TN_WONT, option);
3014                         break;
3015                     }
3016                     break;
3017                   case TN_DONT:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<DONT ");
3020                     switch (option = (unsigned char) buf[++i]) {
3021                       default:
3022                         if (appData.debugMode)
3023                           fprintf(debugFP, "%d ", option);
3024                         /* Whatever this is, we are already not doing
3025                            it, because we never agree to do anything
3026                            non-default, so according to the protocol
3027                            rules, we don't reply. */
3028                         break;
3029                     }
3030                     break;
3031                   case TN_IAC:
3032                     if (appData.debugMode)
3033                       fprintf(debugFP, "\n<IAC ");
3034                     /* Doubled IAC; pass it through */
3035                     i--;
3036                     break;
3037                   default:
3038                     if (appData.debugMode)
3039                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3040                     /* Drop all other telnet commands on the floor */
3041                     break;
3042                 }
3043                 if (oldi > next_out)
3044                   SendToPlayer(&buf[next_out], oldi - next_out);
3045                 if (++i > next_out)
3046                   next_out = i;
3047                 continue;
3048             }
3049
3050             /* OK, this at least will *usually* work */
3051             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3052                 loggedOn = TRUE;
3053             }
3054
3055             if (loggedOn && !intfSet) {
3056                 if (ics_type == ICS_ICC) {
3057                   snprintf(str, MSG_SIZ,
3058                           "/set-quietly interface %s\n/set-quietly style 12\n",
3059                           programVersion);
3060                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3061                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3062                 } else if (ics_type == ICS_CHESSNET) {
3063                   snprintf(str, MSG_SIZ, "/style 12\n");
3064                 } else {
3065                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3066                   strcat(str, programVersion);
3067                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3068                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3070 #ifdef WIN32
3071                   strcat(str, "$iset nohighlight 1\n");
3072 #endif
3073                   strcat(str, "$iset lock 1\n$style 12\n");
3074                 }
3075                 SendToICS(str);
3076                 NotifyFrontendLogin();
3077                 intfSet = TRUE;
3078             }
3079
3080             if (started == STARTED_COMMENT) {
3081                 /* Accumulate characters in comment */
3082                 parse[parse_pos++] = buf[i];
3083                 if (buf[i] == '\n') {
3084                     parse[parse_pos] = NULLCHAR;
3085                     if(chattingPartner>=0) {
3086                         char mess[MSG_SIZ];
3087                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3088                         OutputChatMessage(chattingPartner, mess);
3089                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3090                             int p;
3091                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3092                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3093                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3094                                 OutputChatMessage(p, mess);
3095                                 break;
3096                             }
3097                         }
3098                         chattingPartner = -1;
3099                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3100                         collective = 0;
3101                     } else
3102                     if(!suppressKibitz) // [HGM] kibitz
3103                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3104                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3105                         int nrDigit = 0, nrAlph = 0, j;
3106                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3107                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3108                         parse[parse_pos] = NULLCHAR;
3109                         // try to be smart: if it does not look like search info, it should go to
3110                         // ICS interaction window after all, not to engine-output window.
3111                         for(j=0; j<parse_pos; j++) { // count letters and digits
3112                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3113                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3114                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3115                         }
3116                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3117                             int depth=0; float score;
3118                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3119                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3120                                 pvInfoList[forwardMostMove-1].depth = depth;
3121                                 pvInfoList[forwardMostMove-1].score = 100*score;
3122                             }
3123                             OutputKibitz(suppressKibitz, parse);
3124                         } else {
3125                             char tmp[MSG_SIZ];
3126                             if(gameMode == IcsObserving) // restore original ICS messages
3127                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3129                             else
3130                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3131                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3132                             SendToPlayer(tmp, strlen(tmp));
3133                         }
3134                         next_out = i+1; // [HGM] suppress printing in ICS window
3135                     }
3136                     started = STARTED_NONE;
3137                 } else {
3138                     /* Don't match patterns against characters in comment */
3139                     i++;
3140                     continue;
3141                 }
3142             }
3143             if (started == STARTED_CHATTER) {
3144                 if (buf[i] != '\n') {
3145                     /* Don't match patterns against characters in chatter */
3146                     i++;
3147                     continue;
3148                 }
3149                 started = STARTED_NONE;
3150                 if(suppressKibitz) next_out = i+1;
3151             }
3152
3153             /* Kludge to deal with rcmd protocol */
3154             if (firstTime && looking_at(buf, &i, "\001*")) {
3155                 DisplayFatalError(&buf[1], 0, 1);
3156                 continue;
3157             } else {
3158                 firstTime = FALSE;
3159             }
3160
3161             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3162                 ics_type = ICS_ICC;
3163                 ics_prefix = "/";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3169                 ics_type = ICS_FICS;
3170                 ics_prefix = "$";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3176                 ics_type = ICS_CHESSNET;
3177                 ics_prefix = "/";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182
3183             if (!loggedOn &&
3184                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3185                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3186                  looking_at(buf, &i, "will be \"*\""))) {
3187               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3188               continue;
3189             }
3190
3191             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3192               char buf[MSG_SIZ];
3193               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3194               DisplayIcsInteractionTitle(buf);
3195               have_set_title = TRUE;
3196             }
3197
3198             /* skip finger notes */
3199             if (started == STARTED_NONE &&
3200                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3201                  (buf[i] == '1' && buf[i+1] == '0')) &&
3202                 buf[i+2] == ':' && buf[i+3] == ' ') {
3203               started = STARTED_CHATTER;
3204               i += 3;
3205               continue;
3206             }
3207
3208             oldi = i;
3209             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3210             if(appData.seekGraph) {
3211                 if(soughtPending && MatchSoughtLine(buf+i)) {
3212                     i = strstr(buf+i, "rated") - buf;
3213                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3214                     next_out = leftover_start = i;
3215                     started = STARTED_CHATTER;
3216                     suppressKibitz = TRUE;
3217                     continue;
3218                 }
3219                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3220                         && looking_at(buf, &i, "* ads displayed")) {
3221                     soughtPending = FALSE;
3222                     seekGraphUp = TRUE;
3223                     DrawSeekGraph();
3224                     continue;
3225                 }
3226                 if(appData.autoRefresh) {
3227                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3228                         int s = (ics_type == ICS_ICC); // ICC format differs
3229                         if(seekGraphUp)
3230                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3231                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3232                         looking_at(buf, &i, "*% "); // eat prompt
3233                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3234                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3235                         next_out = i; // suppress
3236                         continue;
3237                     }
3238                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3239                         char *p = star_match[0];
3240                         while(*p) {
3241                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3242                             while(*p && *p++ != ' '); // next
3243                         }
3244                         looking_at(buf, &i, "*% "); // eat prompt
3245                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246                         next_out = i;
3247                         continue;
3248                     }
3249                 }
3250             }
3251
3252             /* skip formula vars */
3253             if (started == STARTED_NONE &&
3254                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3255               started = STARTED_CHATTER;
3256               i += 3;
3257               continue;
3258             }
3259
3260             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3261             if (appData.autoKibitz && started == STARTED_NONE &&
3262                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3263                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3264                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3265                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3266                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3267                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3268                         suppressKibitz = TRUE;
3269                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270                         next_out = i;
3271                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3272                                 && (gameMode == IcsPlayingWhite)) ||
3273                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3274                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3275                             started = STARTED_CHATTER; // own kibitz we simply discard
3276                         else {
3277                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3278                             parse_pos = 0; parse[0] = NULLCHAR;
3279                             savingComment = TRUE;
3280                             suppressKibitz = gameMode != IcsObserving ? 2 :
3281                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3282                         }
3283                         continue;
3284                 } else
3285                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3286                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3287                          && atoi(star_match[0])) {
3288                     // suppress the acknowledgements of our own autoKibitz
3289                     char *p;
3290                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3291                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3292                     SendToPlayer(star_match[0], strlen(star_match[0]));
3293                     if(looking_at(buf, &i, "*% ")) // eat prompt
3294                         suppressKibitz = FALSE;
3295                     next_out = i;
3296                     continue;
3297                 }
3298             } // [HGM] kibitz: end of patch
3299
3300             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3301
3302             // [HGM] chat: intercept tells by users for which we have an open chat window
3303             channel = -1;
3304             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3305                                            looking_at(buf, &i, "* whispers:") ||
3306                                            looking_at(buf, &i, "* kibitzes:") ||
3307                                            looking_at(buf, &i, "* shouts:") ||
3308                                            looking_at(buf, &i, "* c-shouts:") ||
3309                                            looking_at(buf, &i, "--> * ") ||
3310                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3311                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3314                 int p;
3315                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3316                 chattingPartner = -1; collective = 0;
3317
3318                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3319                 for(p=0; p<MAX_CHAT; p++) {
3320                     collective = 1;
3321                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3322                     talker[0] = '['; strcat(talker, "] ");
3323                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3324                     chattingPartner = p; break;
3325                     }
3326                 } else
3327                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(!strcmp("kibitzes", chatPartner[p])) {
3331                         talker[0] = '['; strcat(talker, "] ");
3332                         chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3336                 for(p=0; p<MAX_CHAT; p++) {
3337                     collective = 1;
3338                     if(!strcmp("whispers", chatPartner[p])) {
3339                         talker[0] = '['; strcat(talker, "] ");
3340                         chattingPartner = p; break;
3341                     }
3342                 } else
3343                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3344                   if(buf[i-8] == '-' && buf[i-3] == 't')
3345                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3346                     collective = 1;
3347                     if(!strcmp("c-shouts", chatPartner[p])) {
3348                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3349                         chattingPartner = p; break;
3350                     }
3351                   }
3352                   if(chattingPartner < 0)
3353                   for(p=0; p<MAX_CHAT; p++) {
3354                     collective = 1;
3355                     if(!strcmp("shouts", chatPartner[p])) {
3356                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3357                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3358                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3359                         chattingPartner = p; break;
3360                     }
3361                   }
3362                 }
3363                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3364                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3365                     talker[0] = 0;
3366                     Colorize(ColorTell, FALSE);
3367                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3368                     collective |= 2;
3369                     chattingPartner = p; break;
3370                 }
3371                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3372                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3373                     started = STARTED_COMMENT;
3374                     parse_pos = 0; parse[0] = NULLCHAR;
3375                     savingComment = 3 + chattingPartner; // counts as TRUE
3376                     if(collective == 3) i = oldi; else {
3377                         suppressKibitz = TRUE;
3378                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3379                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3380                         continue;
3381                     }
3382                 }
3383             } // [HGM] chat: end of patch
3384
3385           backup = i;
3386             if (appData.zippyTalk || appData.zippyPlay) {
3387                 /* [DM] Backup address for color zippy lines */
3388 #if ZIPPY
3389                if (loggedOn == TRUE)
3390                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3391                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3392 #endif
3393             } // [DM] 'else { ' deleted
3394                 if (
3395                     /* Regular tells and says */
3396                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3397                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3398                     looking_at(buf, &i, "* says: ") ||
3399                     /* Don't color "message" or "messages" output */
3400                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3401                     looking_at(buf, &i, "*. * at *:*: ") ||
3402                     looking_at(buf, &i, "--* (*:*): ") ||
3403                     /* Message notifications (same color as tells) */
3404                     looking_at(buf, &i, "* has left a message ") ||
3405                     looking_at(buf, &i, "* just sent you a message:\n") ||
3406                     /* Whispers and kibitzes */
3407                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3408                     looking_at(buf, &i, "* kibitzes: ") ||
3409                     /* Channel tells */
3410                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3411
3412                   if (tkind == 1 && strchr(star_match[0], ':')) {
3413                       /* Avoid "tells you:" spoofs in channels */
3414                      tkind = 3;
3415                   }
3416                   if (star_match[0][0] == NULLCHAR ||
3417                       strchr(star_match[0], ' ') ||
3418                       (tkind == 3 && strchr(star_match[1], ' '))) {
3419                     /* Reject bogus matches */
3420                     i = oldi;
3421                   } else {
3422                     if (appData.colorize) {
3423                       if (oldi > next_out) {
3424                         SendToPlayer(&buf[next_out], oldi - next_out);
3425                         next_out = oldi;
3426                       }
3427                       switch (tkind) {
3428                       case 1:
3429                         Colorize(ColorTell, FALSE);
3430                         curColor = ColorTell;
3431                         break;
3432                       case 2:
3433                         Colorize(ColorKibitz, FALSE);
3434                         curColor = ColorKibitz;
3435                         break;
3436                       case 3:
3437                         p = strrchr(star_match[1], '(');
3438                         if (p == NULL) {
3439                           p = star_match[1];
3440                         } else {
3441                           p++;
3442                         }
3443                         if (atoi(p) == 1) {
3444                           Colorize(ColorChannel1, FALSE);
3445                           curColor = ColorChannel1;
3446                         } else {
3447                           Colorize(ColorChannel, FALSE);
3448                           curColor = ColorChannel;
3449                         }
3450                         break;
3451                       case 5:
3452                         curColor = ColorNormal;
3453                         break;
3454                       }
3455                     }
3456                     if (started == STARTED_NONE && appData.autoComment &&
3457                         (gameMode == IcsObserving ||
3458                          gameMode == IcsPlayingWhite ||
3459                          gameMode == IcsPlayingBlack)) {
3460                       parse_pos = i - oldi;
3461                       memcpy(parse, &buf[oldi], parse_pos);
3462                       parse[parse_pos] = NULLCHAR;
3463                       started = STARTED_COMMENT;
3464                       savingComment = TRUE;
3465                     } else if(collective != 3) {
3466                       started = STARTED_CHATTER;
3467                       savingComment = FALSE;
3468                     }
3469                     loggedOn = TRUE;
3470                     continue;
3471                   }
3472                 }
3473
3474                 if (looking_at(buf, &i, "* s-shouts: ") ||
3475                     looking_at(buf, &i, "* c-shouts: ")) {
3476                     if (appData.colorize) {
3477                         if (oldi > next_out) {
3478                             SendToPlayer(&buf[next_out], oldi - next_out);
3479                             next_out = oldi;
3480                         }
3481                         Colorize(ColorSShout, FALSE);
3482                         curColor = ColorSShout;
3483                     }
3484                     loggedOn = TRUE;
3485                     started = STARTED_CHATTER;
3486                     continue;
3487                 }
3488
3489                 if (looking_at(buf, &i, "--->")) {
3490                     loggedOn = TRUE;
3491                     continue;
3492                 }
3493
3494                 if (looking_at(buf, &i, "* shouts: ") ||
3495                     looking_at(buf, &i, "--> ")) {
3496                     if (appData.colorize) {
3497                         if (oldi > next_out) {
3498                             SendToPlayer(&buf[next_out], oldi - next_out);
3499                             next_out = oldi;
3500                         }
3501                         Colorize(ColorShout, FALSE);
3502                         curColor = ColorShout;
3503                     }
3504                     loggedOn = TRUE;
3505                     started = STARTED_CHATTER;
3506                     continue;
3507                 }
3508
3509                 if (looking_at( buf, &i, "Challenge:")) {
3510                     if (appData.colorize) {
3511                         if (oldi > next_out) {
3512                             SendToPlayer(&buf[next_out], oldi - next_out);
3513                             next_out = oldi;
3514                         }
3515                         Colorize(ColorChallenge, FALSE);
3516                         curColor = ColorChallenge;
3517                     }
3518                     loggedOn = TRUE;
3519                     continue;
3520                 }
3521
3522                 if (looking_at(buf, &i, "* offers you") ||
3523                     looking_at(buf, &i, "* offers to be") ||
3524                     looking_at(buf, &i, "* would like to") ||
3525                     looking_at(buf, &i, "* requests to") ||
3526                     looking_at(buf, &i, "Your opponent offers") ||
3527                     looking_at(buf, &i, "Your opponent requests")) {
3528
3529                     if (appData.colorize) {
3530                         if (oldi > next_out) {
3531                             SendToPlayer(&buf[next_out], oldi - next_out);
3532                             next_out = oldi;
3533                         }
3534                         Colorize(ColorRequest, FALSE);
3535                         curColor = ColorRequest;
3536                     }
3537                     continue;
3538                 }
3539
3540                 if (looking_at(buf, &i, "* (*) seeking")) {
3541                     if (appData.colorize) {
3542                         if (oldi > next_out) {
3543                             SendToPlayer(&buf[next_out], oldi - next_out);
3544                             next_out = oldi;
3545                         }
3546                         Colorize(ColorSeek, FALSE);
3547                         curColor = ColorSeek;
3548                     }
3549                     continue;
3550             }
3551
3552           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3553
3554             if (looking_at(buf, &i, "\\   ")) {
3555                 if (prevColor != ColorNormal) {
3556                     if (oldi > next_out) {
3557                         SendToPlayer(&buf[next_out], oldi - next_out);
3558                         next_out = oldi;
3559                     }
3560                     Colorize(prevColor, TRUE);
3561                     curColor = prevColor;
3562                 }
3563                 if (savingComment) {
3564                     parse_pos = i - oldi;
3565                     memcpy(parse, &buf[oldi], parse_pos);
3566                     parse[parse_pos] = NULLCHAR;
3567                     started = STARTED_COMMENT;
3568                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3569                         chattingPartner = savingComment - 3; // kludge to remember the box
3570                 } else {
3571                     started = STARTED_CHATTER;
3572                 }
3573                 continue;
3574             }
3575
3576             if (looking_at(buf, &i, "Black Strength :") ||
3577                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3578                 looking_at(buf, &i, "<10>") ||
3579                 looking_at(buf, &i, "#@#")) {
3580                 /* Wrong board style */
3581                 loggedOn = TRUE;
3582                 SendToICS(ics_prefix);
3583                 SendToICS("set style 12\n");
3584                 SendToICS(ics_prefix);
3585                 SendToICS("refresh\n");
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "login:")) {
3590               if (!have_sent_ICS_logon) {
3591                 if(ICSInitScript())
3592                   have_sent_ICS_logon = 1;
3593                 else // no init script was found
3594                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3595               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3596                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3597               }
3598                 continue;
3599             }
3600
3601             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3602                 (looking_at(buf, &i, "\n<12> ") ||
3603                  looking_at(buf, &i, "<12> "))) {
3604                 loggedOn = TRUE;
3605                 if (oldi > next_out) {
3606                     SendToPlayer(&buf[next_out], oldi - next_out);
3607                 }
3608                 next_out = i;
3609                 started = STARTED_BOARD;
3610                 parse_pos = 0;
3611                 continue;
3612             }
3613
3614             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3615                 looking_at(buf, &i, "<b1> ")) {
3616                 if (oldi > next_out) {
3617                     SendToPlayer(&buf[next_out], oldi - next_out);
3618                 }
3619                 next_out = i;
3620                 started = STARTED_HOLDINGS;
3621                 parse_pos = 0;
3622                 continue;
3623             }
3624
3625             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3626                 loggedOn = TRUE;
3627                 /* Header for a move list -- first line */
3628
3629                 switch (ics_getting_history) {
3630                   case H_FALSE:
3631                     switch (gameMode) {
3632                       case IcsIdle:
3633                       case BeginningOfGame:
3634                         /* User typed "moves" or "oldmoves" while we
3635                            were idle.  Pretend we asked for these
3636                            moves and soak them up so user can step
3637                            through them and/or save them.
3638                            */
3639                         Reset(FALSE, TRUE);
3640                         gameMode = IcsObserving;
3641                         ModeHighlight();
3642                         ics_gamenum = -1;
3643                         ics_getting_history = H_GOT_UNREQ_HEADER;
3644                         break;
3645                       case EditGame: /*?*/
3646                       case EditPosition: /*?*/
3647                         /* Should above feature work in these modes too? */
3648                         /* For now it doesn't */
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                       default:
3652                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3653                         break;
3654                     }
3655                     break;
3656                   case H_REQUESTED:
3657                     /* Is this the right one? */
3658                     if (gameInfo.white && gameInfo.black &&
3659                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3660                         strcmp(gameInfo.black, star_match[2]) == 0) {
3661                         /* All is well */
3662                         ics_getting_history = H_GOT_REQ_HEADER;
3663                     }
3664                     break;
3665                   case H_GOT_REQ_HEADER:
3666                   case H_GOT_UNREQ_HEADER:
3667                   case H_GOT_UNWANTED_HEADER:
3668                   case H_GETTING_MOVES:
3669                     /* Should not happen */
3670                     DisplayError(_("Error gathering move list: two headers"), 0);
3671                     ics_getting_history = H_FALSE;
3672                     break;
3673                 }
3674
3675                 /* Save player ratings into gameInfo if needed */
3676                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3677                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3678                     (gameInfo.whiteRating == -1 ||
3679                      gameInfo.blackRating == -1)) {
3680
3681                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3682                     gameInfo.blackRating = string_to_rating(star_match[3]);
3683                     if (appData.debugMode)
3684                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3685                               gameInfo.whiteRating, gameInfo.blackRating);
3686                 }
3687                 continue;
3688             }
3689
3690             if (looking_at(buf, &i,
3691               "* * match, initial time: * minute*, increment: * second")) {
3692                 /* Header for a move list -- second line */
3693                 /* Initial board will follow if this is a wild game */
3694                 if (gameInfo.event != NULL) free(gameInfo.event);
3695                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3696                 gameInfo.event = StrSave(str);
3697                 /* [HGM] we switched variant. Translate boards if needed. */
3698                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3699                 continue;
3700             }
3701
3702             if (looking_at(buf, &i, "Move  ")) {
3703                 /* Beginning of a move list */
3704                 switch (ics_getting_history) {
3705                   case H_FALSE:
3706                     /* Normally should not happen */
3707                     /* Maybe user hit reset while we were parsing */
3708                     break;
3709                   case H_REQUESTED:
3710                     /* Happens if we are ignoring a move list that is not
3711                      * the one we just requested.  Common if the user
3712                      * tries to observe two games without turning off
3713                      * getMoveList */
3714                     break;
3715                   case H_GETTING_MOVES:
3716                     /* Should not happen */
3717                     DisplayError(_("Error gathering move list: nested"), 0);
3718                     ics_getting_history = H_FALSE;
3719                     break;
3720                   case H_GOT_REQ_HEADER:
3721                     ics_getting_history = H_GETTING_MOVES;
3722                     started = STARTED_MOVES;
3723                     parse_pos = 0;
3724                     if (oldi > next_out) {
3725                         SendToPlayer(&buf[next_out], oldi - next_out);
3726                     }
3727                     break;
3728                   case H_GOT_UNREQ_HEADER:
3729                     ics_getting_history = H_GETTING_MOVES;
3730                     started = STARTED_MOVES_NOHIDE;
3731                     parse_pos = 0;
3732                     break;
3733                   case H_GOT_UNWANTED_HEADER:
3734                     ics_getting_history = H_FALSE;
3735                     break;
3736                 }
3737                 continue;
3738             }
3739
3740             if (looking_at(buf, &i, "% ") ||
3741                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3742                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3743                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3744                     soughtPending = FALSE;
3745                     seekGraphUp = TRUE;
3746                     DrawSeekGraph();
3747                 }
3748                 if(suppressKibitz) next_out = i;
3749                 savingComment = FALSE;
3750                 suppressKibitz = 0;
3751                 switch (started) {
3752                   case STARTED_MOVES:
3753                   case STARTED_MOVES_NOHIDE:
3754                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3755                     parse[parse_pos + i - oldi] = NULLCHAR;
3756                     ParseGameHistory(parse);
3757 #if ZIPPY
3758                     if (appData.zippyPlay && first.initDone) {
3759                         FeedMovesToProgram(&first, forwardMostMove);
3760                         if (gameMode == IcsPlayingWhite) {
3761                             if (WhiteOnMove(forwardMostMove)) {
3762                                 if (first.sendTime) {
3763                                   if (first.useColors) {
3764                                     SendToProgram("black\n", &first);
3765                                   }
3766                                   SendTimeRemaining(&first, TRUE);
3767                                 }
3768                                 if (first.useColors) {
3769                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3770                                 }
3771                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3772                                 first.maybeThinking = TRUE;
3773                             } else {
3774                                 if (first.usePlayother) {
3775                                   if (first.sendTime) {
3776                                     SendTimeRemaining(&first, TRUE);
3777                                   }
3778                                   SendToProgram("playother\n", &first);
3779                                   firstMove = FALSE;
3780                                 } else {
3781                                   firstMove = TRUE;
3782                                 }
3783                             }
3784                         } else if (gameMode == IcsPlayingBlack) {
3785                             if (!WhiteOnMove(forwardMostMove)) {
3786                                 if (first.sendTime) {
3787                                   if (first.useColors) {
3788                                     SendToProgram("white\n", &first);
3789                                   }
3790                                   SendTimeRemaining(&first, FALSE);
3791                                 }
3792                                 if (first.useColors) {
3793                                   SendToProgram("black\n", &first);
3794                                 }
3795                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3796                                 first.maybeThinking = TRUE;
3797                             } else {
3798                                 if (first.usePlayother) {
3799                                   if (first.sendTime) {
3800                                     SendTimeRemaining(&first, FALSE);
3801                                   }
3802                                   SendToProgram("playother\n", &first);
3803                                   firstMove = FALSE;
3804                                 } else {
3805                                   firstMove = TRUE;
3806                                 }
3807                             }
3808                         }
3809                     }
3810 #endif
3811                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3812                         /* Moves came from oldmoves or moves command
3813                            while we weren't doing anything else.
3814                            */
3815                         currentMove = forwardMostMove;
3816                         ClearHighlights();/*!!could figure this out*/
3817                         flipView = appData.flipView;
3818                         DrawPosition(TRUE, boards[currentMove]);
3819                         DisplayBothClocks();
3820                         snprintf(str, MSG_SIZ, "%s %s %s",
3821                                 gameInfo.white, _("vs."),  gameInfo.black);
3822                         DisplayTitle(str);
3823                         gameMode = IcsIdle;
3824                     } else {
3825                         /* Moves were history of an active game */
3826                         if (gameInfo.resultDetails != NULL) {
3827                             free(gameInfo.resultDetails);
3828                             gameInfo.resultDetails = NULL;
3829                         }
3830                     }
3831                     HistorySet(parseList, backwardMostMove,
3832                                forwardMostMove, currentMove-1);
3833                     DisplayMove(currentMove - 1);
3834                     if (started == STARTED_MOVES) next_out = i;
3835                     started = STARTED_NONE;
3836                     ics_getting_history = H_FALSE;
3837                     break;
3838
3839                   case STARTED_OBSERVE:
3840                     started = STARTED_NONE;
3841                     SendToICS(ics_prefix);
3842                     SendToICS("refresh\n");
3843                     break;
3844
3845                   default:
3846                     break;
3847                 }
3848                 if(bookHit) { // [HGM] book: simulate book reply
3849                     static char bookMove[MSG_SIZ]; // a bit generous?
3850
3851                     programStats.nodes = programStats.depth = programStats.time =
3852                     programStats.score = programStats.got_only_move = 0;
3853                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3854
3855                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3856                     strcat(bookMove, bookHit);
3857                     HandleMachineMove(bookMove, &first);
3858                 }
3859                 continue;
3860             }
3861
3862             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3863                  started == STARTED_HOLDINGS ||
3864                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3865                 /* Accumulate characters in move list or board */
3866                 parse[parse_pos++] = buf[i];
3867             }
3868
3869             /* Start of game messages.  Mostly we detect start of game
3870                when the first board image arrives.  On some versions
3871                of the ICS, though, we need to do a "refresh" after starting
3872                to observe in order to get the current board right away. */
3873             if (looking_at(buf, &i, "Adding game * to observation list")) {
3874                 started = STARTED_OBSERVE;
3875                 continue;
3876             }
3877
3878             /* Handle auto-observe */
3879             if (appData.autoObserve &&
3880                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3881                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3882                 char *player;
3883                 /* Choose the player that was highlighted, if any. */
3884                 if (star_match[0][0] == '\033' ||
3885                     star_match[1][0] != '\033') {
3886                     player = star_match[0];
3887                 } else {
3888                     player = star_match[2];
3889                 }
3890                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3891                         ics_prefix, StripHighlightAndTitle(player));
3892                 SendToICS(str);
3893
3894                 /* Save ratings from notify string */
3895                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3896                 player1Rating = string_to_rating(star_match[1]);
3897                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3898                 player2Rating = string_to_rating(star_match[3]);
3899
3900                 if (appData.debugMode)
3901                   fprintf(debugFP,
3902                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3903                           player1Name, player1Rating,
3904                           player2Name, player2Rating);
3905
3906                 continue;
3907             }
3908
3909             /* Deal with automatic examine mode after a game,
3910                and with IcsObserving -> IcsExamining transition */
3911             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3912                 looking_at(buf, &i, "has made you an examiner of game *")) {
3913
3914                 int gamenum = atoi(star_match[0]);
3915                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3916                     gamenum == ics_gamenum) {
3917                     /* We were already playing or observing this game;
3918                        no need to refetch history */
3919                     gameMode = IcsExamining;
3920                     if (pausing) {
3921                         pauseExamForwardMostMove = forwardMostMove;
3922                     } else if (currentMove < forwardMostMove) {
3923                         ForwardInner(forwardMostMove);
3924                     }
3925                 } else {
3926                     /* I don't think this case really can happen */
3927                     SendToICS(ics_prefix);
3928                     SendToICS("refresh\n");
3929                 }
3930                 continue;
3931             }
3932
3933             /* Error messages */
3934 //          if (ics_user_moved) {
3935             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3936                 if (looking_at(buf, &i, "Illegal move") ||
3937                     looking_at(buf, &i, "Not a legal move") ||
3938                     looking_at(buf, &i, "Your king is in check") ||
3939                     looking_at(buf, &i, "It isn't your turn") ||
3940                     looking_at(buf, &i, "It is not your move")) {
3941                     /* Illegal move */
3942                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3943                         currentMove = forwardMostMove-1;
3944                         DisplayMove(currentMove - 1); /* before DMError */
3945                         DrawPosition(FALSE, boards[currentMove]);
3946                         SwitchClocks(forwardMostMove-1); // [HGM] race
3947                         DisplayBothClocks();
3948                     }
3949                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3950                     ics_user_moved = 0;
3951                     continue;
3952                 }
3953             }
3954
3955             if (looking_at(buf, &i, "still have time") ||
3956                 looking_at(buf, &i, "not out of time") ||
3957                 looking_at(buf, &i, "either player is out of time") ||
3958                 looking_at(buf, &i, "has timeseal; checking")) {
3959                 /* We must have called his flag a little too soon */
3960                 whiteFlag = blackFlag = FALSE;
3961                 continue;
3962             }
3963
3964             if (looking_at(buf, &i, "added * seconds to") ||
3965                 looking_at(buf, &i, "seconds were added to")) {
3966                 /* Update the clocks */
3967                 SendToICS(ics_prefix);
3968                 SendToICS("refresh\n");
3969                 continue;
3970             }
3971
3972             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3973                 ics_clock_paused = TRUE;
3974                 StopClocks();
3975                 continue;
3976             }
3977
3978             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3979                 ics_clock_paused = FALSE;
3980                 StartClocks();
3981                 continue;
3982             }
3983
3984             /* Grab player ratings from the Creating: message.
3985                Note we have to check for the special case when
3986                the ICS inserts things like [white] or [black]. */
3987             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3988                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3989                 /* star_matches:
3990                    0    player 1 name (not necessarily white)
3991                    1    player 1 rating
3992                    2    empty, white, or black (IGNORED)
3993                    3    player 2 name (not necessarily black)
3994                    4    player 2 rating
3995
3996                    The names/ratings are sorted out when the game
3997                    actually starts (below).
3998                 */
3999                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4000                 player1Rating = string_to_rating(star_match[1]);
4001                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4002                 player2Rating = string_to_rating(star_match[4]);
4003
4004                 if (appData.debugMode)
4005                   fprintf(debugFP,
4006                           "Ratings from 'Creating:' %s %d, %s %d\n",
4007                           player1Name, player1Rating,
4008                           player2Name, player2Rating);
4009
4010                 continue;
4011             }
4012
4013             /* Improved generic start/end-of-game messages */
4014             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4015                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4016                 /* If tkind == 0: */
4017                 /* star_match[0] is the game number */
4018                 /*           [1] is the white player's name */
4019                 /*           [2] is the black player's name */
4020                 /* For end-of-game: */
4021                 /*           [3] is the reason for the game end */
4022                 /*           [4] is a PGN end game-token, preceded by " " */
4023                 /* For start-of-game: */
4024                 /*           [3] begins with "Creating" or "Continuing" */
4025                 /*           [4] is " *" or empty (don't care). */
4026                 int gamenum = atoi(star_match[0]);
4027                 char *whitename, *blackname, *why, *endtoken;
4028                 ChessMove endtype = EndOfFile;
4029
4030                 if (tkind == 0) {
4031                   whitename = star_match[1];
4032                   blackname = star_match[2];
4033                   why = star_match[3];
4034                   endtoken = star_match[4];
4035                 } else {
4036                   whitename = star_match[1];
4037                   blackname = star_match[3];
4038                   why = star_match[5];
4039                   endtoken = star_match[6];
4040                 }
4041
4042                 /* Game start messages */
4043                 if (strncmp(why, "Creating ", 9) == 0 ||
4044                     strncmp(why, "Continuing ", 11) == 0) {
4045                     gs_gamenum = gamenum;
4046                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4047                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4048                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4049 #if ZIPPY
4050                     if (appData.zippyPlay) {
4051                         ZippyGameStart(whitename, blackname);
4052                     }
4053 #endif /*ZIPPY*/
4054                     partnerBoardValid = FALSE; // [HGM] bughouse
4055                     continue;
4056                 }
4057
4058                 /* Game end messages */
4059                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4060                     ics_gamenum != gamenum) {
4061                     continue;
4062                 }
4063                 while (endtoken[0] == ' ') endtoken++;
4064                 switch (endtoken[0]) {
4065                   case '*':
4066                   default:
4067                     endtype = GameUnfinished;
4068                     break;
4069                   case '0':
4070                     endtype = BlackWins;
4071                     break;
4072                   case '1':
4073                     if (endtoken[1] == '/')
4074                       endtype = GameIsDrawn;
4075                     else
4076                       endtype = WhiteWins;
4077                     break;
4078                 }
4079                 GameEnds(endtype, why, GE_ICS);
4080 #if ZIPPY
4081                 if (appData.zippyPlay && first.initDone) {
4082                     ZippyGameEnd(endtype, why);
4083                     if (first.pr == NoProc) {
4084                       /* Start the next process early so that we'll
4085                          be ready for the next challenge */
4086                       StartChessProgram(&first);
4087                     }
4088                     /* Send "new" early, in case this command takes
4089                        a long time to finish, so that we'll be ready
4090                        for the next challenge. */
4091                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4092                     Reset(TRUE, TRUE);
4093                 }
4094 #endif /*ZIPPY*/
4095                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4096                 continue;
4097             }
4098
4099             if (looking_at(buf, &i, "Removing game * from observation") ||
4100                 looking_at(buf, &i, "no longer observing game *") ||
4101                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4102                 if (gameMode == IcsObserving &&
4103                     atoi(star_match[0]) == ics_gamenum)
4104                   {
4105                       /* icsEngineAnalyze */
4106                       if (appData.icsEngineAnalyze) {
4107                             ExitAnalyzeMode();
4108                             ModeHighlight();
4109                       }
4110                       StopClocks();
4111                       gameMode = IcsIdle;
4112                       ics_gamenum = -1;
4113                       ics_user_moved = FALSE;
4114                   }
4115                 continue;
4116             }
4117
4118             if (looking_at(buf, &i, "no longer examining game *")) {
4119                 if (gameMode == IcsExamining &&
4120                     atoi(star_match[0]) == ics_gamenum)
4121                   {
4122                       gameMode = IcsIdle;
4123                       ics_gamenum = -1;
4124                       ics_user_moved = FALSE;
4125                   }
4126                 continue;
4127             }
4128
4129             /* Advance leftover_start past any newlines we find,
4130                so only partial lines can get reparsed */
4131             if (looking_at(buf, &i, "\n")) {
4132                 prevColor = curColor;
4133                 if (curColor != ColorNormal) {
4134                     if (oldi > next_out) {
4135                         SendToPlayer(&buf[next_out], oldi - next_out);
4136                         next_out = oldi;
4137                     }
4138                     Colorize(ColorNormal, FALSE);
4139                     curColor = ColorNormal;
4140                 }
4141                 if (started == STARTED_BOARD) {
4142                     started = STARTED_NONE;
4143                     parse[parse_pos] = NULLCHAR;
4144                     ParseBoard12(parse);
4145                     ics_user_moved = 0;
4146
4147                     /* Send premove here */
4148                     if (appData.premove) {
4149                       char str[MSG_SIZ];
4150                       if (currentMove == 0 &&
4151                           gameMode == IcsPlayingWhite &&
4152                           appData.premoveWhite) {
4153                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                         SendToICS(str);
4157                       } else if (currentMove == 1 &&
4158                                  gameMode == IcsPlayingBlack &&
4159                                  appData.premoveBlack) {
4160                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4161                         if (appData.debugMode)
4162                           fprintf(debugFP, "Sending premove:\n");
4163                         SendToICS(str);
4164                       } else if (gotPremove) {
4165                         gotPremove = 0;
4166                         ClearPremoveHighlights();
4167                         if (appData.debugMode)
4168                           fprintf(debugFP, "Sending premove:\n");
4169                           UserMoveEvent(premoveFromX, premoveFromY,
4170                                         premoveToX, premoveToY,
4171                                         premovePromoChar);
4172                       }
4173                     }
4174
4175                     /* Usually suppress following prompt */
4176                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4177                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4178                         if (looking_at(buf, &i, "*% ")) {
4179                             savingComment = FALSE;
4180                             suppressKibitz = 0;
4181                         }
4182                     }
4183                     next_out = i;
4184                 } else if (started == STARTED_HOLDINGS) {
4185                     int gamenum;
4186                     char new_piece[MSG_SIZ];
4187                     started = STARTED_NONE;
4188                     parse[parse_pos] = NULLCHAR;
4189                     if (appData.debugMode)
4190                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4191                                                         parse, currentMove);
4192                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4193                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4194                         if (gameInfo.variant == VariantNormal) {
4195                           /* [HGM] We seem to switch variant during a game!
4196                            * Presumably no holdings were displayed, so we have
4197                            * to move the position two files to the right to
4198                            * create room for them!
4199                            */
4200                           VariantClass newVariant;
4201                           switch(gameInfo.boardWidth) { // base guess on board width
4202                                 case 9:  newVariant = VariantShogi; break;
4203                                 case 10: newVariant = VariantGreat; break;
4204                                 default: newVariant = VariantCrazyhouse; break;
4205                           }
4206                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4207                           /* Get a move list just to see the header, which
4208                              will tell us whether this is really bug or zh */
4209                           if (ics_getting_history == H_FALSE) {
4210                             ics_getting_history = H_REQUESTED;
4211                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4212                             SendToICS(str);
4213                           }
4214                         }
4215                         new_piece[0] = NULLCHAR;
4216                         sscanf(parse, "game %d white [%s black [%s <- %s",
4217                                &gamenum, white_holding, black_holding,
4218                                new_piece);
4219                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4220                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4221                         /* [HGM] copy holdings to board holdings area */
4222                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4223                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4224                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4225 #if ZIPPY
4226                         if (appData.zippyPlay && first.initDone) {
4227                             ZippyHoldings(white_holding, black_holding,
4228                                           new_piece);
4229                         }
4230 #endif /*ZIPPY*/
4231                         if (tinyLayout || smallLayout) {
4232                             char wh[16], bh[16];
4233                             PackHolding(wh, white_holding);
4234                             PackHolding(bh, black_holding);
4235                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4236                                     gameInfo.white, gameInfo.black);
4237                         } else {
4238                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4239                                     gameInfo.white, white_holding, _("vs."),
4240                                     gameInfo.black, black_holding);
4241                         }
4242                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4243                         DrawPosition(FALSE, boards[currentMove]);
4244                         DisplayTitle(str);
4245                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4246                         sscanf(parse, "game %d white [%s black [%s <- %s",
4247                                &gamenum, white_holding, black_holding,
4248                                new_piece);
4249                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4250                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4251                         /* [HGM] copy holdings to partner-board holdings area */
4252                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4253                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4254                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4255                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4256                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4257                       }
4258                     }
4259                     /* Suppress following prompt */
4260                     if (looking_at(buf, &i, "*% ")) {
4261                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4262                         savingComment = FALSE;
4263                         suppressKibitz = 0;
4264                     }
4265                     next_out = i;
4266                 }
4267                 continue;
4268             }
4269
4270             i++;                /* skip unparsed character and loop back */
4271         }
4272
4273         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4274 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4275 //          SendToPlayer(&buf[next_out], i - next_out);
4276             started != STARTED_HOLDINGS && leftover_start > next_out) {
4277             SendToPlayer(&buf[next_out], leftover_start - next_out);
4278             next_out = i;
4279         }
4280
4281         leftover_len = buf_len - leftover_start;
4282         /* if buffer ends with something we couldn't parse,
4283            reparse it after appending the next read */
4284
4285     } else if (count == 0) {
4286         RemoveInputSource(isr);
4287         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4288     } else {
4289         DisplayFatalError(_("Error reading from ICS"), error, 1);
4290     }
4291 }
4292
4293
4294 /* Board style 12 looks like this:
4295
4296    <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
4297
4298  * The "<12> " is stripped before it gets to this routine.  The two
4299  * trailing 0's (flip state and clock ticking) are later addition, and
4300  * some chess servers may not have them, or may have only the first.
4301  * Additional trailing fields may be added in the future.
4302  */
4303
4304 #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"
4305
4306 #define RELATION_OBSERVING_PLAYED    0
4307 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4308 #define RELATION_PLAYING_MYMOVE      1
4309 #define RELATION_PLAYING_NOTMYMOVE  -1
4310 #define RELATION_EXAMINING           2
4311 #define RELATION_ISOLATED_BOARD     -3
4312 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4313
4314 void
4315 ParseBoard12 (char *string)
4316 {
4317 #if ZIPPY
4318     int i, takeback;
4319     char *bookHit = NULL; // [HGM] book
4320 #endif
4321     GameMode newGameMode;
4322     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4323     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4324     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4325     char to_play, board_chars[200];
4326     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4327     char black[32], white[32];
4328     Board board;
4329     int prevMove = currentMove;
4330     int ticking = 2;
4331     ChessMove moveType;
4332     int fromX, fromY, toX, toY;
4333     char promoChar;
4334     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4335     Boolean weird = FALSE, reqFlag = FALSE;
4336
4337     fromX = fromY = toX = toY = -1;
4338
4339     newGame = FALSE;
4340
4341     if (appData.debugMode)
4342       fprintf(debugFP, "Parsing board: %s\n", string);
4343
4344     move_str[0] = NULLCHAR;
4345     elapsed_time[0] = NULLCHAR;
4346     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4347         int  i = 0, j;
4348         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4349             if(string[i] == ' ') { ranks++; files = 0; }
4350             else files++;
4351             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4352             i++;
4353         }
4354         for(j = 0; j <i; j++) board_chars[j] = string[j];
4355         board_chars[i] = '\0';
4356         string += i + 1;
4357     }
4358     n = sscanf(string, PATTERN, &to_play, &double_push,
4359                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4360                &gamenum, white, black, &relation, &basetime, &increment,
4361                &white_stren, &black_stren, &white_time, &black_time,
4362                &moveNum, str, elapsed_time, move_str, &ics_flip,
4363                &ticking);
4364
4365     if (n < 21) {
4366         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4367         DisplayError(str, 0);
4368         return;
4369     }
4370
4371     /* Convert the move number to internal form */
4372     moveNum = (moveNum - 1) * 2;
4373     if (to_play == 'B') moveNum++;
4374     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4375       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4376                         0, 1);
4377       return;
4378     }
4379
4380     switch (relation) {
4381       case RELATION_OBSERVING_PLAYED:
4382       case RELATION_OBSERVING_STATIC:
4383         if (gamenum == -1) {
4384             /* Old ICC buglet */
4385             relation = RELATION_OBSERVING_STATIC;
4386         }
4387         newGameMode = IcsObserving;
4388         break;
4389       case RELATION_PLAYING_MYMOVE:
4390       case RELATION_PLAYING_NOTMYMOVE:
4391         newGameMode =
4392           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4393             IcsPlayingWhite : IcsPlayingBlack;
4394         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4395         break;
4396       case RELATION_EXAMINING:
4397         newGameMode = IcsExamining;
4398         break;
4399       case RELATION_ISOLATED_BOARD:
4400       default:
4401         /* Just display this board.  If user was doing something else,
4402            we will forget about it until the next board comes. */
4403         newGameMode = IcsIdle;
4404         break;
4405       case RELATION_STARTING_POSITION:
4406         newGameMode = gameMode;
4407         break;
4408     }
4409
4410     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4411         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4412          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4413       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4414       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4415       static int lastBgGame = -1;
4416       char *toSqr;
4417       for (k = 0; k < ranks; k++) {
4418         for (j = 0; j < files; j++)
4419           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4420         if(gameInfo.holdingsWidth > 1) {
4421              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4422              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4423         }
4424       }
4425       CopyBoard(partnerBoard, board);
4426       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4427         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4428         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4429       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4430       if(toSqr = strchr(str, '-')) {
4431         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4432         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4433       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4434       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4435       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4436       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4437       if(twoBoards) {
4438           DisplayWhiteClock(white_time*fac, to_play == 'W');
4439           DisplayBlackClock(black_time*fac, to_play != 'W');
4440           activePartner = to_play;
4441           if(gamenum != lastBgGame) {
4442               char buf[MSG_SIZ];
4443               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4444               DisplayTitle(buf);
4445           }
4446           lastBgGame = gamenum;
4447           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4448                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4449       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4450                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4451       if(!twoBoards) DisplayMessage(partnerStatus, "");
4452         partnerBoardValid = TRUE;
4453       return;
4454     }
4455
4456     if(appData.dualBoard && appData.bgObserve) {
4457         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4458             SendToICS(ics_prefix), SendToICS("pobserve\n");
4459         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4460             char buf[MSG_SIZ];
4461             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4462             SendToICS(buf);
4463         }
4464     }
4465
4466     /* Modify behavior for initial board display on move listing
4467        of wild games.
4468        */
4469     switch (ics_getting_history) {
4470       case H_FALSE:
4471       case H_REQUESTED:
4472         break;
4473       case H_GOT_REQ_HEADER:
4474       case H_GOT_UNREQ_HEADER:
4475         /* This is the initial position of the current game */
4476         gamenum = ics_gamenum;
4477         moveNum = 0;            /* old ICS bug workaround */
4478         if (to_play == 'B') {
4479           startedFromSetupPosition = TRUE;
4480           blackPlaysFirst = TRUE;
4481           moveNum = 1;
4482           if (forwardMostMove == 0) forwardMostMove = 1;
4483           if (backwardMostMove == 0) backwardMostMove = 1;
4484           if (currentMove == 0) currentMove = 1;
4485         }
4486         newGameMode = gameMode;
4487         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4488         break;
4489       case H_GOT_UNWANTED_HEADER:
4490         /* This is an initial board that we don't want */
4491         return;
4492       case H_GETTING_MOVES:
4493         /* Should not happen */
4494         DisplayError(_("Error gathering move list: extra board"), 0);
4495         ics_getting_history = H_FALSE;
4496         return;
4497     }
4498
4499    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4500                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4501                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4502      /* [HGM] We seem to have switched variant unexpectedly
4503       * Try to guess new variant from board size
4504       */
4505           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4506           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4507           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4508           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4509           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4510           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4511           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4512           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4513           /* Get a move list just to see the header, which
4514              will tell us whether this is really bug or zh */
4515           if (ics_getting_history == H_FALSE) {
4516             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4517             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4518             SendToICS(str);
4519           }
4520     }
4521
4522     /* Take action if this is the first board of a new game, or of a
4523        different game than is currently being displayed.  */
4524     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4525         relation == RELATION_ISOLATED_BOARD) {
4526
4527         /* Forget the old game and get the history (if any) of the new one */
4528         if (gameMode != BeginningOfGame) {
4529           Reset(TRUE, TRUE);
4530         }
4531         newGame = TRUE;
4532         if (appData.autoRaiseBoard) BoardToTop();
4533         prevMove = -3;
4534         if (gamenum == -1) {
4535             newGameMode = IcsIdle;
4536         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4537                    appData.getMoveList && !reqFlag) {
4538             /* Need to get game history */
4539             ics_getting_history = H_REQUESTED;
4540             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4541             SendToICS(str);
4542         }
4543
4544         /* Initially flip the board to have black on the bottom if playing
4545            black or if the ICS flip flag is set, but let the user change
4546            it with the Flip View button. */
4547         flipView = appData.autoFlipView ?
4548           (newGameMode == IcsPlayingBlack) || ics_flip :
4549           appData.flipView;
4550
4551         /* Done with values from previous mode; copy in new ones */
4552         gameMode = newGameMode;
4553         ModeHighlight();
4554         ics_gamenum = gamenum;
4555         if (gamenum == gs_gamenum) {
4556             int klen = strlen(gs_kind);
4557             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4558             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4559             gameInfo.event = StrSave(str);
4560         } else {
4561             gameInfo.event = StrSave("ICS game");
4562         }
4563         gameInfo.site = StrSave(appData.icsHost);
4564         gameInfo.date = PGNDate();
4565         gameInfo.round = StrSave("-");
4566         gameInfo.white = StrSave(white);
4567         gameInfo.black = StrSave(black);
4568         timeControl = basetime * 60 * 1000;
4569         timeControl_2 = 0;
4570         timeIncrement = increment * 1000;
4571         movesPerSession = 0;
4572         gameInfo.timeControl = TimeControlTagValue();
4573         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4574   if (appData.debugMode) {
4575     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4576     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4577     setbuf(debugFP, NULL);
4578   }
4579
4580         gameInfo.outOfBook = NULL;
4581
4582         /* Do we have the ratings? */
4583         if (strcmp(player1Name, white) == 0 &&
4584             strcmp(player2Name, black) == 0) {
4585             if (appData.debugMode)
4586               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4587                       player1Rating, player2Rating);
4588             gameInfo.whiteRating = player1Rating;
4589             gameInfo.blackRating = player2Rating;
4590         } else if (strcmp(player2Name, white) == 0 &&
4591                    strcmp(player1Name, black) == 0) {
4592             if (appData.debugMode)
4593               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4594                       player2Rating, player1Rating);
4595             gameInfo.whiteRating = player2Rating;
4596             gameInfo.blackRating = player1Rating;
4597         }
4598         player1Name[0] = player2Name[0] = NULLCHAR;
4599
4600         /* Silence shouts if requested */
4601         if (appData.quietPlay &&
4602             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4603             SendToICS(ics_prefix);
4604             SendToICS("set shout 0\n");
4605         }
4606     }
4607
4608     /* Deal with midgame name changes */
4609     if (!newGame) {
4610         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4611             if (gameInfo.white) free(gameInfo.white);
4612             gameInfo.white = StrSave(white);
4613         }
4614         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4615             if (gameInfo.black) free(gameInfo.black);
4616             gameInfo.black = StrSave(black);
4617         }
4618     }
4619
4620     /* Throw away game result if anything actually changes in examine mode */
4621     if (gameMode == IcsExamining && !newGame) {
4622         gameInfo.result = GameUnfinished;
4623         if (gameInfo.resultDetails != NULL) {
4624             free(gameInfo.resultDetails);
4625             gameInfo.resultDetails = NULL;
4626         }
4627     }
4628
4629     /* In pausing && IcsExamining mode, we ignore boards coming
4630        in if they are in a different variation than we are. */
4631     if (pauseExamInvalid) return;
4632     if (pausing && gameMode == IcsExamining) {
4633         if (moveNum <= pauseExamForwardMostMove) {
4634             pauseExamInvalid = TRUE;
4635             forwardMostMove = pauseExamForwardMostMove;
4636             return;
4637         }
4638     }
4639
4640   if (appData.debugMode) {
4641     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4642   }
4643     /* Parse the board */
4644     for (k = 0; k < ranks; k++) {
4645       for (j = 0; j < files; j++)
4646         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4647       if(gameInfo.holdingsWidth > 1) {
4648            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4649            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4650       }
4651     }
4652     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4653       board[5][BOARD_RGHT+1] = WhiteAngel;
4654       board[6][BOARD_RGHT+1] = WhiteMarshall;
4655       board[1][0] = BlackMarshall;
4656       board[2][0] = BlackAngel;
4657       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4658     }
4659     CopyBoard(boards[moveNum], board);
4660     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4661     if (moveNum == 0) {
4662         startedFromSetupPosition =
4663           !CompareBoards(board, initialPosition);
4664         if(startedFromSetupPosition)
4665             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4666     }
4667
4668     /* [HGM] Set castling rights. Take the outermost Rooks,
4669        to make it also work for FRC opening positions. Note that board12
4670        is really defective for later FRC positions, as it has no way to
4671        indicate which Rook can castle if they are on the same side of King.
4672        For the initial position we grant rights to the outermost Rooks,
4673        and remember thos rights, and we then copy them on positions
4674        later in an FRC game. This means WB might not recognize castlings with
4675        Rooks that have moved back to their original position as illegal,
4676        but in ICS mode that is not its job anyway.
4677     */
4678     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4679     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4680
4681         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4685             if(board[0][i] == WhiteRook) j = i;
4686         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4691             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4692         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4693
4694         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4695         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4696         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4697             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4698         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4699             if(board[BOARD_HEIGHT-1][k] == bKing)
4700                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4701         if(gameInfo.variant == VariantTwoKings) {
4702             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4703             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4704             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4705         }
4706     } else { int r;
4707         r = boards[moveNum][CASTLING][0] = initialRights[0];
4708         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4709         r = boards[moveNum][CASTLING][1] = initialRights[1];
4710         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4711         r = boards[moveNum][CASTLING][3] = initialRights[3];
4712         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4713         r = boards[moveNum][CASTLING][4] = initialRights[4];
4714         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4715         /* wildcastle kludge: always assume King has rights */
4716         r = boards[moveNum][CASTLING][2] = initialRights[2];
4717         r = boards[moveNum][CASTLING][5] = initialRights[5];
4718     }
4719     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4720     boards[moveNum][EP_STATUS] = EP_NONE;
4721     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4722     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4723     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4724
4725
4726     if (ics_getting_history == H_GOT_REQ_HEADER ||
4727         ics_getting_history == H_GOT_UNREQ_HEADER) {
4728         /* This was an initial position from a move list, not
4729            the current position */
4730         return;
4731     }
4732
4733     /* Update currentMove and known move number limits */
4734     newMove = newGame || moveNum > forwardMostMove;
4735
4736     if (newGame) {
4737         forwardMostMove = backwardMostMove = currentMove = moveNum;
4738         if (gameMode == IcsExamining && moveNum == 0) {
4739           /* Workaround for ICS limitation: we are not told the wild
4740              type when starting to examine a game.  But if we ask for
4741              the move list, the move list header will tell us */
4742             ics_getting_history = H_REQUESTED;
4743             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4744             SendToICS(str);
4745         }
4746     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4747                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4748 #if ZIPPY
4749         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4750         /* [HGM] applied this also to an engine that is silently watching        */
4751         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4752             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4753             gameInfo.variant == currentlyInitializedVariant) {
4754           takeback = forwardMostMove - moveNum;
4755           for (i = 0; i < takeback; i++) {
4756             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4757             SendToProgram("undo\n", &first);
4758           }
4759         }
4760 #endif
4761
4762         forwardMostMove = moveNum;
4763         if (!pausing || currentMove > forwardMostMove)
4764           currentMove = forwardMostMove;
4765     } else {
4766         /* New part of history that is not contiguous with old part */
4767         if (pausing && gameMode == IcsExamining) {
4768             pauseExamInvalid = TRUE;
4769             forwardMostMove = pauseExamForwardMostMove;
4770             return;
4771         }
4772         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4773 #if ZIPPY
4774             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4775                 // [HGM] when we will receive the move list we now request, it will be
4776                 // fed to the engine from the first move on. So if the engine is not
4777                 // in the initial position now, bring it there.
4778                 InitChessProgram(&first, 0);
4779             }
4780 #endif
4781             ics_getting_history = H_REQUESTED;
4782             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4783             SendToICS(str);
4784         }
4785         forwardMostMove = backwardMostMove = currentMove = moveNum;
4786     }
4787
4788     /* Update the clocks */
4789     if (strchr(elapsed_time, '.')) {
4790       /* Time is in ms */
4791       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4792       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4793     } else {
4794       /* Time is in seconds */
4795       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4796       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4797     }
4798
4799
4800 #if ZIPPY
4801     if (appData.zippyPlay && newGame &&
4802         gameMode != IcsObserving && gameMode != IcsIdle &&
4803         gameMode != IcsExamining)
4804       ZippyFirstBoard(moveNum, basetime, increment);
4805 #endif
4806
4807     /* Put the move on the move list, first converting
4808        to canonical algebraic form. */
4809     if (moveNum > 0) {
4810   if (appData.debugMode) {
4811     int f = forwardMostMove;
4812     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4813             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4814             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4815     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4816     fprintf(debugFP, "moveNum = %d\n", moveNum);
4817     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4818     setbuf(debugFP, NULL);
4819   }
4820         if (moveNum <= backwardMostMove) {
4821             /* We don't know what the board looked like before
4822                this move.  Punt. */
4823           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4824             strcat(parseList[moveNum - 1], " ");
4825             strcat(parseList[moveNum - 1], elapsed_time);
4826             moveList[moveNum - 1][0] = NULLCHAR;
4827         } else if (strcmp(move_str, "none") == 0) {
4828             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4829             /* Again, we don't know what the board looked like;
4830                this is really the start of the game. */
4831             parseList[moveNum - 1][0] = NULLCHAR;
4832             moveList[moveNum - 1][0] = NULLCHAR;
4833             backwardMostMove = moveNum;
4834             startedFromSetupPosition = TRUE;
4835             fromX = fromY = toX = toY = -1;
4836         } else {
4837           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4838           //                 So we parse the long-algebraic move string in stead of the SAN move
4839           int valid; char buf[MSG_SIZ], *prom;
4840
4841           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4842                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4843           // str looks something like "Q/a1-a2"; kill the slash
4844           if(str[1] == '/')
4845             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4846           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4847           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4848                 strcat(buf, prom); // long move lacks promo specification!
4849           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4850                 if(appData.debugMode)
4851                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4852                 safeStrCpy(move_str, buf, MSG_SIZ);
4853           }
4854           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4855                                 &fromX, &fromY, &toX, &toY, &promoChar)
4856                || ParseOneMove(buf, moveNum - 1, &moveType,
4857                                 &fromX, &fromY, &toX, &toY, &promoChar);
4858           // end of long SAN patch
4859           if (valid) {
4860             (void) CoordsToAlgebraic(boards[moveNum - 1],
4861                                      PosFlags(moveNum - 1),
4862                                      fromY, fromX, toY, toX, promoChar,
4863                                      parseList[moveNum-1]);
4864             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4865               case MT_NONE:
4866               case MT_STALEMATE:
4867               default:
4868                 break;
4869               case MT_CHECK:
4870                 if(!IS_SHOGI(gameInfo.variant))
4871                     strcat(parseList[moveNum - 1], "+");
4872                 break;
4873               case MT_CHECKMATE:
4874               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4875                 strcat(parseList[moveNum - 1], "#");
4876                 break;
4877             }
4878             strcat(parseList[moveNum - 1], " ");
4879             strcat(parseList[moveNum - 1], elapsed_time);
4880             /* currentMoveString is set as a side-effect of ParseOneMove */
4881             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4882             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4883             strcat(moveList[moveNum - 1], "\n");
4884
4885             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4886                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4887               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4888                 ChessSquare old, new = boards[moveNum][k][j];
4889                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4890                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4891                   if(old == new) continue;
4892                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4893                   else if(new == WhiteWazir || new == BlackWazir) {
4894                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4895                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4896                       else boards[moveNum][k][j] = old; // preserve type of Gold
4897                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4898                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4899               }
4900           } else {
4901             /* Move from ICS was illegal!?  Punt. */
4902             if (appData.debugMode) {
4903               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4904               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4905             }
4906             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4907             strcat(parseList[moveNum - 1], " ");
4908             strcat(parseList[moveNum - 1], elapsed_time);
4909             moveList[moveNum - 1][0] = NULLCHAR;
4910             fromX = fromY = toX = toY = -1;
4911           }
4912         }
4913   if (appData.debugMode) {
4914     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4915     setbuf(debugFP, NULL);
4916   }
4917
4918 #if ZIPPY
4919         /* Send move to chess program (BEFORE animating it). */
4920         if (appData.zippyPlay && !newGame && newMove &&
4921            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4922
4923             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4924                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4925                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4926                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4927                             move_str);
4928                     DisplayError(str, 0);
4929                 } else {
4930                     if (first.sendTime) {
4931                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4932                     }
4933                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4934                     if (firstMove && !bookHit) {
4935                         firstMove = FALSE;
4936                         if (first.useColors) {
4937                           SendToProgram(gameMode == IcsPlayingWhite ?
4938                                         "white\ngo\n" :
4939                                         "black\ngo\n", &first);
4940                         } else {
4941                           SendToProgram("go\n", &first);
4942                         }
4943                         first.maybeThinking = TRUE;
4944                     }
4945                 }
4946             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4947               if (moveList[moveNum - 1][0] == NULLCHAR) {
4948                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4949                 DisplayError(str, 0);
4950               } else {
4951                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4952                 SendMoveToProgram(moveNum - 1, &first);
4953               }
4954             }
4955         }
4956 #endif
4957     }
4958
4959     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4960         /* If move comes from a remote source, animate it.  If it
4961            isn't remote, it will have already been animated. */
4962         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4963             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4964         }
4965         if (!pausing && appData.highlightLastMove) {
4966             SetHighlights(fromX, fromY, toX, toY);
4967         }
4968     }
4969
4970     /* Start the clocks */
4971     whiteFlag = blackFlag = FALSE;
4972     appData.clockMode = !(basetime == 0 && increment == 0);
4973     if (ticking == 0) {
4974       ics_clock_paused = TRUE;
4975       StopClocks();
4976     } else if (ticking == 1) {
4977       ics_clock_paused = FALSE;
4978     }
4979     if (gameMode == IcsIdle ||
4980         relation == RELATION_OBSERVING_STATIC ||
4981         relation == RELATION_EXAMINING ||
4982         ics_clock_paused)
4983       DisplayBothClocks();
4984     else
4985       StartClocks();
4986
4987     /* Display opponents and material strengths */
4988     if (gameInfo.variant != VariantBughouse &&
4989         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4990         if (tinyLayout || smallLayout) {
4991             if(gameInfo.variant == VariantNormal)
4992               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4994                     basetime, increment);
4995             else
4996               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4997                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4998                     basetime, increment, (int) gameInfo.variant);
4999         } else {
5000             if(gameInfo.variant == VariantNormal)
5001               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5002                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5003                     basetime, increment);
5004             else
5005               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5006                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5007                     basetime, increment, VariantName(gameInfo.variant));
5008         }
5009         DisplayTitle(str);
5010   if (appData.debugMode) {
5011     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5012   }
5013     }
5014
5015
5016     /* Display the board */
5017     if (!pausing && !appData.noGUI) {
5018
5019       if (appData.premove)
5020           if (!gotPremove ||
5021              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5022              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5023               ClearPremoveHighlights();
5024
5025       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5026         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5027       DrawPosition(j, boards[currentMove]);
5028
5029       DisplayMove(moveNum - 1);
5030       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5031             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5032               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5033         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5034       }
5035     }
5036
5037     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5038 #if ZIPPY
5039     if(bookHit) { // [HGM] book: simulate book reply
5040         static char bookMove[MSG_SIZ]; // a bit generous?
5041
5042         programStats.nodes = programStats.depth = programStats.time =
5043         programStats.score = programStats.got_only_move = 0;
5044         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5045
5046         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5047         strcat(bookMove, bookHit);
5048         HandleMachineMove(bookMove, &first);
5049     }
5050 #endif
5051 }
5052
5053 void
5054 GetMoveListEvent ()
5055 {
5056     char buf[MSG_SIZ];
5057     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5058         ics_getting_history = H_REQUESTED;
5059         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5060         SendToICS(buf);
5061     }
5062 }
5063
5064 void
5065 SendToBoth (char *msg)
5066 {   // to make it easy to keep two engines in step in dual analysis
5067     SendToProgram(msg, &first);
5068     if(second.analyzing) SendToProgram(msg, &second);
5069 }
5070
5071 void
5072 AnalysisPeriodicEvent (int force)
5073 {
5074     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5075          && !force) || !appData.periodicUpdates)
5076       return;
5077
5078     /* Send . command to Crafty to collect stats */
5079     SendToBoth(".\n");
5080
5081     /* Don't send another until we get a response (this makes
5082        us stop sending to old Crafty's which don't understand
5083        the "." command (sending illegal cmds resets node count & time,
5084        which looks bad)) */
5085     programStats.ok_to_send = 0;
5086 }
5087
5088 void
5089 ics_update_width (int new_width)
5090 {
5091         ics_printf("set width %d\n", new_width);
5092 }
5093
5094 void
5095 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5096 {
5097     char buf[MSG_SIZ];
5098
5099     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5100         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5101             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5102             SendToProgram(buf, cps);
5103             return;
5104         }
5105         // null move in variant where engine does not understand it (for analysis purposes)
5106         SendBoard(cps, moveNum + 1); // send position after move in stead.
5107         return;
5108     }
5109     if (cps->useUsermove) {
5110       SendToProgram("usermove ", cps);
5111     }
5112     if (cps->useSAN) {
5113       char *space;
5114       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5115         int len = space - parseList[moveNum];
5116         memcpy(buf, parseList[moveNum], len);
5117         buf[len++] = '\n';
5118         buf[len] = NULLCHAR;
5119       } else {
5120         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5121       }
5122       SendToProgram(buf, cps);
5123     } else {
5124       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5125         AlphaRank(moveList[moveNum], 4);
5126         SendToProgram(moveList[moveNum], cps);
5127         AlphaRank(moveList[moveNum], 4); // and back
5128       } else
5129       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5130        * the engine. It would be nice to have a better way to identify castle
5131        * moves here. */
5132       if(appData.fischerCastling && cps->useOOCastle) {
5133         int fromX = moveList[moveNum][0] - AAA;
5134         int fromY = moveList[moveNum][1] - ONE;
5135         int toX = moveList[moveNum][2] - AAA;
5136         int toY = moveList[moveNum][3] - ONE;
5137         if((boards[moveNum][fromY][fromX] == WhiteKing
5138             && boards[moveNum][toY][toX] == WhiteRook)
5139            || (boards[moveNum][fromY][fromX] == BlackKing
5140                && boards[moveNum][toY][toX] == BlackRook)) {
5141           if(toX > fromX) SendToProgram("O-O\n", cps);
5142           else SendToProgram("O-O-O\n", cps);
5143         }
5144         else SendToProgram(moveList[moveNum], cps);
5145       } else
5146       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5147         char *m = moveList[moveNum];
5148         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
5149           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5150                                                m[2], m[3] - '0',
5151                                                m[5], m[6] - '0',
5152                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5153         else
5154           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5155                                                m[5], m[6] - '0',
5156                                                m[5], m[6] - '0',
5157                                                m[2], m[3] - '0');
5158           SendToProgram(buf, cps);
5159       } else
5160       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5161         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5162           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5163           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5164                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         } else
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5167                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5168         SendToProgram(buf, cps);
5169       }
5170       else SendToProgram(moveList[moveNum], cps);
5171       /* End of additions by Tord */
5172     }
5173
5174     /* [HGM] setting up the opening has brought engine in force mode! */
5175     /*       Send 'go' if we are in a mode where machine should play. */
5176     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5177         (gameMode == TwoMachinesPlay   ||
5178 #if ZIPPY
5179          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5180 #endif
5181          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5182         SendToProgram("go\n", cps);
5183   if (appData.debugMode) {
5184     fprintf(debugFP, "(extra)\n");
5185   }
5186     }
5187     setboardSpoiledMachineBlack = 0;
5188 }
5189
5190 void
5191 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5192 {
5193     char user_move[MSG_SIZ];
5194     char suffix[4];
5195
5196     if(gameInfo.variant == VariantSChess && promoChar) {
5197         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5198         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5199     } else suffix[0] = NULLCHAR;
5200
5201     switch (moveType) {
5202       default:
5203         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5204                 (int)moveType, fromX, fromY, toX, toY);
5205         DisplayError(user_move + strlen("say "), 0);
5206         break;
5207       case WhiteKingSideCastle:
5208       case BlackKingSideCastle:
5209       case WhiteQueenSideCastleWild:
5210       case BlackQueenSideCastleWild:
5211       /* PUSH Fabien */
5212       case WhiteHSideCastleFR:
5213       case BlackHSideCastleFR:
5214       /* POP Fabien */
5215         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5216         break;
5217       case WhiteQueenSideCastle:
5218       case BlackQueenSideCastle:
5219       case WhiteKingSideCastleWild:
5220       case BlackKingSideCastleWild:
5221       /* PUSH Fabien */
5222       case WhiteASideCastleFR:
5223       case BlackASideCastleFR:
5224       /* POP Fabien */
5225         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5226         break;
5227       case WhiteNonPromotion:
5228       case BlackNonPromotion:
5229         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5230         break;
5231       case WhitePromotion:
5232       case BlackPromotion:
5233         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5234            gameInfo.variant == VariantMakruk)
5235           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5236                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5237                 PieceToChar(WhiteFerz));
5238         else if(gameInfo.variant == VariantGreat)
5239           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5240                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5241                 PieceToChar(WhiteMan));
5242         else
5243           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5244                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5245                 promoChar);
5246         break;
5247       case WhiteDrop:
5248       case BlackDrop:
5249       drop:
5250         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5251                  ToUpper(PieceToChar((ChessSquare) fromX)),
5252                  AAA + toX, ONE + toY);
5253         break;
5254       case IllegalMove:  /* could be a variant we don't quite understand */
5255         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5256       case NormalMove:
5257       case WhiteCapturesEnPassant:
5258       case BlackCapturesEnPassant:
5259         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5260                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5261         break;
5262     }
5263     SendToICS(user_move);
5264     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5265         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5266 }
5267
5268 void
5269 UploadGameEvent ()
5270 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5271     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5272     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5273     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5274       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5275       return;
5276     }
5277     if(gameMode != IcsExamining) { // is this ever not the case?
5278         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5279
5280         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5281           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5282         } else { // on FICS we must first go to general examine mode
5283           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5284         }
5285         if(gameInfo.variant != VariantNormal) {
5286             // try figure out wild number, as xboard names are not always valid on ICS
5287             for(i=1; i<=36; i++) {
5288               snprintf(buf, MSG_SIZ, "wild/%d", i);
5289                 if(StringToVariant(buf) == gameInfo.variant) break;
5290             }
5291             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5292             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5293             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5294         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5295         SendToICS(ics_prefix);
5296         SendToICS(buf);
5297         if(startedFromSetupPosition || backwardMostMove != 0) {
5298           fen = PositionToFEN(backwardMostMove, NULL, 1);
5299           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5300             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5301             SendToICS(buf);
5302           } else { // FICS: everything has to set by separate bsetup commands
5303             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5304             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5305             SendToICS(buf);
5306             if(!WhiteOnMove(backwardMostMove)) {
5307                 SendToICS("bsetup tomove black\n");
5308             }
5309             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5310             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5313             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5314             SendToICS(buf);
5315             i = boards[backwardMostMove][EP_STATUS];
5316             if(i >= 0) { // set e.p.
5317               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5318                 SendToICS(buf);
5319             }
5320             bsetup++;
5321           }
5322         }
5323       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5324             SendToICS("bsetup done\n"); // switch to normal examining.
5325     }
5326     for(i = backwardMostMove; i<last; i++) {
5327         char buf[20];
5328         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5329         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5330             int len = strlen(moveList[i]);
5331             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5332             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5333         }
5334         SendToICS(buf);
5335     }
5336     SendToICS(ics_prefix);
5337     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5338 }
5339
5340 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5341 int legNr = 1;
5342
5343 void
5344 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5345 {
5346     if (rf == DROP_RANK) {
5347       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5348       sprintf(move, "%c@%c%c\n",
5349                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5350     } else {
5351         if (promoChar == 'x' || promoChar == NULLCHAR) {
5352           sprintf(move, "%c%c%c%c\n",
5353                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5354           if(killX >= 0 && killY >= 0) {
5355             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5356             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5357           }
5358         } else {
5359             sprintf(move, "%c%c%c%c%c\n",
5360                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5361         }
5362     }
5363 }
5364
5365 void
5366 ProcessICSInitScript (FILE *f)
5367 {
5368     char buf[MSG_SIZ];
5369
5370     while (fgets(buf, MSG_SIZ, f)) {
5371         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5372     }
5373
5374     fclose(f);
5375 }
5376
5377
5378 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5379 int dragging;
5380 static ClickType lastClickType;
5381
5382 int
5383 PieceInString (char *s, ChessSquare piece)
5384 {
5385   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5386   while((p = strchr(s, ID))) {
5387     if(!suffix || p[1] == suffix) return TRUE;
5388     s = p;
5389   }
5390   return FALSE;
5391 }
5392
5393 int
5394 Partner (ChessSquare *p)
5395 { // change piece into promotion partner if one shogi-promotes to the other
5396   ChessSquare partner = promoPartner[*p];
5397   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5398   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
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 == '*') {
6063                     table[i] = partner[i];
6064                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6065                 }
6066             }
6067         }
6068
6069         result = TRUE;
6070     }
6071
6072     return result;
6073 }
6074
6075 int
6076 SetCharTable (unsigned char *table, const char * map)
6077 {
6078     return SetCharTableEsc(table, map, "");
6079 }
6080
6081 void
6082 Prelude (Board board)
6083 {       // [HGM] superchess: random selection of exo-pieces
6084         int i, j, k; ChessSquare p;
6085         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6086
6087         GetPositionNumber(); // use FRC position number
6088
6089         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6090             SetCharTable(pieceToChar, appData.pieceToCharTable);
6091             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6092                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6093         }
6094
6095         j = seed%4;                 seed /= 4;
6096         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6097         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6098         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6099         j = seed%3 + (seed%3 >= j); seed /= 3;
6100         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6101         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6102         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6103         j = seed%3;                 seed /= 3;
6104         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6105         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6106         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6107         j = seed%2 + (seed%2 >= j); seed /= 2;
6108         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6109         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6110         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6111         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6112         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6113         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6114         put(board, exoPieces[0],    0, 0, ANY);
6115         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6116 }
6117
6118 void
6119 InitPosition (int redraw)
6120 {
6121     ChessSquare (* pieces)[BOARD_FILES];
6122     int i, j, pawnRow=1, pieceRows=1, overrule,
6123     oldx = gameInfo.boardWidth,
6124     oldy = gameInfo.boardHeight,
6125     oldh = gameInfo.holdingsWidth;
6126     static int oldv;
6127
6128     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6129
6130     /* [AS] Initialize pv info list [HGM] and game status */
6131     {
6132         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6133             pvInfoList[i].depth = 0;
6134             boards[i][EP_STATUS] = EP_NONE;
6135             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6136         }
6137
6138         initialRulePlies = 0; /* 50-move counter start */
6139
6140         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6141         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6142     }
6143
6144
6145     /* [HGM] logic here is completely changed. In stead of full positions */
6146     /* the initialized data only consist of the two backranks. The switch */
6147     /* selects which one we will use, which is than copied to the Board   */
6148     /* initialPosition, which for the rest is initialized by Pawns and    */
6149     /* empty squares. This initial position is then copied to boards[0],  */
6150     /* possibly after shuffling, so that it remains available.            */
6151
6152     gameInfo.holdingsWidth = 0; /* default board sizes */
6153     gameInfo.boardWidth    = 8;
6154     gameInfo.boardHeight   = 8;
6155     gameInfo.holdingsSize  = 0;
6156     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6157     for(i=0; i<BOARD_FILES-6; i++)
6158       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6159     initialPosition[EP_STATUS] = EP_NONE;
6160     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6161     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6162     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6163          SetCharTable(pieceNickName, appData.pieceNickNames);
6164     else SetCharTable(pieceNickName, "............");
6165     pieces = FIDEArray;
6166
6167     switch (gameInfo.variant) {
6168     case VariantFischeRandom:
6169       shuffleOpenings = TRUE;
6170       appData.fischerCastling = TRUE;
6171     default:
6172       break;
6173     case VariantShatranj:
6174       pieces = ShatranjArray;
6175       nrCastlingRights = 0;
6176       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6177       break;
6178     case VariantMakruk:
6179       pieces = makrukArray;
6180       nrCastlingRights = 0;
6181       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6182       break;
6183     case VariantASEAN:
6184       pieces = aseanArray;
6185       nrCastlingRights = 0;
6186       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6187       break;
6188     case VariantTwoKings:
6189       pieces = twoKingsArray;
6190       break;
6191     case VariantGrand:
6192       pieces = GrandArray;
6193       nrCastlingRights = 0;
6194       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6195       gameInfo.boardWidth = 10;
6196       gameInfo.boardHeight = 10;
6197       gameInfo.holdingsSize = 7;
6198       break;
6199     case VariantCapaRandom:
6200       shuffleOpenings = TRUE;
6201       appData.fischerCastling = TRUE;
6202     case VariantCapablanca:
6203       pieces = CapablancaArray;
6204       gameInfo.boardWidth = 10;
6205       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6206       break;
6207     case VariantGothic:
6208       pieces = GothicArray;
6209       gameInfo.boardWidth = 10;
6210       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6211       break;
6212     case VariantSChess:
6213       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6214       gameInfo.holdingsSize = 7;
6215       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6216       break;
6217     case VariantJanus:
6218       pieces = JanusArray;
6219       gameInfo.boardWidth = 10;
6220       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6221       nrCastlingRights = 6;
6222         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6223         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6224         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6225         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6226         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6227         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6228       break;
6229     case VariantFalcon:
6230       pieces = FalconArray;
6231       gameInfo.boardWidth = 10;
6232       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6233       break;
6234     case VariantXiangqi:
6235       pieces = XiangqiArray;
6236       gameInfo.boardWidth  = 9;
6237       gameInfo.boardHeight = 10;
6238       nrCastlingRights = 0;
6239       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6240       break;
6241     case VariantShogi:
6242       pieces = ShogiArray;
6243       gameInfo.boardWidth  = 9;
6244       gameInfo.boardHeight = 9;
6245       gameInfo.holdingsSize = 7;
6246       nrCastlingRights = 0;
6247       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6248       break;
6249     case VariantChu:
6250       pieces = ChuArray; pieceRows = 3;
6251       gameInfo.boardWidth  = 12;
6252       gameInfo.boardHeight = 12;
6253       nrCastlingRights = 0;
6254       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/^P.^B^R.^S^E^X^O^G^C^A^T^H^D.^V^M^L^I^FK"
6255                                    "p.brqsexogcathd.vmlifn/^p.^b^r.^s^e^x^o^g^c^a^t^h^d.^v^m^l^i^fk", SUFFIXES);
6256       break;
6257     case VariantCourier:
6258       pieces = CourierArray;
6259       gameInfo.boardWidth  = 12;
6260       nrCastlingRights = 0;
6261       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6262       break;
6263     case VariantKnightmate:
6264       pieces = KnightmateArray;
6265       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6266       break;
6267     case VariantSpartan:
6268       pieces = SpartanArray;
6269       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6270       break;
6271     case VariantLion:
6272       pieces = lionArray;
6273       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6274       break;
6275     case VariantChuChess:
6276       pieces = ChuChessArray;
6277       gameInfo.boardWidth = 10;
6278       gameInfo.boardHeight = 10;
6279       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6280       break;
6281     case VariantFairy:
6282       pieces = fairyArray;
6283       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6284       break;
6285     case VariantGreat:
6286       pieces = GreatArray;
6287       gameInfo.boardWidth = 10;
6288       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6289       gameInfo.holdingsSize = 8;
6290       break;
6291     case VariantSuper:
6292       pieces = FIDEArray;
6293       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6294       gameInfo.holdingsSize = 8;
6295       startedFromSetupPosition = TRUE;
6296       break;
6297     case VariantCrazyhouse:
6298     case VariantBughouse:
6299       pieces = FIDEArray;
6300       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6301       gameInfo.holdingsSize = 5;
6302       break;
6303     case VariantWildCastle:
6304       pieces = FIDEArray;
6305       /* !!?shuffle with kings guaranteed to be on d or e file */
6306       shuffleOpenings = 1;
6307       break;
6308     case VariantNoCastle:
6309       pieces = FIDEArray;
6310       nrCastlingRights = 0;
6311       /* !!?unconstrained back-rank shuffle */
6312       shuffleOpenings = 1;
6313       break;
6314     }
6315
6316     overrule = 0;
6317     if(appData.NrFiles >= 0) {
6318         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6319         gameInfo.boardWidth = appData.NrFiles;
6320     }
6321     if(appData.NrRanks >= 0) {
6322         gameInfo.boardHeight = appData.NrRanks;
6323     }
6324     if(appData.holdingsSize >= 0) {
6325         i = appData.holdingsSize;
6326         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6327         gameInfo.holdingsSize = i;
6328     }
6329     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6330     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6331         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6332
6333     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6334     if(pawnRow < 1) pawnRow = 1;
6335     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6336        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6337     if(gameInfo.variant == VariantChu) pawnRow = 3;
6338
6339     /* User pieceToChar list overrules defaults */
6340     if(appData.pieceToCharTable != NULL)
6341         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6342
6343     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6344
6345         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6346             s = (ChessSquare) 0; /* account holding counts in guard band */
6347         for( i=0; i<BOARD_HEIGHT; i++ )
6348             initialPosition[i][j] = s;
6349
6350         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6351         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6352         initialPosition[pawnRow][j] = WhitePawn;
6353         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6354         if(gameInfo.variant == VariantXiangqi) {
6355             if(j&1) {
6356                 initialPosition[pawnRow][j] =
6357                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6358                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6359                    initialPosition[2][j] = WhiteCannon;
6360                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6361                 }
6362             }
6363         }
6364         if(gameInfo.variant == VariantChu) {
6365              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6366                initialPosition[pawnRow+1][j] = WhiteCobra,
6367                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6368              for(i=1; i<pieceRows; i++) {
6369                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6370                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6371              }
6372         }
6373         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6374             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6375                initialPosition[0][j] = WhiteRook;
6376                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6377             }
6378         }
6379         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6380     }
6381     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6382     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6383
6384             j=BOARD_LEFT+1;
6385             initialPosition[1][j] = WhiteBishop;
6386             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6387             j=BOARD_RGHT-2;
6388             initialPosition[1][j] = WhiteRook;
6389             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6390     }
6391
6392     if( nrCastlingRights == -1) {
6393         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6394         /*       This sets default castling rights from none to normal corners   */
6395         /* Variants with other castling rights must set them themselves above    */
6396         nrCastlingRights = 6;
6397
6398         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6399         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6400         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6401         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6402         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6403         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6404      }
6405
6406      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6407      if(gameInfo.variant == VariantGreat) { // promotion commoners
6408         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6409         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6410         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6411         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6412      }
6413      if( gameInfo.variant == VariantSChess ) {
6414       initialPosition[1][0] = BlackMarshall;
6415       initialPosition[2][0] = BlackAngel;
6416       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6417       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6418       initialPosition[1][1] = initialPosition[2][1] =
6419       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6420      }
6421   if (appData.debugMode) {
6422     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6423   }
6424     if(shuffleOpenings) {
6425         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6426         startedFromSetupPosition = TRUE;
6427     }
6428     if(startedFromPositionFile) {
6429       /* [HGM] loadPos: use PositionFile for every new game */
6430       CopyBoard(initialPosition, filePosition);
6431       for(i=0; i<nrCastlingRights; i++)
6432           initialRights[i] = filePosition[CASTLING][i];
6433       startedFromSetupPosition = TRUE;
6434     }
6435
6436     CopyBoard(boards[0], initialPosition);
6437
6438     if(oldx != gameInfo.boardWidth ||
6439        oldy != gameInfo.boardHeight ||
6440        oldv != gameInfo.variant ||
6441        oldh != gameInfo.holdingsWidth
6442                                          )
6443             InitDrawingSizes(-2 ,0);
6444
6445     oldv = gameInfo.variant;
6446     if (redraw)
6447       DrawPosition(TRUE, boards[currentMove]);
6448 }
6449
6450 void
6451 SendBoard (ChessProgramState *cps, int moveNum)
6452 {
6453     char message[MSG_SIZ];
6454
6455     if (cps->useSetboard) {
6456       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6457       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6458       SendToProgram(message, cps);
6459       free(fen);
6460
6461     } else {
6462       ChessSquare *bp;
6463       int i, j, left=0, right=BOARD_WIDTH;
6464       /* Kludge to set black to move, avoiding the troublesome and now
6465        * deprecated "black" command.
6466        */
6467       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6468         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6469
6470       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6471
6472       SendToProgram("edit\n", cps);
6473       SendToProgram("#\n", cps);
6474       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6475         bp = &boards[moveNum][i][left];
6476         for (j = left; j < right; j++, bp++) {
6477           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6478           if ((int) *bp < (int) BlackPawn) {
6479             if(j == BOARD_RGHT+1)
6480                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6481             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6482             if(message[0] == '+' || message[0] == '~') {
6483               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6484                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6485                         AAA + j, ONE + i - '0');
6486             }
6487             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6488                 message[1] = BOARD_RGHT   - 1 - j + '1';
6489                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6490             }
6491             SendToProgram(message, cps);
6492           }
6493         }
6494       }
6495
6496       SendToProgram("c\n", cps);
6497       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6498         bp = &boards[moveNum][i][left];
6499         for (j = left; j < right; j++, bp++) {
6500           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6501           if (((int) *bp != (int) EmptySquare)
6502               && ((int) *bp >= (int) BlackPawn)) {
6503             if(j == BOARD_LEFT-2)
6504                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6505             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6506                     AAA + j, ONE + i - '0');
6507             if(message[0] == '+' || message[0] == '~') {
6508               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6509                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6510                         AAA + j, ONE + i - '0');
6511             }
6512             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6513                 message[1] = BOARD_RGHT   - 1 - j + '1';
6514                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6515             }
6516             SendToProgram(message, cps);
6517           }
6518         }
6519       }
6520
6521       SendToProgram(".\n", cps);
6522     }
6523     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6524 }
6525
6526 char exclusionHeader[MSG_SIZ];
6527 int exCnt, excludePtr;
6528 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6529 static Exclusion excluTab[200];
6530 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6531
6532 static void
6533 WriteMap (int s)
6534 {
6535     int j;
6536     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6537     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6538 }
6539
6540 static void
6541 ClearMap ()
6542 {
6543     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6544     excludePtr = 24; exCnt = 0;
6545     WriteMap(0);
6546 }
6547
6548 static void
6549 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6550 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6551     char buf[2*MOVE_LEN], *p;
6552     Exclusion *e = excluTab;
6553     int i;
6554     for(i=0; i<exCnt; i++)
6555         if(e[i].ff == fromX && e[i].fr == fromY &&
6556            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6557     if(i == exCnt) { // was not in exclude list; add it
6558         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6559         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6560             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6561             return; // abort
6562         }
6563         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6564         excludePtr++; e[i].mark = excludePtr++;
6565         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6566         exCnt++;
6567     }
6568     exclusionHeader[e[i].mark] = state;
6569 }
6570
6571 static int
6572 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6573 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6574     char buf[MSG_SIZ];
6575     int j, k;
6576     ChessMove moveType;
6577     if((signed char)promoChar == -1) { // kludge to indicate best move
6578         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6579             return 1; // if unparsable, abort
6580     }
6581     // update exclusion map (resolving toggle by consulting existing state)
6582     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6583     j = k%8; k >>= 3;
6584     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6585     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6586          excludeMap[k] |=   1<<j;
6587     else excludeMap[k] &= ~(1<<j);
6588     // update header
6589     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6590     // inform engine
6591     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6592     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6593     SendToBoth(buf);
6594     return (state == '+');
6595 }
6596
6597 static void
6598 ExcludeClick (int index)
6599 {
6600     int i, j;
6601     Exclusion *e = excluTab;
6602     if(index < 25) { // none, best or tail clicked
6603         if(index < 13) { // none: include all
6604             WriteMap(0); // clear map
6605             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6606             SendToBoth("include all\n"); // and inform engine
6607         } else if(index > 18) { // tail
6608             if(exclusionHeader[19] == '-') { // tail was excluded
6609                 SendToBoth("include all\n");
6610                 WriteMap(0); // clear map completely
6611                 // now re-exclude selected moves
6612                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6613                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6614             } else { // tail was included or in mixed state
6615                 SendToBoth("exclude all\n");
6616                 WriteMap(0xFF); // fill map completely
6617                 // now re-include selected moves
6618                 j = 0; // count them
6619                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6620                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6621                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6622             }
6623         } else { // best
6624             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6625         }
6626     } else {
6627         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6628             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6629             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6630             break;
6631         }
6632     }
6633 }
6634
6635 ChessSquare
6636 DefaultPromoChoice (int white)
6637 {
6638     ChessSquare result;
6639     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6640        gameInfo.variant == VariantMakruk)
6641         result = WhiteFerz; // no choice
6642     else if(gameInfo.variant == VariantASEAN)
6643         result = WhiteRook; // no choice
6644     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6645         result= WhiteKing; // in Suicide Q is the last thing we want
6646     else if(gameInfo.variant == VariantSpartan)
6647         result = white ? WhiteQueen : WhiteAngel;
6648     else result = WhiteQueen;
6649     if(!white) result = WHITE_TO_BLACK result;
6650     return result;
6651 }
6652
6653 static int autoQueen; // [HGM] oneclick
6654
6655 int
6656 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6657 {
6658     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6659     /* [HGM] add Shogi promotions */
6660     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6661     ChessSquare piece, partner;
6662     ChessMove moveType;
6663     Boolean premove;
6664
6665     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6666     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6667
6668     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6669       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6670         return FALSE;
6671
6672     piece = boards[currentMove][fromY][fromX];
6673     if(gameInfo.variant == VariantChu) {
6674         promotionZoneSize = BOARD_HEIGHT/3;
6675         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6676     } else if(gameInfo.variant == VariantShogi) {
6677         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6678         highestPromotingPiece = (int)WhiteAlfil;
6679     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6680         promotionZoneSize = 3;
6681     }
6682
6683     // Treat Lance as Pawn when it is not representing Amazon or Lance
6684     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6685         if(piece == WhiteLance) piece = WhitePawn; else
6686         if(piece == BlackLance) piece = BlackPawn;
6687     }
6688
6689     // next weed out all moves that do not touch the promotion zone at all
6690     if((int)piece >= BlackPawn) {
6691         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6692              return FALSE;
6693         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6694         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6695     } else {
6696         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6697            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6698         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6699              return FALSE;
6700     }
6701
6702     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6703
6704     // weed out mandatory Shogi promotions
6705     if(gameInfo.variant == VariantShogi) {
6706         if(piece >= BlackPawn) {
6707             if(toY == 0 && piece == BlackPawn ||
6708                toY == 0 && piece == BlackQueen ||
6709                toY <= 1 && piece == BlackKnight) {
6710                 *promoChoice = '+';
6711                 return FALSE;
6712             }
6713         } else {
6714             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6715                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6716                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6717                 *promoChoice = '+';
6718                 return FALSE;
6719             }
6720         }
6721     }
6722
6723     // weed out obviously illegal Pawn moves
6724     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6725         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6726         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6727         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6728         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6729         // note we are not allowed to test for valid (non-)capture, due to premove
6730     }
6731
6732     // we either have a choice what to promote to, or (in Shogi) whether to promote
6733     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6734        gameInfo.variant == VariantMakruk) {
6735         ChessSquare p=BlackFerz;  // no choice
6736         while(p < EmptySquare) {  //but make sure we use piece that exists
6737             *promoChoice = PieceToChar(p++);
6738             if(*promoChoice != '.') break;
6739         }
6740         return FALSE;
6741     }
6742     // no sense asking what we must promote to if it is going to explode...
6743     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6744         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6745         return FALSE;
6746     }
6747     // give caller the default choice even if we will not make it
6748     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6749     partner = piece; // pieces can promote if the pieceToCharTable says so
6750     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6751     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6752     if(        sweepSelect && gameInfo.variant != VariantGreat
6753                            && gameInfo.variant != VariantGrand
6754                            && gameInfo.variant != VariantSuper) return FALSE;
6755     if(autoQueen) return FALSE; // predetermined
6756
6757     // suppress promotion popup on illegal moves that are not premoves
6758     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6759               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6760     if(appData.testLegality && !premove) {
6761         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6762                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6763         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6764         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6765             return FALSE;
6766     }
6767
6768     return TRUE;
6769 }
6770
6771 int
6772 InPalace (int row, int column)
6773 {   /* [HGM] for Xiangqi */
6774     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6775          column < (BOARD_WIDTH + 4)/2 &&
6776          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6777     return FALSE;
6778 }
6779
6780 int
6781 PieceForSquare (int x, int y)
6782 {
6783   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6784      return -1;
6785   else
6786      return boards[currentMove][y][x];
6787 }
6788
6789 int
6790 OKToStartUserMove (int x, int y)
6791 {
6792     ChessSquare from_piece;
6793     int white_piece;
6794
6795     if (matchMode) return FALSE;
6796     if (gameMode == EditPosition) return TRUE;
6797
6798     if (x >= 0 && y >= 0)
6799       from_piece = boards[currentMove][y][x];
6800     else
6801       from_piece = EmptySquare;
6802
6803     if (from_piece == EmptySquare) return FALSE;
6804
6805     white_piece = (int)from_piece >= (int)WhitePawn &&
6806       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6807
6808     switch (gameMode) {
6809       case AnalyzeFile:
6810       case TwoMachinesPlay:
6811       case EndOfGame:
6812         return FALSE;
6813
6814       case IcsObserving:
6815       case IcsIdle:
6816         return FALSE;
6817
6818       case MachinePlaysWhite:
6819       case IcsPlayingBlack:
6820         if (appData.zippyPlay) return FALSE;
6821         if (white_piece) {
6822             DisplayMoveError(_("You are playing Black"));
6823             return FALSE;
6824         }
6825         break;
6826
6827       case MachinePlaysBlack:
6828       case IcsPlayingWhite:
6829         if (appData.zippyPlay) return FALSE;
6830         if (!white_piece) {
6831             DisplayMoveError(_("You are playing White"));
6832             return FALSE;
6833         }
6834         break;
6835
6836       case PlayFromGameFile:
6837             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6838       case EditGame:
6839         if (!white_piece && WhiteOnMove(currentMove)) {
6840             DisplayMoveError(_("It is White's turn"));
6841             return FALSE;
6842         }
6843         if (white_piece && !WhiteOnMove(currentMove)) {
6844             DisplayMoveError(_("It is Black's turn"));
6845             return FALSE;
6846         }
6847         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6848             /* Editing correspondence game history */
6849             /* Could disallow this or prompt for confirmation */
6850             cmailOldMove = -1;
6851         }
6852         break;
6853
6854       case BeginningOfGame:
6855         if (appData.icsActive) return FALSE;
6856         if (!appData.noChessProgram) {
6857             if (!white_piece) {
6858                 DisplayMoveError(_("You are playing White"));
6859                 return FALSE;
6860             }
6861         }
6862         break;
6863
6864       case Training:
6865         if (!white_piece && WhiteOnMove(currentMove)) {
6866             DisplayMoveError(_("It is White's turn"));
6867             return FALSE;
6868         }
6869         if (white_piece && !WhiteOnMove(currentMove)) {
6870             DisplayMoveError(_("It is Black's turn"));
6871             return FALSE;
6872         }
6873         break;
6874
6875       default:
6876       case IcsExamining:
6877         break;
6878     }
6879     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6880         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6881         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6882         && gameMode != AnalyzeFile && gameMode != Training) {
6883         DisplayMoveError(_("Displayed position is not current"));
6884         return FALSE;
6885     }
6886     return TRUE;
6887 }
6888
6889 Boolean
6890 OnlyMove (int *x, int *y, Boolean captures)
6891 {
6892     DisambiguateClosure cl;
6893     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6894     switch(gameMode) {
6895       case MachinePlaysBlack:
6896       case IcsPlayingWhite:
6897       case BeginningOfGame:
6898         if(!WhiteOnMove(currentMove)) return FALSE;
6899         break;
6900       case MachinePlaysWhite:
6901       case IcsPlayingBlack:
6902         if(WhiteOnMove(currentMove)) return FALSE;
6903         break;
6904       case EditGame:
6905         break;
6906       default:
6907         return FALSE;
6908     }
6909     cl.pieceIn = EmptySquare;
6910     cl.rfIn = *y;
6911     cl.ffIn = *x;
6912     cl.rtIn = -1;
6913     cl.ftIn = -1;
6914     cl.promoCharIn = NULLCHAR;
6915     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6916     if( cl.kind == NormalMove ||
6917         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6918         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6919         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6920       fromX = cl.ff;
6921       fromY = cl.rf;
6922       *x = cl.ft;
6923       *y = cl.rt;
6924       return TRUE;
6925     }
6926     if(cl.kind != ImpossibleMove) return FALSE;
6927     cl.pieceIn = EmptySquare;
6928     cl.rfIn = -1;
6929     cl.ffIn = -1;
6930     cl.rtIn = *y;
6931     cl.ftIn = *x;
6932     cl.promoCharIn = NULLCHAR;
6933     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6934     if( cl.kind == NormalMove ||
6935         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6936         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6937         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6938       fromX = cl.ff;
6939       fromY = cl.rf;
6940       *x = cl.ft;
6941       *y = cl.rt;
6942       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6943       return TRUE;
6944     }
6945     return FALSE;
6946 }
6947
6948 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6949 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6950 int lastLoadGameUseList = FALSE;
6951 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6952 ChessMove lastLoadGameStart = EndOfFile;
6953 int doubleClick;
6954 Boolean addToBookFlag;
6955
6956 void
6957 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6958 {
6959     ChessMove moveType;
6960     ChessSquare pup;
6961     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6962
6963     /* Check if the user is playing in turn.  This is complicated because we
6964        let the user "pick up" a piece before it is his turn.  So the piece he
6965        tried to pick up may have been captured by the time he puts it down!
6966        Therefore we use the color the user is supposed to be playing in this
6967        test, not the color of the piece that is currently on the starting
6968        square---except in EditGame mode, where the user is playing both
6969        sides; fortunately there the capture race can't happen.  (It can
6970        now happen in IcsExamining mode, but that's just too bad.  The user
6971        will get a somewhat confusing message in that case.)
6972        */
6973
6974     switch (gameMode) {
6975       case AnalyzeFile:
6976       case TwoMachinesPlay:
6977       case EndOfGame:
6978       case IcsObserving:
6979       case IcsIdle:
6980         /* We switched into a game mode where moves are not accepted,
6981            perhaps while the mouse button was down. */
6982         return;
6983
6984       case MachinePlaysWhite:
6985         /* User is moving for Black */
6986         if (WhiteOnMove(currentMove)) {
6987             DisplayMoveError(_("It is White's turn"));
6988             return;
6989         }
6990         break;
6991
6992       case MachinePlaysBlack:
6993         /* User is moving for White */
6994         if (!WhiteOnMove(currentMove)) {
6995             DisplayMoveError(_("It is Black's turn"));
6996             return;
6997         }
6998         break;
6999
7000       case PlayFromGameFile:
7001             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7002       case EditGame:
7003       case IcsExamining:
7004       case BeginningOfGame:
7005       case AnalyzeMode:
7006       case Training:
7007         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7008         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7009             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7010             /* User is moving for Black */
7011             if (WhiteOnMove(currentMove)) {
7012                 DisplayMoveError(_("It is White's turn"));
7013                 return;
7014             }
7015         } else {
7016             /* User is moving for White */
7017             if (!WhiteOnMove(currentMove)) {
7018                 DisplayMoveError(_("It is Black's turn"));
7019                 return;
7020             }
7021         }
7022         break;
7023
7024       case IcsPlayingBlack:
7025         /* User is moving for Black */
7026         if (WhiteOnMove(currentMove)) {
7027             if (!appData.premove) {
7028                 DisplayMoveError(_("It is White's turn"));
7029             } else if (toX >= 0 && toY >= 0) {
7030                 premoveToX = toX;
7031                 premoveToY = toY;
7032                 premoveFromX = fromX;
7033                 premoveFromY = fromY;
7034                 premovePromoChar = promoChar;
7035                 gotPremove = 1;
7036                 if (appData.debugMode)
7037                     fprintf(debugFP, "Got premove: fromX %d,"
7038                             "fromY %d, toX %d, toY %d\n",
7039                             fromX, fromY, toX, toY);
7040             }
7041             return;
7042         }
7043         break;
7044
7045       case IcsPlayingWhite:
7046         /* User is moving for White */
7047         if (!WhiteOnMove(currentMove)) {
7048             if (!appData.premove) {
7049                 DisplayMoveError(_("It is Black's turn"));
7050             } else if (toX >= 0 && toY >= 0) {
7051                 premoveToX = toX;
7052                 premoveToY = toY;
7053                 premoveFromX = fromX;
7054                 premoveFromY = fromY;
7055                 premovePromoChar = promoChar;
7056                 gotPremove = 1;
7057                 if (appData.debugMode)
7058                     fprintf(debugFP, "Got premove: fromX %d,"
7059                             "fromY %d, toX %d, toY %d\n",
7060                             fromX, fromY, toX, toY);
7061             }
7062             return;
7063         }
7064         break;
7065
7066       default:
7067         break;
7068
7069       case EditPosition:
7070         /* EditPosition, empty square, or different color piece;
7071            click-click move is possible */
7072         if (toX == -2 || toY == -2) {
7073             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7074             DrawPosition(FALSE, boards[currentMove]);
7075             return;
7076         } else if (toX >= 0 && toY >= 0) {
7077             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7078                 ChessSquare q, p = boards[0][rf][ff];
7079                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7080                 if(CHUPROMOTED(p) < BlackPawn) p = q = CHUPROMOTED(boards[0][rf][ff]);
7081                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7082                 if(PieceToChar(q) == '+') gatingPiece = p;
7083             }
7084             boards[0][toY][toX] = boards[0][fromY][fromX];
7085             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7086                 if(boards[0][fromY][0] != EmptySquare) {
7087                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7088                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7089                 }
7090             } else
7091             if(fromX == BOARD_RGHT+1) {
7092                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7093                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7094                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7095                 }
7096             } else
7097             boards[0][fromY][fromX] = gatingPiece;
7098             DrawPosition(FALSE, boards[currentMove]);
7099             return;
7100         }
7101         return;
7102     }
7103
7104     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7105     pup = boards[currentMove][toY][toX];
7106
7107     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7108     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7109          if( pup != EmptySquare ) return;
7110          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7111            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7112                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7113            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7114            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7115            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7116            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7117          fromY = DROP_RANK;
7118     }
7119
7120     /* [HGM] always test for legality, to get promotion info */
7121     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7122                                          fromY, fromX, toY, toX, promoChar);
7123
7124     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7125
7126     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7127
7128     /* [HGM] but possibly ignore an IllegalMove result */
7129     if (appData.testLegality) {
7130         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7131             DisplayMoveError(_("Illegal move"));
7132             return;
7133         }
7134     }
7135
7136     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7137         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7138              ClearPremoveHighlights(); // was included
7139         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7140         return;
7141     }
7142
7143     if(addToBookFlag) { // adding moves to book
7144         char buf[MSG_SIZ], move[MSG_SIZ];
7145         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7146         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');
7147         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7148         AddBookMove(buf);
7149         addToBookFlag = FALSE;
7150         ClearHighlights();
7151         return;
7152     }
7153
7154     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7155 }
7156
7157 /* Common tail of UserMoveEvent and DropMenuEvent */
7158 int
7159 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7160 {
7161     char *bookHit = 0;
7162
7163     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7164         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7165         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7166         if(WhiteOnMove(currentMove)) {
7167             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7168         } else {
7169             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7170         }
7171     }
7172
7173     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7174        move type in caller when we know the move is a legal promotion */
7175     if(moveType == NormalMove && promoChar)
7176         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7177
7178     /* [HGM] <popupFix> The following if has been moved here from
7179        UserMoveEvent(). Because it seemed to belong here (why not allow
7180        piece drops in training games?), and because it can only be
7181        performed after it is known to what we promote. */
7182     if (gameMode == Training) {
7183       /* compare the move played on the board to the next move in the
7184        * game. If they match, display the move and the opponent's response.
7185        * If they don't match, display an error message.
7186        */
7187       int saveAnimate;
7188       Board testBoard;
7189       CopyBoard(testBoard, boards[currentMove]);
7190       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7191
7192       if (CompareBoards(testBoard, boards[currentMove+1])) {
7193         ForwardInner(currentMove+1);
7194
7195         /* Autoplay the opponent's response.
7196          * if appData.animate was TRUE when Training mode was entered,
7197          * the response will be animated.
7198          */
7199         saveAnimate = appData.animate;
7200         appData.animate = animateTraining;
7201         ForwardInner(currentMove+1);
7202         appData.animate = saveAnimate;
7203
7204         /* check for the end of the game */
7205         if (currentMove >= forwardMostMove) {
7206           gameMode = PlayFromGameFile;
7207           ModeHighlight();
7208           SetTrainingModeOff();
7209           DisplayInformation(_("End of game"));
7210         }
7211       } else {
7212         DisplayError(_("Incorrect move"), 0);
7213       }
7214       return 1;
7215     }
7216
7217   /* Ok, now we know that the move is good, so we can kill
7218      the previous line in Analysis Mode */
7219   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7220                                 && currentMove < forwardMostMove) {
7221     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7222     else forwardMostMove = currentMove;
7223   }
7224
7225   ClearMap();
7226
7227   /* If we need the chess program but it's dead, restart it */
7228   ResurrectChessProgram();
7229
7230   /* A user move restarts a paused game*/
7231   if (pausing)
7232     PauseEvent();
7233
7234   thinkOutput[0] = NULLCHAR;
7235
7236   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7237
7238   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7239     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7240     return 1;
7241   }
7242
7243   if (gameMode == BeginningOfGame) {
7244     if (appData.noChessProgram) {
7245       gameMode = EditGame;
7246       SetGameInfo();
7247     } else {
7248       char buf[MSG_SIZ];
7249       gameMode = MachinePlaysBlack;
7250       StartClocks();
7251       SetGameInfo();
7252       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7253       DisplayTitle(buf);
7254       if (first.sendName) {
7255         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7256         SendToProgram(buf, &first);
7257       }
7258       StartClocks();
7259     }
7260     ModeHighlight();
7261   }
7262
7263   /* Relay move to ICS or chess engine */
7264   if (appData.icsActive) {
7265     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7266         gameMode == IcsExamining) {
7267       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7268         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7269         SendToICS("draw ");
7270         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7271       }
7272       // also send plain move, in case ICS does not understand atomic claims
7273       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7274       ics_user_moved = 1;
7275     }
7276   } else {
7277     if (first.sendTime && (gameMode == BeginningOfGame ||
7278                            gameMode == MachinePlaysWhite ||
7279                            gameMode == MachinePlaysBlack)) {
7280       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7281     }
7282     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7283          // [HGM] book: if program might be playing, let it use book
7284         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7285         first.maybeThinking = TRUE;
7286     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7287         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7288         SendBoard(&first, currentMove+1);
7289         if(second.analyzing) {
7290             if(!second.useSetboard) SendToProgram("undo\n", &second);
7291             SendBoard(&second, currentMove+1);
7292         }
7293     } else {
7294         SendMoveToProgram(forwardMostMove-1, &first);
7295         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7296     }
7297     if (currentMove == cmailOldMove + 1) {
7298       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7299     }
7300   }
7301
7302   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7303
7304   switch (gameMode) {
7305   case EditGame:
7306     if(appData.testLegality)
7307     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7308     case MT_NONE:
7309     case MT_CHECK:
7310       break;
7311     case MT_CHECKMATE:
7312     case MT_STAINMATE:
7313       if (WhiteOnMove(currentMove)) {
7314         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7315       } else {
7316         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7317       }
7318       break;
7319     case MT_STALEMATE:
7320       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7321       break;
7322     }
7323     break;
7324
7325   case MachinePlaysBlack:
7326   case MachinePlaysWhite:
7327     /* disable certain menu options while machine is thinking */
7328     SetMachineThinkingEnables();
7329     break;
7330
7331   default:
7332     break;
7333   }
7334
7335   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7336   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7337
7338   if(bookHit) { // [HGM] book: simulate book reply
7339         static char bookMove[MSG_SIZ]; // a bit generous?
7340
7341         programStats.nodes = programStats.depth = programStats.time =
7342         programStats.score = programStats.got_only_move = 0;
7343         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7344
7345         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7346         strcat(bookMove, bookHit);
7347         HandleMachineMove(bookMove, &first);
7348   }
7349   return 1;
7350 }
7351
7352 void
7353 MarkByFEN(char *fen)
7354 {
7355         int r, f;
7356         if(!appData.markers || !appData.highlightDragging) return;
7357         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7358         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7359         while(*fen) {
7360             int s = 0;
7361             marker[r][f] = 0;
7362             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7363             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7364             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7365             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7366             if(*fen == 'T') marker[r][f++] = 0; else
7367             if(*fen == 'Y') marker[r][f++] = 1; else
7368             if(*fen == 'G') marker[r][f++] = 3; else
7369             if(*fen == 'B') marker[r][f++] = 4; else
7370             if(*fen == 'C') marker[r][f++] = 5; else
7371             if(*fen == 'M') marker[r][f++] = 6; else
7372             if(*fen == 'W') marker[r][f++] = 7; else
7373             if(*fen == 'D') marker[r][f++] = 8; else
7374             if(*fen == 'R') marker[r][f++] = 2; else {
7375                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7376               f += s; fen -= s>0;
7377             }
7378             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7379             if(r < 0) break;
7380             fen++;
7381         }
7382         DrawPosition(TRUE, NULL);
7383 }
7384
7385 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7386
7387 void
7388 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7389 {
7390     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7391     Markers *m = (Markers *) closure;
7392     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7393         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7394                          || kind == WhiteCapturesEnPassant
7395                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7396     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7397 }
7398
7399 static int hoverSavedValid;
7400
7401 void
7402 MarkTargetSquares (int clear)
7403 {
7404   int x, y, sum=0;
7405   if(clear) { // no reason to ever suppress clearing
7406     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7407     hoverSavedValid = 0;
7408     if(!sum) return; // nothing was cleared,no redraw needed
7409   } else {
7410     int capt = 0;
7411     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7412        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7413     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7414     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7415       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7416       if(capt)
7417       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7418     }
7419   }
7420   DrawPosition(FALSE, NULL);
7421 }
7422
7423 int
7424 Explode (Board board, int fromX, int fromY, int toX, int toY)
7425 {
7426     if(gameInfo.variant == VariantAtomic &&
7427        (board[toY][toX] != EmptySquare ||                     // capture?
7428         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7429                          board[fromY][fromX] == BlackPawn   )
7430       )) {
7431         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7432         return TRUE;
7433     }
7434     return FALSE;
7435 }
7436
7437 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7438
7439 int
7440 CanPromote (ChessSquare piece, int y)
7441 {
7442         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7443         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7444         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7445         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7446            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7447            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7448          gameInfo.variant == VariantMakruk) return FALSE;
7449         return (piece == BlackPawn && y <= zone ||
7450                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7451                 piece == BlackLance && y <= zone ||
7452                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7453 }
7454
7455 void
7456 HoverEvent (int xPix, int yPix, int x, int y)
7457 {
7458         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7459         int r, f;
7460         if(!first.highlight) return;
7461         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7462         if(x == oldX && y == oldY) return; // only do something if we enter new square
7463         oldFromX = fromX; oldFromY = fromY;
7464         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7465           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7466             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7467           hoverSavedValid = 1;
7468         } else if(oldX != x || oldY != y) {
7469           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7470           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7471           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7472             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7473           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7474             char buf[MSG_SIZ];
7475             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7476             SendToProgram(buf, &first);
7477           }
7478           oldX = x; oldY = y;
7479 //        SetHighlights(fromX, fromY, x, y);
7480         }
7481 }
7482
7483 void ReportClick(char *action, int x, int y)
7484 {
7485         char buf[MSG_SIZ]; // Inform engine of what user does
7486         int r, f;
7487         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7488           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7489             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7490         if(!first.highlight || gameMode == EditPosition) return;
7491         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7492         SendToProgram(buf, &first);
7493 }
7494
7495 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7496
7497 void
7498 LeftClick (ClickType clickType, int xPix, int yPix)
7499 {
7500     int x, y;
7501     Boolean saveAnimate;
7502     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7503     char promoChoice = NULLCHAR;
7504     ChessSquare piece;
7505     static TimeMark lastClickTime, prevClickTime;
7506
7507     x = EventToSquare(xPix, BOARD_WIDTH);
7508     y = EventToSquare(yPix, BOARD_HEIGHT);
7509     if (!flipView && y >= 0) {
7510         y = BOARD_HEIGHT - 1 - y;
7511     }
7512     if (flipView && x >= 0) {
7513         x = BOARD_WIDTH - 1 - x;
7514     }
7515
7516     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7517         static int dummy;
7518         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7519         right = TRUE;
7520         return;
7521     }
7522
7523     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7524
7525     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7526
7527     if (clickType == Press) ErrorPopDown();
7528     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7529
7530     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7531         defaultPromoChoice = promoSweep;
7532         promoSweep = EmptySquare;   // terminate sweep
7533         promoDefaultAltered = TRUE;
7534         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7535     }
7536
7537     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7538         if(clickType == Release) return; // ignore upclick of click-click destination
7539         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7540         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7541         if(gameInfo.holdingsWidth &&
7542                 (WhiteOnMove(currentMove)
7543                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7544                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7545             // click in right holdings, for determining promotion piece
7546             ChessSquare p = boards[currentMove][y][x];
7547             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7548             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7549             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7550                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7551                 fromX = fromY = -1;
7552                 return;
7553             }
7554         }
7555         DrawPosition(FALSE, boards[currentMove]);
7556         return;
7557     }
7558
7559     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7560     if(clickType == Press
7561             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7562               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7563               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7564         return;
7565
7566     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7567         // could be static click on premove from-square: abort premove
7568         gotPremove = 0;
7569         ClearPremoveHighlights();
7570     }
7571
7572     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7573         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7574
7575     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7576         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7577                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7578         defaultPromoChoice = DefaultPromoChoice(side);
7579     }
7580
7581     autoQueen = appData.alwaysPromoteToQueen;
7582
7583     if (fromX == -1) {
7584       int originalY = y;
7585       gatingPiece = EmptySquare;
7586       if (clickType != Press) {
7587         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7588             DragPieceEnd(xPix, yPix); dragging = 0;
7589             DrawPosition(FALSE, NULL);
7590         }
7591         return;
7592       }
7593       doubleClick = FALSE;
7594       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7595         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7596       }
7597       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7598       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7599          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7600          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7601             /* First square */
7602             if (OKToStartUserMove(fromX, fromY)) {
7603                 second = 0;
7604                 ReportClick("lift", x, y);
7605                 MarkTargetSquares(0);
7606                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7607                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7608                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7609                     promoSweep = defaultPromoChoice;
7610                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7611                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7612                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7613                 }
7614                 if (appData.highlightDragging) {
7615                     SetHighlights(fromX, fromY, -1, -1);
7616                 } else {
7617                     ClearHighlights();
7618                 }
7619             } else fromX = fromY = -1;
7620             return;
7621         }
7622     }
7623
7624     /* fromX != -1 */
7625     if (clickType == Press && gameMode != EditPosition) {
7626         ChessSquare fromP;
7627         ChessSquare toP;
7628         int frc;
7629
7630         // ignore off-board to clicks
7631         if(y < 0 || x < 0) return;
7632
7633         /* Check if clicking again on the same color piece */
7634         fromP = boards[currentMove][fromY][fromX];
7635         toP = boards[currentMove][y][x];
7636         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7637         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7638             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7639            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7640              WhitePawn <= toP && toP <= WhiteKing &&
7641              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7642              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7643             (BlackPawn <= fromP && fromP <= BlackKing &&
7644              BlackPawn <= toP && toP <= BlackKing &&
7645              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7646              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7647             /* Clicked again on same color piece -- changed his mind */
7648             second = (x == fromX && y == fromY);
7649             killX = killY = -1;
7650             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7651                 second = FALSE; // first double-click rather than scond click
7652                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7653             }
7654             promoDefaultAltered = FALSE;
7655             MarkTargetSquares(1);
7656            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7657             if (appData.highlightDragging) {
7658                 SetHighlights(x, y, -1, -1);
7659             } else {
7660                 ClearHighlights();
7661             }
7662             if (OKToStartUserMove(x, y)) {
7663                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7664                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7665                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7666                  gatingPiece = boards[currentMove][fromY][fromX];
7667                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7668                 fromX = x;
7669                 fromY = y; dragging = 1;
7670                 if(!second) ReportClick("lift", x, y);
7671                 MarkTargetSquares(0);
7672                 DragPieceBegin(xPix, yPix, FALSE);
7673                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7674                     promoSweep = defaultPromoChoice;
7675                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7676                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7677                 }
7678             }
7679            }
7680            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7681            second = FALSE;
7682         }
7683         // ignore clicks on holdings
7684         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7685     }
7686
7687     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7688         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7689         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7690         return;
7691     }
7692
7693     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7694         DragPieceEnd(xPix, yPix); dragging = 0;
7695         if(clearFlag) {
7696             // a deferred attempt to click-click move an empty square on top of a piece
7697             boards[currentMove][y][x] = EmptySquare;
7698             ClearHighlights();
7699             DrawPosition(FALSE, boards[currentMove]);
7700             fromX = fromY = -1; clearFlag = 0;
7701             return;
7702         }
7703         if (appData.animateDragging) {
7704             /* Undo animation damage if any */
7705             DrawPosition(FALSE, NULL);
7706         }
7707         if (second) {
7708             /* Second up/down in same square; just abort move */
7709             second = 0;
7710             fromX = fromY = -1;
7711             gatingPiece = EmptySquare;
7712             MarkTargetSquares(1);
7713             ClearHighlights();
7714             gotPremove = 0;
7715             ClearPremoveHighlights();
7716         } else {
7717             /* First upclick in same square; start click-click mode */
7718             SetHighlights(x, y, -1, -1);
7719         }
7720         return;
7721     }
7722
7723     clearFlag = 0;
7724
7725     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7726        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7727         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7728         DisplayMessage(_("only marked squares are legal"),"");
7729         DrawPosition(TRUE, NULL);
7730         return; // ignore to-click
7731     }
7732
7733     /* we now have a different from- and (possibly off-board) to-square */
7734     /* Completed move */
7735     if(!sweepSelecting) {
7736         toX = x;
7737         toY = y;
7738     }
7739
7740     piece = boards[currentMove][fromY][fromX];
7741
7742     saveAnimate = appData.animate;
7743     if (clickType == Press) {
7744         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7745         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7746             // must be Edit Position mode with empty-square selected
7747             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7748             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7749             return;
7750         }
7751         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7752             return;
7753         }
7754         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7755             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7756         } else
7757         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7758         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7759           if(appData.sweepSelect) {
7760             promoSweep = defaultPromoChoice;
7761             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7762             selectFlag = 0; lastX = xPix; lastY = yPix;
7763             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7764             Sweep(0); // Pawn that is going to promote: preview promotion piece
7765             sweepSelecting = 1;
7766             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7767             MarkTargetSquares(1);
7768           }
7769           return; // promo popup appears on up-click
7770         }
7771         /* Finish clickclick move */
7772         if (appData.animate || appData.highlightLastMove) {
7773             SetHighlights(fromX, fromY, toX, toY);
7774         } else {
7775             ClearHighlights();
7776         }
7777     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7778         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7779         *promoRestrict = 0;
7780         if (appData.animate || appData.highlightLastMove) {
7781             SetHighlights(fromX, fromY, toX, toY);
7782         } else {
7783             ClearHighlights();
7784         }
7785     } else {
7786 #if 0
7787 // [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
7788         /* Finish drag move */
7789         if (appData.highlightLastMove) {
7790             SetHighlights(fromX, fromY, toX, toY);
7791         } else {
7792             ClearHighlights();
7793         }
7794 #endif
7795         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7796         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7797           dragging *= 2;            // flag button-less dragging if we are dragging
7798           MarkTargetSquares(1);
7799           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7800           else {
7801             kill2X = killX; kill2Y = killY;
7802             killX = x; killY = y;     //remeber this square as intermediate
7803             ReportClick("put", x, y); // and inform engine
7804             ReportClick("lift", x, y);
7805             MarkTargetSquares(0);
7806             return;
7807           }
7808         }
7809         DragPieceEnd(xPix, yPix); dragging = 0;
7810         /* Don't animate move and drag both */
7811         appData.animate = FALSE;
7812     }
7813
7814     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7815     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7816         ChessSquare piece = boards[currentMove][fromY][fromX];
7817         if(gameMode == EditPosition && piece != EmptySquare &&
7818            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7819             int n;
7820
7821             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7822                 n = PieceToNumber(piece - (int)BlackPawn);
7823                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7824                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7825                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7826             } else
7827             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7828                 n = PieceToNumber(piece);
7829                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7830                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7831                 boards[currentMove][n][BOARD_WIDTH-2]++;
7832             }
7833             boards[currentMove][fromY][fromX] = EmptySquare;
7834         }
7835         ClearHighlights();
7836         fromX = fromY = -1;
7837         MarkTargetSquares(1);
7838         DrawPosition(TRUE, boards[currentMove]);
7839         return;
7840     }
7841
7842     // off-board moves should not be highlighted
7843     if(x < 0 || y < 0) ClearHighlights();
7844     else ReportClick("put", x, y);
7845
7846     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7847
7848     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7849
7850     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7851         SetHighlights(fromX, fromY, toX, toY);
7852         MarkTargetSquares(1);
7853         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7854             // [HGM] super: promotion to captured piece selected from holdings
7855             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7856             promotionChoice = TRUE;
7857             // kludge follows to temporarily execute move on display, without promoting yet
7858             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7859             boards[currentMove][toY][toX] = p;
7860             DrawPosition(FALSE, boards[currentMove]);
7861             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7862             boards[currentMove][toY][toX] = q;
7863             DisplayMessage("Click in holdings to choose piece", "");
7864             return;
7865         }
7866         PromotionPopUp(promoChoice);
7867     } else {
7868         int oldMove = currentMove;
7869         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7870         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7871         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7872         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7873            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7874             DrawPosition(TRUE, boards[currentMove]);
7875         MarkTargetSquares(1);
7876         fromX = fromY = -1;
7877     }
7878     appData.animate = saveAnimate;
7879     if (appData.animate || appData.animateDragging) {
7880         /* Undo animation damage if needed */
7881         DrawPosition(FALSE, NULL);
7882     }
7883 }
7884
7885 int
7886 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7887 {   // front-end-free part taken out of PieceMenuPopup
7888     int whichMenu; int xSqr, ySqr;
7889
7890     if(seekGraphUp) { // [HGM] seekgraph
7891         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7892         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7893         return -2;
7894     }
7895
7896     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7897          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7898         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7899         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7900         if(action == Press)   {
7901             originalFlip = flipView;
7902             flipView = !flipView; // temporarily flip board to see game from partners perspective
7903             DrawPosition(TRUE, partnerBoard);
7904             DisplayMessage(partnerStatus, "");
7905             partnerUp = TRUE;
7906         } else if(action == Release) {
7907             flipView = originalFlip;
7908             DrawPosition(TRUE, boards[currentMove]);
7909             partnerUp = FALSE;
7910         }
7911         return -2;
7912     }
7913
7914     xSqr = EventToSquare(x, BOARD_WIDTH);
7915     ySqr = EventToSquare(y, BOARD_HEIGHT);
7916     if (action == Release) {
7917         if(pieceSweep != EmptySquare) {
7918             EditPositionMenuEvent(pieceSweep, toX, toY);
7919             pieceSweep = EmptySquare;
7920         } else UnLoadPV(); // [HGM] pv
7921     }
7922     if (action != Press) return -2; // return code to be ignored
7923     switch (gameMode) {
7924       case IcsExamining:
7925         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7926       case EditPosition:
7927         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7928         if (xSqr < 0 || ySqr < 0) return -1;
7929         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7930         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7931         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7932         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7933         NextPiece(0);
7934         return 2; // grab
7935       case IcsObserving:
7936         if(!appData.icsEngineAnalyze) return -1;
7937       case IcsPlayingWhite:
7938       case IcsPlayingBlack:
7939         if(!appData.zippyPlay) goto noZip;
7940       case AnalyzeMode:
7941       case AnalyzeFile:
7942       case MachinePlaysWhite:
7943       case MachinePlaysBlack:
7944       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7945         if (!appData.dropMenu) {
7946           LoadPV(x, y);
7947           return 2; // flag front-end to grab mouse events
7948         }
7949         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7950            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7951       case EditGame:
7952       noZip:
7953         if (xSqr < 0 || ySqr < 0) return -1;
7954         if (!appData.dropMenu || appData.testLegality &&
7955             gameInfo.variant != VariantBughouse &&
7956             gameInfo.variant != VariantCrazyhouse) return -1;
7957         whichMenu = 1; // drop menu
7958         break;
7959       default:
7960         return -1;
7961     }
7962
7963     if (((*fromX = xSqr) < 0) ||
7964         ((*fromY = ySqr) < 0)) {
7965         *fromX = *fromY = -1;
7966         return -1;
7967     }
7968     if (flipView)
7969       *fromX = BOARD_WIDTH - 1 - *fromX;
7970     else
7971       *fromY = BOARD_HEIGHT - 1 - *fromY;
7972
7973     return whichMenu;
7974 }
7975
7976 void
7977 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7978 {
7979 //    char * hint = lastHint;
7980     FrontEndProgramStats stats;
7981
7982     stats.which = cps == &first ? 0 : 1;
7983     stats.depth = cpstats->depth;
7984     stats.nodes = cpstats->nodes;
7985     stats.score = cpstats->score;
7986     stats.time = cpstats->time;
7987     stats.pv = cpstats->movelist;
7988     stats.hint = lastHint;
7989     stats.an_move_index = 0;
7990     stats.an_move_count = 0;
7991
7992     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7993         stats.hint = cpstats->move_name;
7994         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7995         stats.an_move_count = cpstats->nr_moves;
7996     }
7997
7998     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
7999
8000     SetProgramStats( &stats );
8001 }
8002
8003 void
8004 ClearEngineOutputPane (int which)
8005 {
8006     static FrontEndProgramStats dummyStats;
8007     dummyStats.which = which;
8008     dummyStats.pv = "#";
8009     SetProgramStats( &dummyStats );
8010 }
8011
8012 #define MAXPLAYERS 500
8013
8014 char *
8015 TourneyStandings (int display)
8016 {
8017     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8018     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8019     char result, *p, *names[MAXPLAYERS];
8020
8021     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8022         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8023     names[0] = p = strdup(appData.participants);
8024     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8025
8026     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8027
8028     while(result = appData.results[nr]) {
8029         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8030         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8031         wScore = bScore = 0;
8032         switch(result) {
8033           case '+': wScore = 2; break;
8034           case '-': bScore = 2; break;
8035           case '=': wScore = bScore = 1; break;
8036           case ' ':
8037           case '*': return strdup("busy"); // tourney not finished
8038         }
8039         score[w] += wScore;
8040         score[b] += bScore;
8041         games[w]++;
8042         games[b]++;
8043         nr++;
8044     }
8045     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8046     for(w=0; w<nPlayers; w++) {
8047         bScore = -1;
8048         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8049         ranking[w] = b; points[w] = bScore; score[b] = -2;
8050     }
8051     p = malloc(nPlayers*34+1);
8052     for(w=0; w<nPlayers && w<display; w++)
8053         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8054     free(names[0]);
8055     return p;
8056 }
8057
8058 void
8059 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8060 {       // count all piece types
8061         int p, f, r;
8062         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8063         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8064         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8065                 p = board[r][f];
8066                 pCnt[p]++;
8067                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8068                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8069                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8070                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8071                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8072                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8073         }
8074 }
8075
8076 int
8077 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8078 {
8079         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8080         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8081
8082         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8083         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8084         if(myPawns == 2 && nMine == 3) // KPP
8085             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8086         if(myPawns == 1 && nMine == 2) // KP
8087             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8088         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8089             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8090         if(myPawns) return FALSE;
8091         if(pCnt[WhiteRook+side])
8092             return pCnt[BlackRook-side] ||
8093                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8094                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8095                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8096         if(pCnt[WhiteCannon+side]) {
8097             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8098             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8099         }
8100         if(pCnt[WhiteKnight+side])
8101             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8102         return FALSE;
8103 }
8104
8105 int
8106 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8107 {
8108         VariantClass v = gameInfo.variant;
8109
8110         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8111         if(v == VariantShatranj) return TRUE; // always winnable through baring
8112         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8113         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8114
8115         if(v == VariantXiangqi) {
8116                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8117
8118                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8119                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8120                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8121                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8122                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8123                 if(stale) // we have at least one last-rank P plus perhaps C
8124                     return majors // KPKX
8125                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8126                 else // KCA*E*
8127                     return pCnt[WhiteFerz+side] // KCAK
8128                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8129                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8130                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8131
8132         } else if(v == VariantKnightmate) {
8133                 if(nMine == 1) return FALSE;
8134                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8135         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8136                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8137
8138                 if(nMine == 1) return FALSE; // bare King
8139                 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
8140                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8141                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8142                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8143                 if(pCnt[WhiteKnight+side])
8144                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8145                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8146                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8147                 if(nBishops)
8148                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8149                 if(pCnt[WhiteAlfil+side])
8150                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8151                 if(pCnt[WhiteWazir+side])
8152                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8153         }
8154
8155         return TRUE;
8156 }
8157
8158 int
8159 CompareWithRights (Board b1, Board b2)
8160 {
8161     int rights = 0;
8162     if(!CompareBoards(b1, b2)) return FALSE;
8163     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8164     /* compare castling rights */
8165     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8166            rights++; /* King lost rights, while rook still had them */
8167     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8168         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8169            rights++; /* but at least one rook lost them */
8170     }
8171     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8172            rights++;
8173     if( b1[CASTLING][5] != NoRights ) {
8174         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8175            rights++;
8176     }
8177     return rights == 0;
8178 }
8179
8180 int
8181 Adjudicate (ChessProgramState *cps)
8182 {       // [HGM] some adjudications useful with buggy engines
8183         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8184         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8185         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8186         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8187         int k, drop, count = 0; static int bare = 1;
8188         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8189         Boolean canAdjudicate = !appData.icsActive;
8190
8191         // most tests only when we understand the game, i.e. legality-checking on
8192             if( appData.testLegality )
8193             {   /* [HGM] Some more adjudications for obstinate engines */
8194                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8195                 static int moveCount = 6;
8196                 ChessMove result;
8197                 char *reason = NULL;
8198
8199                 /* Count what is on board. */
8200                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8201
8202                 /* Some material-based adjudications that have to be made before stalemate test */
8203                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8204                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8205                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8206                      if(canAdjudicate && appData.checkMates) {
8207                          if(engineOpponent)
8208                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8209                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8210                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8211                          return 1;
8212                      }
8213                 }
8214
8215                 /* Bare King in Shatranj (loses) or Losers (wins) */
8216                 if( nrW == 1 || nrB == 1) {
8217                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8218                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8219                      if(canAdjudicate && appData.checkMates) {
8220                          if(engineOpponent)
8221                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8222                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8223                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8224                          return 1;
8225                      }
8226                   } else
8227                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8228                   {    /* bare King */
8229                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8230                         if(canAdjudicate && appData.checkMates) {
8231                             /* but only adjudicate if adjudication enabled */
8232                             if(engineOpponent)
8233                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8234                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8235                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8236                             return 1;
8237                         }
8238                   }
8239                 } else bare = 1;
8240
8241
8242             // don't wait for engine to announce game end if we can judge ourselves
8243             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8244               case MT_CHECK:
8245                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8246                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8247                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8248                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8249                             checkCnt++;
8250                         if(checkCnt >= 2) {
8251                             reason = "Xboard adjudication: 3rd check";
8252                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8253                             break;
8254                         }
8255                     }
8256                 }
8257               case MT_NONE:
8258               default:
8259                 break;
8260               case MT_STEALMATE:
8261               case MT_STALEMATE:
8262               case MT_STAINMATE:
8263                 reason = "Xboard adjudication: Stalemate";
8264                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8265                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8266                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8267                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8268                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8269                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8270                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8271                                                                         EP_CHECKMATE : EP_WINS);
8272                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8273                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8274                 }
8275                 break;
8276               case MT_CHECKMATE:
8277                 reason = "Xboard adjudication: Checkmate";
8278                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8279                 if(gameInfo.variant == VariantShogi) {
8280                     if(forwardMostMove > backwardMostMove
8281                        && moveList[forwardMostMove-1][1] == '@'
8282                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8283                         reason = "XBoard adjudication: pawn-drop mate";
8284                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8285                     }
8286                 }
8287                 break;
8288             }
8289
8290                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8291                     case EP_STALEMATE:
8292                         result = GameIsDrawn; break;
8293                     case EP_CHECKMATE:
8294                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8295                     case EP_WINS:
8296                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8297                     default:
8298                         result = EndOfFile;
8299                 }
8300                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8301                     if(engineOpponent)
8302                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8303                     GameEnds( result, reason, GE_XBOARD );
8304                     return 1;
8305                 }
8306
8307                 /* Next absolutely insufficient mating material. */
8308                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8309                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8310                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8311
8312                      /* always flag draws, for judging claims */
8313                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8314
8315                      if(canAdjudicate && appData.materialDraws) {
8316                          /* but only adjudicate them if adjudication enabled */
8317                          if(engineOpponent) {
8318                            SendToProgram("force\n", engineOpponent); // suppress reply
8319                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8320                          }
8321                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8322                          return 1;
8323                      }
8324                 }
8325
8326                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8327                 if(gameInfo.variant == VariantXiangqi ?
8328                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8329                  : nrW + nrB == 4 &&
8330                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8331                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8332                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8333                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8334                    ) ) {
8335                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8336                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8337                           if(engineOpponent) {
8338                             SendToProgram("force\n", engineOpponent); // suppress reply
8339                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8340                           }
8341                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8342                           return 1;
8343                      }
8344                 } else moveCount = 6;
8345             }
8346
8347         // Repetition draws and 50-move rule can be applied independently of legality testing
8348
8349                 /* Check for rep-draws */
8350                 count = 0;
8351                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8352                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8353                 for(k = forwardMostMove-2;
8354                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8355                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8356                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8357                     k-=2)
8358                 {   int rights=0;
8359                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8360                         /* compare castling rights */
8361                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8362                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8363                                 rights++; /* King lost rights, while rook still had them */
8364                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8365                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8366                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8367                                    rights++; /* but at least one rook lost them */
8368                         }
8369                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8370                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8371                                 rights++;
8372                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8373                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8374                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8375                                    rights++;
8376                         }
8377                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8378                             && appData.drawRepeats > 1) {
8379                              /* adjudicate after user-specified nr of repeats */
8380                              int result = GameIsDrawn;
8381                              char *details = "XBoard adjudication: repetition draw";
8382                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8383                                 // [HGM] xiangqi: check for forbidden perpetuals
8384                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8385                                 for(m=forwardMostMove; m>k; m-=2) {
8386                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8387                                         ourPerpetual = 0; // the current mover did not always check
8388                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8389                                         hisPerpetual = 0; // the opponent did not always check
8390                                 }
8391                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8392                                                                         ourPerpetual, hisPerpetual);
8393                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8394                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8395                                     details = "Xboard adjudication: perpetual checking";
8396                                 } else
8397                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8398                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8399                                 } else
8400                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8401                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8402                                         result = BlackWins;
8403                                         details = "Xboard adjudication: repetition";
8404                                     }
8405                                 } else // it must be XQ
8406                                 // Now check for perpetual chases
8407                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8408                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8409                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8410                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8411                                         static char resdet[MSG_SIZ];
8412                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8413                                         details = resdet;
8414                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8415                                     } else
8416                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8417                                         break; // Abort repetition-checking loop.
8418                                 }
8419                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8420                              }
8421                              if(engineOpponent) {
8422                                SendToProgram("force\n", engineOpponent); // suppress reply
8423                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8424                              }
8425                              GameEnds( result, details, GE_XBOARD );
8426                              return 1;
8427                         }
8428                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8429                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8430                     }
8431                 }
8432
8433                 /* Now we test for 50-move draws. Determine ply count */
8434                 count = forwardMostMove;
8435                 /* look for last irreversble move */
8436                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8437                     count--;
8438                 /* if we hit starting position, add initial plies */
8439                 if( count == backwardMostMove )
8440                     count -= initialRulePlies;
8441                 count = forwardMostMove - count;
8442                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8443                         // adjust reversible move counter for checks in Xiangqi
8444                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8445                         if(i < backwardMostMove) i = backwardMostMove;
8446                         while(i <= forwardMostMove) {
8447                                 lastCheck = inCheck; // check evasion does not count
8448                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8449                                 if(inCheck || lastCheck) count--; // check does not count
8450                                 i++;
8451                         }
8452                 }
8453                 if( count >= 100)
8454                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8455                          /* this is used to judge if draw claims are legal */
8456                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8457                          if(engineOpponent) {
8458                            SendToProgram("force\n", engineOpponent); // suppress reply
8459                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8460                          }
8461                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8462                          return 1;
8463                 }
8464
8465                 /* if draw offer is pending, treat it as a draw claim
8466                  * when draw condition present, to allow engines a way to
8467                  * claim draws before making their move to avoid a race
8468                  * condition occurring after their move
8469                  */
8470                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8471                          char *p = NULL;
8472                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8473                              p = "Draw claim: 50-move rule";
8474                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8475                              p = "Draw claim: 3-fold repetition";
8476                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8477                              p = "Draw claim: insufficient mating material";
8478                          if( p != NULL && canAdjudicate) {
8479                              if(engineOpponent) {
8480                                SendToProgram("force\n", engineOpponent); // suppress reply
8481                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8482                              }
8483                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8484                              return 1;
8485                          }
8486                 }
8487
8488                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8489                     if(engineOpponent) {
8490                       SendToProgram("force\n", engineOpponent); // suppress reply
8491                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8492                     }
8493                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8494                     return 1;
8495                 }
8496         return 0;
8497 }
8498
8499 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8500 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8501 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8502
8503 static int
8504 BitbaseProbe ()
8505 {
8506     int pieces[10], squares[10], cnt=0, r, f, res;
8507     static int loaded;
8508     static PPROBE_EGBB probeBB;
8509     if(!appData.testLegality) return 10;
8510     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8511     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8512     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8513     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8514         ChessSquare piece = boards[forwardMostMove][r][f];
8515         int black = (piece >= BlackPawn);
8516         int type = piece - black*BlackPawn;
8517         if(piece == EmptySquare) continue;
8518         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8519         if(type == WhiteKing) type = WhiteQueen + 1;
8520         type = egbbCode[type];
8521         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8522         pieces[cnt] = type + black*6;
8523         if(++cnt > 5) return 11;
8524     }
8525     pieces[cnt] = squares[cnt] = 0;
8526     // probe EGBB
8527     if(loaded == 2) return 13; // loading failed before
8528     if(loaded == 0) {
8529         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8530         HMODULE lib;
8531         PLOAD_EGBB loadBB;
8532         loaded = 2; // prepare for failure
8533         if(!path) return 13; // no egbb installed
8534         strncpy(buf, path + 8, MSG_SIZ);
8535         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8536         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8537         lib = LoadLibrary(buf);
8538         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8539         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8540         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8541         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8542         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8543         loaded = 1; // success!
8544     }
8545     res = probeBB(forwardMostMove & 1, pieces, squares);
8546     return res > 0 ? 1 : res < 0 ? -1 : 0;
8547 }
8548
8549 char *
8550 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8551 {   // [HGM] book: this routine intercepts moves to simulate book replies
8552     char *bookHit = NULL;
8553
8554     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8555         char buf[MSG_SIZ];
8556         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8557         SendToProgram(buf, cps);
8558     }
8559     //first determine if the incoming move brings opponent into his book
8560     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8561         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8562     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8563     if(bookHit != NULL && !cps->bookSuspend) {
8564         // make sure opponent is not going to reply after receiving move to book position
8565         SendToProgram("force\n", cps);
8566         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8567     }
8568     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8569     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8570     // now arrange restart after book miss
8571     if(bookHit) {
8572         // after a book hit we never send 'go', and the code after the call to this routine
8573         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8574         char buf[MSG_SIZ], *move = bookHit;
8575         if(cps->useSAN) {
8576             int fromX, fromY, toX, toY;
8577             char promoChar;
8578             ChessMove moveType;
8579             move = buf + 30;
8580             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8581                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8582                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8583                                     PosFlags(forwardMostMove),
8584                                     fromY, fromX, toY, toX, promoChar, move);
8585             } else {
8586                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8587                 bookHit = NULL;
8588             }
8589         }
8590         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8591         SendToProgram(buf, cps);
8592         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8593     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8594         SendToProgram("go\n", cps);
8595         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8596     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8597         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8598             SendToProgram("go\n", cps);
8599         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8600     }
8601     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8602 }
8603
8604 int
8605 LoadError (char *errmess, ChessProgramState *cps)
8606 {   // unloads engine and switches back to -ncp mode if it was first
8607     if(cps->initDone) return FALSE;
8608     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8609     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8610     cps->pr = NoProc;
8611     if(cps == &first) {
8612         appData.noChessProgram = TRUE;
8613         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8614         gameMode = BeginningOfGame; ModeHighlight();
8615         SetNCPMode();
8616     }
8617     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8618     DisplayMessage("", ""); // erase waiting message
8619     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8620     return TRUE;
8621 }
8622
8623 char *savedMessage;
8624 ChessProgramState *savedState;
8625 void
8626 DeferredBookMove (void)
8627 {
8628         if(savedState->lastPing != savedState->lastPong)
8629                     ScheduleDelayedEvent(DeferredBookMove, 10);
8630         else
8631         HandleMachineMove(savedMessage, savedState);
8632 }
8633
8634 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8635 static ChessProgramState *stalledEngine;
8636 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8637
8638 void
8639 HandleMachineMove (char *message, ChessProgramState *cps)
8640 {
8641     static char firstLeg[20];
8642     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8643     char realname[MSG_SIZ];
8644     int fromX, fromY, toX, toY;
8645     ChessMove moveType;
8646     char promoChar, roar;
8647     char *p, *pv=buf1;
8648     int oldError;
8649     char *bookHit;
8650
8651     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8652         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8653         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8654             DisplayError(_("Invalid pairing from pairing engine"), 0);
8655             return;
8656         }
8657         pairingReceived = 1;
8658         NextMatchGame();
8659         return; // Skim the pairing messages here.
8660     }
8661
8662     oldError = cps->userError; cps->userError = 0;
8663
8664 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8665     /*
8666      * Kludge to ignore BEL characters
8667      */
8668     while (*message == '\007') message++;
8669
8670     /*
8671      * [HGM] engine debug message: ignore lines starting with '#' character
8672      */
8673     if(cps->debug && *message == '#') return;
8674
8675     /*
8676      * Look for book output
8677      */
8678     if (cps == &first && bookRequested) {
8679         if (message[0] == '\t' || message[0] == ' ') {
8680             /* Part of the book output is here; append it */
8681             strcat(bookOutput, message);
8682             strcat(bookOutput, "  \n");
8683             return;
8684         } else if (bookOutput[0] != NULLCHAR) {
8685             /* All of book output has arrived; display it */
8686             char *p = bookOutput;
8687             while (*p != NULLCHAR) {
8688                 if (*p == '\t') *p = ' ';
8689                 p++;
8690             }
8691             DisplayInformation(bookOutput);
8692             bookRequested = FALSE;
8693             /* Fall through to parse the current output */
8694         }
8695     }
8696
8697     /*
8698      * Look for machine move.
8699      */
8700     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8701         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8702     {
8703         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8704             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8705             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8706             stalledEngine = cps;
8707             if(appData.ponderNextMove) { // bring opponent out of ponder
8708                 if(gameMode == TwoMachinesPlay) {
8709                     if(cps->other->pause)
8710                         PauseEngine(cps->other);
8711                     else
8712                         SendToProgram("easy\n", cps->other);
8713                 }
8714             }
8715             StopClocks();
8716             return;
8717         }
8718
8719       if(cps->usePing) {
8720
8721         /* This method is only useful on engines that support ping */
8722         if(abortEngineThink) {
8723             if (appData.debugMode) {
8724                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8725             }
8726             SendToProgram("undo\n", cps);
8727             return;
8728         }
8729
8730         if (cps->lastPing != cps->lastPong) {
8731             /* Extra move from before last new; ignore */
8732             if (appData.debugMode) {
8733                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8734             }
8735           return;
8736         }
8737
8738       } else {
8739
8740         int machineWhite = FALSE;
8741
8742         switch (gameMode) {
8743           case BeginningOfGame:
8744             /* Extra move from before last reset; ignore */
8745             if (appData.debugMode) {
8746                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8747             }
8748             return;
8749
8750           case EndOfGame:
8751           case IcsIdle:
8752           default:
8753             /* Extra move after we tried to stop.  The mode test is
8754                not a reliable way of detecting this problem, but it's
8755                the best we can do on engines that don't support ping.
8756             */
8757             if (appData.debugMode) {
8758                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8759                         cps->which, gameMode);
8760             }
8761             SendToProgram("undo\n", cps);
8762             return;
8763
8764           case MachinePlaysWhite:
8765           case IcsPlayingWhite:
8766             machineWhite = TRUE;
8767             break;
8768
8769           case MachinePlaysBlack:
8770           case IcsPlayingBlack:
8771             machineWhite = FALSE;
8772             break;
8773
8774           case TwoMachinesPlay:
8775             machineWhite = (cps->twoMachinesColor[0] == 'w');
8776             break;
8777         }
8778         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8779             if (appData.debugMode) {
8780                 fprintf(debugFP,
8781                         "Ignoring move out of turn by %s, gameMode %d"
8782                         ", forwardMost %d\n",
8783                         cps->which, gameMode, forwardMostMove);
8784             }
8785             return;
8786         }
8787       }
8788
8789         if(cps->alphaRank) AlphaRank(machineMove, 4);
8790
8791         // [HGM] lion: (some very limited) support for Alien protocol
8792         killX = killY = kill2X = kill2Y = -1;
8793         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8794             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8795             return;
8796         }
8797         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8798             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8799             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8800         }
8801         if(firstLeg[0]) { // there was a previous leg;
8802             // only support case where same piece makes two step
8803             char buf[20], *p = machineMove+1, *q = buf+1, f;
8804             safeStrCpy(buf, machineMove, 20);
8805             while(isdigit(*q)) q++; // find start of to-square
8806             safeStrCpy(machineMove, firstLeg, 20);
8807             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8808             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8809             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8810             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8811             firstLeg[0] = NULLCHAR;
8812         }
8813
8814         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8815                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8816             /* Machine move could not be parsed; ignore it. */
8817           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8818                     machineMove, _(cps->which));
8819             DisplayMoveError(buf1);
8820             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8821                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8822             if (gameMode == TwoMachinesPlay) {
8823               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8824                        buf1, GE_XBOARD);
8825             }
8826             return;
8827         }
8828
8829         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8830         /* So we have to redo legality test with true e.p. status here,  */
8831         /* to make sure an illegal e.p. capture does not slip through,   */
8832         /* to cause a forfeit on a justified illegal-move complaint      */
8833         /* of the opponent.                                              */
8834         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8835            ChessMove moveType;
8836            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8837                              fromY, fromX, toY, toX, promoChar);
8838             if(moveType == IllegalMove) {
8839               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8840                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8841                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8842                            buf1, GE_XBOARD);
8843                 return;
8844            } else if(!appData.fischerCastling)
8845            /* [HGM] Kludge to handle engines that send FRC-style castling
8846               when they shouldn't (like TSCP-Gothic) */
8847            switch(moveType) {
8848              case WhiteASideCastleFR:
8849              case BlackASideCastleFR:
8850                toX+=2;
8851                currentMoveString[2]++;
8852                break;
8853              case WhiteHSideCastleFR:
8854              case BlackHSideCastleFR:
8855                toX--;
8856                currentMoveString[2]--;
8857                break;
8858              default: ; // nothing to do, but suppresses warning of pedantic compilers
8859            }
8860         }
8861         hintRequested = FALSE;
8862         lastHint[0] = NULLCHAR;
8863         bookRequested = FALSE;
8864         /* Program may be pondering now */
8865         cps->maybeThinking = TRUE;
8866         if (cps->sendTime == 2) cps->sendTime = 1;
8867         if (cps->offeredDraw) cps->offeredDraw--;
8868
8869         /* [AS] Save move info*/
8870         pvInfoList[ forwardMostMove ].score = programStats.score;
8871         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8872         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8873
8874         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8875
8876         /* Test suites abort the 'game' after one move */
8877         if(*appData.finger) {
8878            static FILE *f;
8879            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8880            if(!f) f = fopen(appData.finger, "w");
8881            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8882            else { DisplayFatalError("Bad output file", errno, 0); return; }
8883            free(fen);
8884            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8885         }
8886         if(appData.epd) {
8887            if(solvingTime >= 0) {
8888               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8889               totalTime += solvingTime; first.matchWins++;
8890            } else {
8891               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8892               second.matchWins++;
8893            }
8894            OutputKibitz(2, buf1);
8895            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8896         }
8897
8898         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8899         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8900             int count = 0;
8901
8902             while( count < adjudicateLossPlies ) {
8903                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8904
8905                 if( count & 1 ) {
8906                     score = -score; /* Flip score for winning side */
8907                 }
8908
8909                 if( score > appData.adjudicateLossThreshold ) {
8910                     break;
8911                 }
8912
8913                 count++;
8914             }
8915
8916             if( count >= adjudicateLossPlies ) {
8917                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8918
8919                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8920                     "Xboard adjudication",
8921                     GE_XBOARD );
8922
8923                 return;
8924             }
8925         }
8926
8927         if(Adjudicate(cps)) {
8928             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8929             return; // [HGM] adjudicate: for all automatic game ends
8930         }
8931
8932 #if ZIPPY
8933         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8934             first.initDone) {
8935           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8936                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8937                 SendToICS("draw ");
8938                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8939           }
8940           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8941           ics_user_moved = 1;
8942           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8943                 char buf[3*MSG_SIZ];
8944
8945                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8946                         programStats.score / 100.,
8947                         programStats.depth,
8948                         programStats.time / 100.,
8949                         (unsigned int)programStats.nodes,
8950                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8951                         programStats.movelist);
8952                 SendToICS(buf);
8953           }
8954         }
8955 #endif
8956
8957         /* [AS] Clear stats for next move */
8958         ClearProgramStats();
8959         thinkOutput[0] = NULLCHAR;
8960         hiddenThinkOutputState = 0;
8961
8962         bookHit = NULL;
8963         if (gameMode == TwoMachinesPlay) {
8964             /* [HGM] relaying draw offers moved to after reception of move */
8965             /* and interpreting offer as claim if it brings draw condition */
8966             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8967                 SendToProgram("draw\n", cps->other);
8968             }
8969             if (cps->other->sendTime) {
8970                 SendTimeRemaining(cps->other,
8971                                   cps->other->twoMachinesColor[0] == 'w');
8972             }
8973             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8974             if (firstMove && !bookHit) {
8975                 firstMove = FALSE;
8976                 if (cps->other->useColors) {
8977                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8978                 }
8979                 SendToProgram("go\n", cps->other);
8980             }
8981             cps->other->maybeThinking = TRUE;
8982         }
8983
8984         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8985
8986         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8987
8988         if (!pausing && appData.ringBellAfterMoves) {
8989             if(!roar) RingBell();
8990         }
8991
8992         /*
8993          * Reenable menu items that were disabled while
8994          * machine was thinking
8995          */
8996         if (gameMode != TwoMachinesPlay)
8997             SetUserThinkingEnables();
8998
8999         // [HGM] book: after book hit opponent has received move and is now in force mode
9000         // force the book reply into it, and then fake that it outputted this move by jumping
9001         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9002         if(bookHit) {
9003                 static char bookMove[MSG_SIZ]; // a bit generous?
9004
9005                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9006                 strcat(bookMove, bookHit);
9007                 message = bookMove;
9008                 cps = cps->other;
9009                 programStats.nodes = programStats.depth = programStats.time =
9010                 programStats.score = programStats.got_only_move = 0;
9011                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9012
9013                 if(cps->lastPing != cps->lastPong) {
9014                     savedMessage = message; // args for deferred call
9015                     savedState = cps;
9016                     ScheduleDelayedEvent(DeferredBookMove, 10);
9017                     return;
9018                 }
9019                 goto FakeBookMove;
9020         }
9021
9022         return;
9023     }
9024
9025     /* Set special modes for chess engines.  Later something general
9026      *  could be added here; for now there is just one kludge feature,
9027      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9028      *  when "xboard" is given as an interactive command.
9029      */
9030     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9031         cps->useSigint = FALSE;
9032         cps->useSigterm = FALSE;
9033     }
9034     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9035       ParseFeatures(message+8, cps);
9036       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9037     }
9038
9039     if (!strncmp(message, "setup ", 6) && 
9040         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9041           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9042                                         ) { // [HGM] allow first engine to define opening position
9043       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9044       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9045       *buf = NULLCHAR;
9046       if(sscanf(message, "setup (%s", buf) == 1) {
9047         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9048         ASSIGN(appData.pieceToCharTable, buf);
9049       }
9050       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9051       if(dummy >= 3) {
9052         while(message[s] && message[s++] != ' ');
9053         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9054            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9055             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9056             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9057           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9058           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9059           startedFromSetupPosition = FALSE;
9060         }
9061       }
9062       if(startedFromSetupPosition) return;
9063       ParseFEN(boards[0], &dummy, message+s, FALSE);
9064       DrawPosition(TRUE, boards[0]);
9065       CopyBoard(initialPosition, boards[0]);
9066       startedFromSetupPosition = TRUE;
9067       return;
9068     }
9069     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9070       ChessSquare piece = WhitePawn;
9071       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9072       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9073       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9074       piece += CharToPiece(ID & 255) - WhitePawn;
9075       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9076       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9077       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9078       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9079       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9080       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9081                                                && gameInfo.variant != VariantGreat
9082                                                && gameInfo.variant != VariantFairy    ) return;
9083       if(piece < EmptySquare) {
9084         pieceDefs = TRUE;
9085         ASSIGN(pieceDesc[piece], buf1);
9086         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9087       }
9088       return;
9089     }
9090     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9091       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9092       Sweep(0);
9093       return;
9094     }
9095     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9096      * want this, I was asked to put it in, and obliged.
9097      */
9098     if (!strncmp(message, "setboard ", 9)) {
9099         Board initial_position;
9100
9101         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9102
9103         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9104             DisplayError(_("Bad FEN received from engine"), 0);
9105             return ;
9106         } else {
9107            Reset(TRUE, FALSE);
9108            CopyBoard(boards[0], initial_position);
9109            initialRulePlies = FENrulePlies;
9110            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9111            else gameMode = MachinePlaysBlack;
9112            DrawPosition(FALSE, boards[currentMove]);
9113         }
9114         return;
9115     }
9116
9117     /*
9118      * Look for communication commands
9119      */
9120     if (!strncmp(message, "telluser ", 9)) {
9121         if(message[9] == '\\' && message[10] == '\\')
9122             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9123         PlayTellSound();
9124         DisplayNote(message + 9);
9125         return;
9126     }
9127     if (!strncmp(message, "tellusererror ", 14)) {
9128         cps->userError = 1;
9129         if(message[14] == '\\' && message[15] == '\\')
9130             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9131         PlayTellSound();
9132         DisplayError(message + 14, 0);
9133         return;
9134     }
9135     if (!strncmp(message, "tellopponent ", 13)) {
9136       if (appData.icsActive) {
9137         if (loggedOn) {
9138           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9139           SendToICS(buf1);
9140         }
9141       } else {
9142         DisplayNote(message + 13);
9143       }
9144       return;
9145     }
9146     if (!strncmp(message, "tellothers ", 11)) {
9147       if (appData.icsActive) {
9148         if (loggedOn) {
9149           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9150           SendToICS(buf1);
9151         }
9152       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9153       return;
9154     }
9155     if (!strncmp(message, "tellall ", 8)) {
9156       if (appData.icsActive) {
9157         if (loggedOn) {
9158           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9159           SendToICS(buf1);
9160         }
9161       } else {
9162         DisplayNote(message + 8);
9163       }
9164       return;
9165     }
9166     if (strncmp(message, "warning", 7) == 0) {
9167         /* Undocumented feature, use tellusererror in new code */
9168         DisplayError(message, 0);
9169         return;
9170     }
9171     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9172         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9173         strcat(realname, " query");
9174         AskQuestion(realname, buf2, buf1, cps->pr);
9175         return;
9176     }
9177     /* Commands from the engine directly to ICS.  We don't allow these to be
9178      *  sent until we are logged on. Crafty kibitzes have been known to
9179      *  interfere with the login process.
9180      */
9181     if (loggedOn) {
9182         if (!strncmp(message, "tellics ", 8)) {
9183             SendToICS(message + 8);
9184             SendToICS("\n");
9185             return;
9186         }
9187         if (!strncmp(message, "tellicsnoalias ", 15)) {
9188             SendToICS(ics_prefix);
9189             SendToICS(message + 15);
9190             SendToICS("\n");
9191             return;
9192         }
9193         /* The following are for backward compatibility only */
9194         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9195             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9196             SendToICS(ics_prefix);
9197             SendToICS(message);
9198             SendToICS("\n");
9199             return;
9200         }
9201     }
9202     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9203         if(initPing == cps->lastPong) {
9204             if(gameInfo.variant == VariantUnknown) {
9205                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9206                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9207                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9208             }
9209             initPing = -1;
9210         }
9211         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9212             abortEngineThink = FALSE;
9213             DisplayMessage("", "");
9214             ThawUI();
9215         }
9216         return;
9217     }
9218     if(!strncmp(message, "highlight ", 10)) {
9219         if(appData.testLegality && !*engineVariant && appData.markers) return;
9220         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9221         return;
9222     }
9223     if(!strncmp(message, "click ", 6)) {
9224         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9225         if(appData.testLegality || !appData.oneClick) return;
9226         sscanf(message+6, "%c%d%c", &f, &y, &c);
9227         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9228         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9229         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9230         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9231         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9232         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9233             LeftClick(Release, lastLeftX, lastLeftY);
9234         controlKey  = (c == ',');
9235         LeftClick(Press, x, y);
9236         LeftClick(Release, x, y);
9237         first.highlight = f;
9238         return;
9239     }
9240     /*
9241      * If the move is illegal, cancel it and redraw the board.
9242      * Also deal with other error cases.  Matching is rather loose
9243      * here to accommodate engines written before the spec.
9244      */
9245     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9246         strncmp(message, "Error", 5) == 0) {
9247         if (StrStr(message, "name") ||
9248             StrStr(message, "rating") || StrStr(message, "?") ||
9249             StrStr(message, "result") || StrStr(message, "board") ||
9250             StrStr(message, "bk") || StrStr(message, "computer") ||
9251             StrStr(message, "variant") || StrStr(message, "hint") ||
9252             StrStr(message, "random") || StrStr(message, "depth") ||
9253             StrStr(message, "accepted")) {
9254             return;
9255         }
9256         if (StrStr(message, "protover")) {
9257           /* Program is responding to input, so it's apparently done
9258              initializing, and this error message indicates it is
9259              protocol version 1.  So we don't need to wait any longer
9260              for it to initialize and send feature commands. */
9261           FeatureDone(cps, 1);
9262           cps->protocolVersion = 1;
9263           return;
9264         }
9265         cps->maybeThinking = FALSE;
9266
9267         if (StrStr(message, "draw")) {
9268             /* Program doesn't have "draw" command */
9269             cps->sendDrawOffers = 0;
9270             return;
9271         }
9272         if (cps->sendTime != 1 &&
9273             (StrStr(message, "time") || StrStr(message, "otim"))) {
9274           /* Program apparently doesn't have "time" or "otim" command */
9275           cps->sendTime = 0;
9276           return;
9277         }
9278         if (StrStr(message, "analyze")) {
9279             cps->analysisSupport = FALSE;
9280             cps->analyzing = FALSE;
9281 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9282             EditGameEvent(); // [HGM] try to preserve loaded game
9283             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9284             DisplayError(buf2, 0);
9285             return;
9286         }
9287         if (StrStr(message, "(no matching move)st")) {
9288           /* Special kludge for GNU Chess 4 only */
9289           cps->stKludge = TRUE;
9290           SendTimeControl(cps, movesPerSession, timeControl,
9291                           timeIncrement, appData.searchDepth,
9292                           searchTime);
9293           return;
9294         }
9295         if (StrStr(message, "(no matching move)sd")) {
9296           /* Special kludge for GNU Chess 4 only */
9297           cps->sdKludge = TRUE;
9298           SendTimeControl(cps, movesPerSession, timeControl,
9299                           timeIncrement, appData.searchDepth,
9300                           searchTime);
9301           return;
9302         }
9303         if (!StrStr(message, "llegal")) {
9304             return;
9305         }
9306         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9307             gameMode == IcsIdle) return;
9308         if (forwardMostMove <= backwardMostMove) return;
9309         if (pausing) PauseEvent();
9310       if(appData.forceIllegal) {
9311             // [HGM] illegal: machine refused move; force position after move into it
9312           SendToProgram("force\n", cps);
9313           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9314                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9315                 // when black is to move, while there might be nothing on a2 or black
9316                 // might already have the move. So send the board as if white has the move.
9317                 // But first we must change the stm of the engine, as it refused the last move
9318                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9319                 if(WhiteOnMove(forwardMostMove)) {
9320                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9321                     SendBoard(cps, forwardMostMove); // kludgeless board
9322                 } else {
9323                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9324                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9325                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9326                 }
9327           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9328             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9329                  gameMode == TwoMachinesPlay)
9330               SendToProgram("go\n", cps);
9331             return;
9332       } else
9333         if (gameMode == PlayFromGameFile) {
9334             /* Stop reading this game file */
9335             gameMode = EditGame;
9336             ModeHighlight();
9337         }
9338         /* [HGM] illegal-move claim should forfeit game when Xboard */
9339         /* only passes fully legal moves                            */
9340         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9341             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9342                                 "False illegal-move claim", GE_XBOARD );
9343             return; // do not take back move we tested as valid
9344         }
9345         currentMove = forwardMostMove-1;
9346         DisplayMove(currentMove-1); /* before DisplayMoveError */
9347         SwitchClocks(forwardMostMove-1); // [HGM] race
9348         DisplayBothClocks();
9349         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9350                 parseList[currentMove], _(cps->which));
9351         DisplayMoveError(buf1);
9352         DrawPosition(FALSE, boards[currentMove]);
9353
9354         SetUserThinkingEnables();
9355         return;
9356     }
9357     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9358         /* Program has a broken "time" command that
9359            outputs a string not ending in newline.
9360            Don't use it. */
9361         cps->sendTime = 0;
9362     }
9363     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9364         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9365             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9366     }
9367
9368     /*
9369      * If chess program startup fails, exit with an error message.
9370      * Attempts to recover here are futile. [HGM] Well, we try anyway
9371      */
9372     if ((StrStr(message, "unknown host") != NULL)
9373         || (StrStr(message, "No remote directory") != NULL)
9374         || (StrStr(message, "not found") != NULL)
9375         || (StrStr(message, "No such file") != NULL)
9376         || (StrStr(message, "can't alloc") != NULL)
9377         || (StrStr(message, "Permission denied") != NULL)) {
9378
9379         cps->maybeThinking = FALSE;
9380         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9381                 _(cps->which), cps->program, cps->host, message);
9382         RemoveInputSource(cps->isr);
9383         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9384             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9385             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9386         }
9387         return;
9388     }
9389
9390     /*
9391      * Look for hint output
9392      */
9393     if (sscanf(message, "Hint: %s", buf1) == 1) {
9394         if (cps == &first && hintRequested) {
9395             hintRequested = FALSE;
9396             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9397                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9398                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9399                                     PosFlags(forwardMostMove),
9400                                     fromY, fromX, toY, toX, promoChar, buf1);
9401                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9402                 DisplayInformation(buf2);
9403             } else {
9404                 /* Hint move could not be parsed!? */
9405               snprintf(buf2, sizeof(buf2),
9406                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9407                         buf1, _(cps->which));
9408                 DisplayError(buf2, 0);
9409             }
9410         } else {
9411           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9412         }
9413         return;
9414     }
9415
9416     /*
9417      * Ignore other messages if game is not in progress
9418      */
9419     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9420         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9421
9422     /*
9423      * look for win, lose, draw, or draw offer
9424      */
9425     if (strncmp(message, "1-0", 3) == 0) {
9426         char *p, *q, *r = "";
9427         p = strchr(message, '{');
9428         if (p) {
9429             q = strchr(p, '}');
9430             if (q) {
9431                 *q = NULLCHAR;
9432                 r = p + 1;
9433             }
9434         }
9435         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9436         return;
9437     } else if (strncmp(message, "0-1", 3) == 0) {
9438         char *p, *q, *r = "";
9439         p = strchr(message, '{');
9440         if (p) {
9441             q = strchr(p, '}');
9442             if (q) {
9443                 *q = NULLCHAR;
9444                 r = p + 1;
9445             }
9446         }
9447         /* Kludge for Arasan 4.1 bug */
9448         if (strcmp(r, "Black resigns") == 0) {
9449             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9450             return;
9451         }
9452         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9453         return;
9454     } else if (strncmp(message, "1/2", 3) == 0) {
9455         char *p, *q, *r = "";
9456         p = strchr(message, '{');
9457         if (p) {
9458             q = strchr(p, '}');
9459             if (q) {
9460                 *q = NULLCHAR;
9461                 r = p + 1;
9462             }
9463         }
9464
9465         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9466         return;
9467
9468     } else if (strncmp(message, "White resign", 12) == 0) {
9469         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9470         return;
9471     } else if (strncmp(message, "Black resign", 12) == 0) {
9472         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9473         return;
9474     } else if (strncmp(message, "White matches", 13) == 0 ||
9475                strncmp(message, "Black matches", 13) == 0   ) {
9476         /* [HGM] ignore GNUShogi noises */
9477         return;
9478     } else if (strncmp(message, "White", 5) == 0 &&
9479                message[5] != '(' &&
9480                StrStr(message, "Black") == NULL) {
9481         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9482         return;
9483     } else if (strncmp(message, "Black", 5) == 0 &&
9484                message[5] != '(') {
9485         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9486         return;
9487     } else if (strcmp(message, "resign") == 0 ||
9488                strcmp(message, "computer resigns") == 0) {
9489         switch (gameMode) {
9490           case MachinePlaysBlack:
9491           case IcsPlayingBlack:
9492             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9493             break;
9494           case MachinePlaysWhite:
9495           case IcsPlayingWhite:
9496             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9497             break;
9498           case TwoMachinesPlay:
9499             if (cps->twoMachinesColor[0] == 'w')
9500               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9501             else
9502               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9503             break;
9504           default:
9505             /* can't happen */
9506             break;
9507         }
9508         return;
9509     } else if (strncmp(message, "opponent mates", 14) == 0) {
9510         switch (gameMode) {
9511           case MachinePlaysBlack:
9512           case IcsPlayingBlack:
9513             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9514             break;
9515           case MachinePlaysWhite:
9516           case IcsPlayingWhite:
9517             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9518             break;
9519           case TwoMachinesPlay:
9520             if (cps->twoMachinesColor[0] == 'w')
9521               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9522             else
9523               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9524             break;
9525           default:
9526             /* can't happen */
9527             break;
9528         }
9529         return;
9530     } else if (strncmp(message, "computer mates", 14) == 0) {
9531         switch (gameMode) {
9532           case MachinePlaysBlack:
9533           case IcsPlayingBlack:
9534             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9535             break;
9536           case MachinePlaysWhite:
9537           case IcsPlayingWhite:
9538             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9539             break;
9540           case TwoMachinesPlay:
9541             if (cps->twoMachinesColor[0] == 'w')
9542               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9543             else
9544               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9545             break;
9546           default:
9547             /* can't happen */
9548             break;
9549         }
9550         return;
9551     } else if (strncmp(message, "checkmate", 9) == 0) {
9552         if (WhiteOnMove(forwardMostMove)) {
9553             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9554         } else {
9555             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9556         }
9557         return;
9558     } else if (strstr(message, "Draw") != NULL ||
9559                strstr(message, "game is a draw") != NULL) {
9560         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9561         return;
9562     } else if (strstr(message, "offer") != NULL &&
9563                strstr(message, "draw") != NULL) {
9564 #if ZIPPY
9565         if (appData.zippyPlay && first.initDone) {
9566             /* Relay offer to ICS */
9567             SendToICS(ics_prefix);
9568             SendToICS("draw\n");
9569         }
9570 #endif
9571         cps->offeredDraw = 2; /* valid until this engine moves twice */
9572         if (gameMode == TwoMachinesPlay) {
9573             if (cps->other->offeredDraw) {
9574                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9575             /* [HGM] in two-machine mode we delay relaying draw offer      */
9576             /* until after we also have move, to see if it is really claim */
9577             }
9578         } else if (gameMode == MachinePlaysWhite ||
9579                    gameMode == MachinePlaysBlack) {
9580           if (userOfferedDraw) {
9581             DisplayInformation(_("Machine accepts your draw offer"));
9582             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9583           } else {
9584             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9585           }
9586         }
9587     }
9588
9589
9590     /*
9591      * Look for thinking output
9592      */
9593     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9594           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9595                                 ) {
9596         int plylev, mvleft, mvtot, curscore, time;
9597         char mvname[MOVE_LEN];
9598         u64 nodes; // [DM]
9599         char plyext;
9600         int ignore = FALSE;
9601         int prefixHint = FALSE;
9602         mvname[0] = NULLCHAR;
9603
9604         switch (gameMode) {
9605           case MachinePlaysBlack:
9606           case IcsPlayingBlack:
9607             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9608             break;
9609           case MachinePlaysWhite:
9610           case IcsPlayingWhite:
9611             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9612             break;
9613           case AnalyzeMode:
9614           case AnalyzeFile:
9615             break;
9616           case IcsObserving: /* [DM] icsEngineAnalyze */
9617             if (!appData.icsEngineAnalyze) ignore = TRUE;
9618             break;
9619           case TwoMachinesPlay:
9620             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9621                 ignore = TRUE;
9622             }
9623             break;
9624           default:
9625             ignore = TRUE;
9626             break;
9627         }
9628
9629         if (!ignore) {
9630             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9631             buf1[0] = NULLCHAR;
9632             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9633                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9634                 char score_buf[MSG_SIZ];
9635
9636                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9637                     nodes += u64Const(0x100000000);
9638
9639                 if (plyext != ' ' && plyext != '\t') {
9640                     time *= 100;
9641                 }
9642
9643                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9644                 if( cps->scoreIsAbsolute &&
9645                     ( gameMode == MachinePlaysBlack ||
9646                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9647                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9648                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9649                      !WhiteOnMove(currentMove)
9650                     ) )
9651                 {
9652                     curscore = -curscore;
9653                 }
9654
9655                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9656
9657                 if(*bestMove) { // rememer time best EPD move was first found
9658                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9659                     ChessMove mt;
9660                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9661                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9662                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9663                 }
9664
9665                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9666                         char buf[MSG_SIZ];
9667                         FILE *f;
9668                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9669                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9670                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9671                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9672                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9673                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9674                                 fclose(f);
9675                         }
9676                         else
9677                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9678                           DisplayError(_("failed writing PV"), 0);
9679                 }
9680
9681                 tempStats.depth = plylev;
9682                 tempStats.nodes = nodes;
9683                 tempStats.time = time;
9684                 tempStats.score = curscore;
9685                 tempStats.got_only_move = 0;
9686
9687                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9688                         int ticklen;
9689
9690                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9691                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9692                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9693                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9694                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9695                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9696                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9697                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9698                 }
9699
9700                 /* Buffer overflow protection */
9701                 if (pv[0] != NULLCHAR) {
9702                     if (strlen(pv) >= sizeof(tempStats.movelist)
9703                         && appData.debugMode) {
9704                         fprintf(debugFP,
9705                                 "PV is too long; using the first %u bytes.\n",
9706                                 (unsigned) sizeof(tempStats.movelist) - 1);
9707                     }
9708
9709                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9710                 } else {
9711                     sprintf(tempStats.movelist, " no PV\n");
9712                 }
9713
9714                 if (tempStats.seen_stat) {
9715                     tempStats.ok_to_send = 1;
9716                 }
9717
9718                 if (strchr(tempStats.movelist, '(') != NULL) {
9719                     tempStats.line_is_book = 1;
9720                     tempStats.nr_moves = 0;
9721                     tempStats.moves_left = 0;
9722                 } else {
9723                     tempStats.line_is_book = 0;
9724                 }
9725
9726                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9727                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9728
9729                 SendProgramStatsToFrontend( cps, &tempStats );
9730
9731                 /*
9732                     [AS] Protect the thinkOutput buffer from overflow... this
9733                     is only useful if buf1 hasn't overflowed first!
9734                 */
9735                 if(curscore >= MATE_SCORE) 
9736                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9737                 else if(curscore <= -MATE_SCORE) 
9738                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9739                 else
9740                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9741                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9742                          plylev,
9743                          (gameMode == TwoMachinesPlay ?
9744                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9745                          score_buf,
9746                          prefixHint ? lastHint : "",
9747                          prefixHint ? " " : "" );
9748
9749                 if( buf1[0] != NULLCHAR ) {
9750                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9751
9752                     if( strlen(pv) > max_len ) {
9753                         if( appData.debugMode) {
9754                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9755                         }
9756                         pv[max_len+1] = '\0';
9757                     }
9758
9759                     strcat( thinkOutput, pv);
9760                 }
9761
9762                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9763                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9764                     DisplayMove(currentMove - 1);
9765                 }
9766                 return;
9767
9768             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9769                 /* crafty (9.25+) says "(only move) <move>"
9770                  * if there is only 1 legal move
9771                  */
9772                 sscanf(p, "(only move) %s", buf1);
9773                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9774                 sprintf(programStats.movelist, "%s (only move)", buf1);
9775                 programStats.depth = 1;
9776                 programStats.nr_moves = 1;
9777                 programStats.moves_left = 1;
9778                 programStats.nodes = 1;
9779                 programStats.time = 1;
9780                 programStats.got_only_move = 1;
9781
9782                 /* Not really, but we also use this member to
9783                    mean "line isn't going to change" (Crafty
9784                    isn't searching, so stats won't change) */
9785                 programStats.line_is_book = 1;
9786
9787                 SendProgramStatsToFrontend( cps, &programStats );
9788
9789                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9790                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9791                     DisplayMove(currentMove - 1);
9792                 }
9793                 return;
9794             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9795                               &time, &nodes, &plylev, &mvleft,
9796                               &mvtot, mvname) >= 5) {
9797                 /* The stat01: line is from Crafty (9.29+) in response
9798                    to the "." command */
9799                 programStats.seen_stat = 1;
9800                 cps->maybeThinking = TRUE;
9801
9802                 if (programStats.got_only_move || !appData.periodicUpdates)
9803                   return;
9804
9805                 programStats.depth = plylev;
9806                 programStats.time = time;
9807                 programStats.nodes = nodes;
9808                 programStats.moves_left = mvleft;
9809                 programStats.nr_moves = mvtot;
9810                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9811                 programStats.ok_to_send = 1;
9812                 programStats.movelist[0] = '\0';
9813
9814                 SendProgramStatsToFrontend( cps, &programStats );
9815
9816                 return;
9817
9818             } else if (strncmp(message,"++",2) == 0) {
9819                 /* Crafty 9.29+ outputs this */
9820                 programStats.got_fail = 2;
9821                 return;
9822
9823             } else if (strncmp(message,"--",2) == 0) {
9824                 /* Crafty 9.29+ outputs this */
9825                 programStats.got_fail = 1;
9826                 return;
9827
9828             } else if (thinkOutput[0] != NULLCHAR &&
9829                        strncmp(message, "    ", 4) == 0) {
9830                 unsigned message_len;
9831
9832                 p = message;
9833                 while (*p && *p == ' ') p++;
9834
9835                 message_len = strlen( p );
9836
9837                 /* [AS] Avoid buffer overflow */
9838                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9839                     strcat(thinkOutput, " ");
9840                     strcat(thinkOutput, p);
9841                 }
9842
9843                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9844                     strcat(programStats.movelist, " ");
9845                     strcat(programStats.movelist, p);
9846                 }
9847
9848                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9849                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9850                     DisplayMove(currentMove - 1);
9851                 }
9852                 return;
9853             }
9854         }
9855         else {
9856             buf1[0] = NULLCHAR;
9857
9858             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9859                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9860             {
9861                 ChessProgramStats cpstats;
9862
9863                 if (plyext != ' ' && plyext != '\t') {
9864                     time *= 100;
9865                 }
9866
9867                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9868                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9869                     curscore = -curscore;
9870                 }
9871
9872                 cpstats.depth = plylev;
9873                 cpstats.nodes = nodes;
9874                 cpstats.time = time;
9875                 cpstats.score = curscore;
9876                 cpstats.got_only_move = 0;
9877                 cpstats.movelist[0] = '\0';
9878
9879                 if (buf1[0] != NULLCHAR) {
9880                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9881                 }
9882
9883                 cpstats.ok_to_send = 0;
9884                 cpstats.line_is_book = 0;
9885                 cpstats.nr_moves = 0;
9886                 cpstats.moves_left = 0;
9887
9888                 SendProgramStatsToFrontend( cps, &cpstats );
9889             }
9890         }
9891     }
9892 }
9893
9894
9895 /* Parse a game score from the character string "game", and
9896    record it as the history of the current game.  The game
9897    score is NOT assumed to start from the standard position.
9898    The display is not updated in any way.
9899    */
9900 void
9901 ParseGameHistory (char *game)
9902 {
9903     ChessMove moveType;
9904     int fromX, fromY, toX, toY, boardIndex;
9905     char promoChar;
9906     char *p, *q;
9907     char buf[MSG_SIZ];
9908
9909     if (appData.debugMode)
9910       fprintf(debugFP, "Parsing game history: %s\n", game);
9911
9912     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9913     gameInfo.site = StrSave(appData.icsHost);
9914     gameInfo.date = PGNDate();
9915     gameInfo.round = StrSave("-");
9916
9917     /* Parse out names of players */
9918     while (*game == ' ') game++;
9919     p = buf;
9920     while (*game != ' ') *p++ = *game++;
9921     *p = NULLCHAR;
9922     gameInfo.white = StrSave(buf);
9923     while (*game == ' ') game++;
9924     p = buf;
9925     while (*game != ' ' && *game != '\n') *p++ = *game++;
9926     *p = NULLCHAR;
9927     gameInfo.black = StrSave(buf);
9928
9929     /* Parse moves */
9930     boardIndex = blackPlaysFirst ? 1 : 0;
9931     yynewstr(game);
9932     for (;;) {
9933         yyboardindex = boardIndex;
9934         moveType = (ChessMove) Myylex();
9935         switch (moveType) {
9936           case IllegalMove:             /* maybe suicide chess, etc. */
9937   if (appData.debugMode) {
9938     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9939     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9940     setbuf(debugFP, NULL);
9941   }
9942           case WhitePromotion:
9943           case BlackPromotion:
9944           case WhiteNonPromotion:
9945           case BlackNonPromotion:
9946           case NormalMove:
9947           case FirstLeg:
9948           case WhiteCapturesEnPassant:
9949           case BlackCapturesEnPassant:
9950           case WhiteKingSideCastle:
9951           case WhiteQueenSideCastle:
9952           case BlackKingSideCastle:
9953           case BlackQueenSideCastle:
9954           case WhiteKingSideCastleWild:
9955           case WhiteQueenSideCastleWild:
9956           case BlackKingSideCastleWild:
9957           case BlackQueenSideCastleWild:
9958           /* PUSH Fabien */
9959           case WhiteHSideCastleFR:
9960           case WhiteASideCastleFR:
9961           case BlackHSideCastleFR:
9962           case BlackASideCastleFR:
9963           /* POP Fabien */
9964             fromX = currentMoveString[0] - AAA;
9965             fromY = currentMoveString[1] - ONE;
9966             toX = currentMoveString[2] - AAA;
9967             toY = currentMoveString[3] - ONE;
9968             promoChar = currentMoveString[4];
9969             break;
9970           case WhiteDrop:
9971           case BlackDrop:
9972             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9973             fromX = moveType == WhiteDrop ?
9974               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9975             (int) CharToPiece(ToLower(currentMoveString[0]));
9976             fromY = DROP_RANK;
9977             toX = currentMoveString[2] - AAA;
9978             toY = currentMoveString[3] - ONE;
9979             promoChar = NULLCHAR;
9980             break;
9981           case AmbiguousMove:
9982             /* bug? */
9983             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9984   if (appData.debugMode) {
9985     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9986     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9987     setbuf(debugFP, NULL);
9988   }
9989             DisplayError(buf, 0);
9990             return;
9991           case ImpossibleMove:
9992             /* bug? */
9993             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9994   if (appData.debugMode) {
9995     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9996     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9997     setbuf(debugFP, NULL);
9998   }
9999             DisplayError(buf, 0);
10000             return;
10001           case EndOfFile:
10002             if (boardIndex < backwardMostMove) {
10003                 /* Oops, gap.  How did that happen? */
10004                 DisplayError(_("Gap in move list"), 0);
10005                 return;
10006             }
10007             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10008             if (boardIndex > forwardMostMove) {
10009                 forwardMostMove = boardIndex;
10010             }
10011             return;
10012           case ElapsedTime:
10013             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10014                 strcat(parseList[boardIndex-1], " ");
10015                 strcat(parseList[boardIndex-1], yy_text);
10016             }
10017             continue;
10018           case Comment:
10019           case PGNTag:
10020           case NAG:
10021           default:
10022             /* ignore */
10023             continue;
10024           case WhiteWins:
10025           case BlackWins:
10026           case GameIsDrawn:
10027           case GameUnfinished:
10028             if (gameMode == IcsExamining) {
10029                 if (boardIndex < backwardMostMove) {
10030                     /* Oops, gap.  How did that happen? */
10031                     return;
10032                 }
10033                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10034                 return;
10035             }
10036             gameInfo.result = moveType;
10037             p = strchr(yy_text, '{');
10038             if (p == NULL) p = strchr(yy_text, '(');
10039             if (p == NULL) {
10040                 p = yy_text;
10041                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10042             } else {
10043                 q = strchr(p, *p == '{' ? '}' : ')');
10044                 if (q != NULL) *q = NULLCHAR;
10045                 p++;
10046             }
10047             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10048             gameInfo.resultDetails = StrSave(p);
10049             continue;
10050         }
10051         if (boardIndex >= forwardMostMove &&
10052             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10053             backwardMostMove = blackPlaysFirst ? 1 : 0;
10054             return;
10055         }
10056         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10057                                  fromY, fromX, toY, toX, promoChar,
10058                                  parseList[boardIndex]);
10059         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10060         /* currentMoveString is set as a side-effect of yylex */
10061         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10062         strcat(moveList[boardIndex], "\n");
10063         boardIndex++;
10064         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10065         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10066           case MT_NONE:
10067           case MT_STALEMATE:
10068           default:
10069             break;
10070           case MT_CHECK:
10071             if(!IS_SHOGI(gameInfo.variant))
10072                 strcat(parseList[boardIndex - 1], "+");
10073             break;
10074           case MT_CHECKMATE:
10075           case MT_STAINMATE:
10076             strcat(parseList[boardIndex - 1], "#");
10077             break;
10078         }
10079     }
10080 }
10081
10082
10083 /* Apply a move to the given board  */
10084 void
10085 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10086 {
10087   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10088   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10089
10090     /* [HGM] compute & store e.p. status and castling rights for new position */
10091     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10092
10093       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10094       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10095       board[EP_STATUS] = EP_NONE;
10096       board[EP_FILE] = board[EP_RANK] = 100;
10097
10098   if (fromY == DROP_RANK) {
10099         /* must be first */
10100         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10101             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10102             return;
10103         }
10104         piece = board[toY][toX] = (ChessSquare) fromX;
10105   } else {
10106 //      ChessSquare victim;
10107       int i;
10108
10109       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10110 //           victim = board[killY][killX],
10111            killed = board[killY][killX],
10112            board[killY][killX] = EmptySquare,
10113            board[EP_STATUS] = EP_CAPTURE;
10114            if( kill2X >= 0 && kill2Y >= 0)
10115              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10116       }
10117
10118       if( board[toY][toX] != EmptySquare ) {
10119            board[EP_STATUS] = EP_CAPTURE;
10120            if( (fromX != toX || fromY != toY) && // not igui!
10121                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10122                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10123                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10124            }
10125       }
10126
10127       pawn = board[fromY][fromX];
10128       if( pawn == WhiteLance || pawn == BlackLance ) {
10129            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10130                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10131                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10132            }
10133       }
10134       if( pawn == WhitePawn ) {
10135            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10136                board[EP_STATUS] = EP_PAWN_MOVE;
10137            if( toY-fromY>=2) {
10138                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10139                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10140                         gameInfo.variant != VariantBerolina || toX < fromX)
10141                       board[EP_STATUS] = toX | berolina;
10142                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10143                         gameInfo.variant != VariantBerolina || toX > fromX)
10144                       board[EP_STATUS] = toX;
10145            }
10146       } else
10147       if( pawn == BlackPawn ) {
10148            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10149                board[EP_STATUS] = EP_PAWN_MOVE;
10150            if( toY-fromY<= -2) {
10151                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10152                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10153                         gameInfo.variant != VariantBerolina || toX < fromX)
10154                       board[EP_STATUS] = toX | berolina;
10155                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10156                         gameInfo.variant != VariantBerolina || toX > fromX)
10157                       board[EP_STATUS] = toX;
10158            }
10159        }
10160
10161        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10162        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10163        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10164        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10165
10166        for(i=0; i<nrCastlingRights; i++) {
10167            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10168               board[CASTLING][i] == toX   && castlingRank[i] == toY
10169              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10170        }
10171
10172        if(gameInfo.variant == VariantSChess) { // update virginity
10173            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10174            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10175            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10176            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10177        }
10178
10179      if (fromX == toX && fromY == toY) return;
10180
10181      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10182      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10183      if(gameInfo.variant == VariantKnightmate)
10184          king += (int) WhiteUnicorn - (int) WhiteKing;
10185
10186     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10187        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10188         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10189         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10190         board[EP_STATUS] = EP_NONE; // capture was fake!
10191     } else
10192     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10193         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10194         board[toY][toX] = piece;
10195         board[EP_STATUS] = EP_NONE; // capture was fake!
10196     } else
10197     /* Code added by Tord: */
10198     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10199     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10200         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10201       board[EP_STATUS] = EP_NONE; // capture was fake!
10202       board[fromY][fromX] = EmptySquare;
10203       board[toY][toX] = EmptySquare;
10204       if((toX > fromX) != (piece == WhiteRook)) {
10205         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10206       } else {
10207         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10208       }
10209     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10210                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10211       board[EP_STATUS] = EP_NONE;
10212       board[fromY][fromX] = EmptySquare;
10213       board[toY][toX] = EmptySquare;
10214       if((toX > fromX) != (piece == BlackRook)) {
10215         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10216       } else {
10217         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10218       }
10219     /* End of code added by Tord */
10220
10221     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10222         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10223         board[toY][toX] = piece;
10224     } else if (board[fromY][fromX] == king
10225         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10226         && toY == fromY && toX > fromX+1) {
10227         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10228         board[fromY][toX-1] = board[fromY][rookX];
10229         board[fromY][rookX] = EmptySquare;
10230         board[fromY][fromX] = EmptySquare;
10231         board[toY][toX] = king;
10232     } else if (board[fromY][fromX] == king
10233         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10234                && toY == fromY && toX < fromX-1) {
10235         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10236         board[fromY][toX+1] = board[fromY][rookX];
10237         board[fromY][rookX] = EmptySquare;
10238         board[fromY][fromX] = EmptySquare;
10239         board[toY][toX] = king;
10240     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10241                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10242                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10243                ) {
10244         /* white pawn promotion */
10245         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10246         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10247             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10248         board[fromY][fromX] = EmptySquare;
10249     } else if ((fromY >= BOARD_HEIGHT>>1)
10250                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10251                && (toX != fromX)
10252                && gameInfo.variant != VariantXiangqi
10253                && gameInfo.variant != VariantBerolina
10254                && (pawn == WhitePawn)
10255                && (board[toY][toX] == EmptySquare)) {
10256         board[fromY][fromX] = EmptySquare;
10257         board[toY][toX] = piece;
10258         if(toY == epRank - 128 + 1)
10259             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10260         else
10261             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10262     } else if ((fromY == BOARD_HEIGHT-4)
10263                && (toX == fromX)
10264                && gameInfo.variant == VariantBerolina
10265                && (board[fromY][fromX] == WhitePawn)
10266                && (board[toY][toX] == EmptySquare)) {
10267         board[fromY][fromX] = EmptySquare;
10268         board[toY][toX] = WhitePawn;
10269         if(oldEP & EP_BEROLIN_A) {
10270                 captured = board[fromY][fromX-1];
10271                 board[fromY][fromX-1] = EmptySquare;
10272         }else{  captured = board[fromY][fromX+1];
10273                 board[fromY][fromX+1] = EmptySquare;
10274         }
10275     } else if (board[fromY][fromX] == king
10276         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10277                && toY == fromY && toX > fromX+1) {
10278         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10279         board[fromY][toX-1] = board[fromY][rookX];
10280         board[fromY][rookX] = EmptySquare;
10281         board[fromY][fromX] = EmptySquare;
10282         board[toY][toX] = king;
10283     } else if (board[fromY][fromX] == king
10284         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10285                && toY == fromY && toX < fromX-1) {
10286         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10287         board[fromY][toX+1] = board[fromY][rookX];
10288         board[fromY][rookX] = EmptySquare;
10289         board[fromY][fromX] = EmptySquare;
10290         board[toY][toX] = king;
10291     } else if (fromY == 7 && fromX == 3
10292                && board[fromY][fromX] == BlackKing
10293                && toY == 7 && toX == 5) {
10294         board[fromY][fromX] = EmptySquare;
10295         board[toY][toX] = BlackKing;
10296         board[fromY][7] = EmptySquare;
10297         board[toY][4] = BlackRook;
10298     } else if (fromY == 7 && fromX == 3
10299                && board[fromY][fromX] == BlackKing
10300                && toY == 7 && toX == 1) {
10301         board[fromY][fromX] = EmptySquare;
10302         board[toY][toX] = BlackKing;
10303         board[fromY][0] = EmptySquare;
10304         board[toY][2] = BlackRook;
10305     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10306                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10307                && toY < promoRank && promoChar
10308                ) {
10309         /* black pawn promotion */
10310         board[toY][toX] = CharToPiece(ToLower(promoChar));
10311         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10312             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10313         board[fromY][fromX] = EmptySquare;
10314     } else if ((fromY < BOARD_HEIGHT>>1)
10315                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10316                && (toX != fromX)
10317                && gameInfo.variant != VariantXiangqi
10318                && gameInfo.variant != VariantBerolina
10319                && (pawn == BlackPawn)
10320                && (board[toY][toX] == EmptySquare)) {
10321         board[fromY][fromX] = EmptySquare;
10322         board[toY][toX] = piece;
10323         if(toY == epRank - 128 - 1)
10324             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10325         else
10326             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10327     } else if ((fromY == 3)
10328                && (toX == fromX)
10329                && gameInfo.variant == VariantBerolina
10330                && (board[fromY][fromX] == BlackPawn)
10331                && (board[toY][toX] == EmptySquare)) {
10332         board[fromY][fromX] = EmptySquare;
10333         board[toY][toX] = BlackPawn;
10334         if(oldEP & EP_BEROLIN_A) {
10335                 captured = board[fromY][fromX-1];
10336                 board[fromY][fromX-1] = EmptySquare;
10337         }else{  captured = board[fromY][fromX+1];
10338                 board[fromY][fromX+1] = EmptySquare;
10339         }
10340     } else {
10341         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10342         board[fromY][fromX] = EmptySquare;
10343         board[toY][toX] = piece;
10344     }
10345   }
10346
10347     if (gameInfo.holdingsWidth != 0) {
10348
10349       /* !!A lot more code needs to be written to support holdings  */
10350       /* [HGM] OK, so I have written it. Holdings are stored in the */
10351       /* penultimate board files, so they are automaticlly stored   */
10352       /* in the game history.                                       */
10353       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10354                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10355         /* Delete from holdings, by decreasing count */
10356         /* and erasing image if necessary            */
10357         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10358         if(p < (int) BlackPawn) { /* white drop */
10359              p -= (int)WhitePawn;
10360                  p = PieceToNumber((ChessSquare)p);
10361              if(p >= gameInfo.holdingsSize) p = 0;
10362              if(--board[p][BOARD_WIDTH-2] <= 0)
10363                   board[p][BOARD_WIDTH-1] = EmptySquare;
10364              if((int)board[p][BOARD_WIDTH-2] < 0)
10365                         board[p][BOARD_WIDTH-2] = 0;
10366         } else {                  /* black drop */
10367              p -= (int)BlackPawn;
10368                  p = PieceToNumber((ChessSquare)p);
10369              if(p >= gameInfo.holdingsSize) p = 0;
10370              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10371                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10372              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10373                         board[BOARD_HEIGHT-1-p][1] = 0;
10374         }
10375       }
10376       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10377           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10378         /* [HGM] holdings: Add to holdings, if holdings exist */
10379         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10380                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10381                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10382         }
10383         p = (int) captured;
10384         if (p >= (int) BlackPawn) {
10385           p -= (int)BlackPawn;
10386           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10387                   /* Restore shogi-promoted piece to its original  first */
10388                   captured = (ChessSquare) (DEMOTED(captured));
10389                   p = DEMOTED(p);
10390           }
10391           p = PieceToNumber((ChessSquare)p);
10392           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10393           board[p][BOARD_WIDTH-2]++;
10394           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10395         } else {
10396           p -= (int)WhitePawn;
10397           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10398                   captured = (ChessSquare) (DEMOTED(captured));
10399                   p = DEMOTED(p);
10400           }
10401           p = PieceToNumber((ChessSquare)p);
10402           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10403           board[BOARD_HEIGHT-1-p][1]++;
10404           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10405         }
10406       }
10407     } else if (gameInfo.variant == VariantAtomic) {
10408       if (captured != EmptySquare) {
10409         int y, x;
10410         for (y = toY-1; y <= toY+1; y++) {
10411           for (x = toX-1; x <= toX+1; x++) {
10412             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10413                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10414               board[y][x] = EmptySquare;
10415             }
10416           }
10417         }
10418         board[toY][toX] = EmptySquare;
10419       }
10420     }
10421
10422     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10423         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10424     } else
10425     if(promoChar == '+') {
10426         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10427         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10428         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10429           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10430     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10431         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10432         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10433            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10434         board[toY][toX] = newPiece;
10435     }
10436     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10437                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10438         // [HGM] superchess: take promotion piece out of holdings
10439         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10440         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10441             if(!--board[k][BOARD_WIDTH-2])
10442                 board[k][BOARD_WIDTH-1] = EmptySquare;
10443         } else {
10444             if(!--board[BOARD_HEIGHT-1-k][1])
10445                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10446         }
10447     }
10448 }
10449
10450 /* Updates forwardMostMove */
10451 void
10452 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10453 {
10454     int x = toX, y = toY;
10455     char *s = parseList[forwardMostMove];
10456     ChessSquare p = boards[forwardMostMove][toY][toX];
10457 //    forwardMostMove++; // [HGM] bare: moved downstream
10458
10459     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10460     (void) CoordsToAlgebraic(boards[forwardMostMove],
10461                              PosFlags(forwardMostMove),
10462                              fromY, fromX, y, x, promoChar,
10463                              s);
10464     if(killX >= 0 && killY >= 0)
10465         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10466
10467     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10468         int timeLeft; static int lastLoadFlag=0; int king, piece;
10469         piece = boards[forwardMostMove][fromY][fromX];
10470         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10471         if(gameInfo.variant == VariantKnightmate)
10472             king += (int) WhiteUnicorn - (int) WhiteKing;
10473         if(forwardMostMove == 0) {
10474             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10475                 fprintf(serverMoves, "%s;", UserName());
10476             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10477                 fprintf(serverMoves, "%s;", second.tidy);
10478             fprintf(serverMoves, "%s;", first.tidy);
10479             if(gameMode == MachinePlaysWhite)
10480                 fprintf(serverMoves, "%s;", UserName());
10481             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10482                 fprintf(serverMoves, "%s;", second.tidy);
10483         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10484         lastLoadFlag = loadFlag;
10485         // print base move
10486         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10487         // print castling suffix
10488         if( toY == fromY && piece == king ) {
10489             if(toX-fromX > 1)
10490                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10491             if(fromX-toX >1)
10492                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10493         }
10494         // e.p. suffix
10495         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10496              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10497              boards[forwardMostMove][toY][toX] == EmptySquare
10498              && fromX != toX && fromY != toY)
10499                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10500         // promotion suffix
10501         if(promoChar != NULLCHAR) {
10502             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10503                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10504                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10505             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10506         }
10507         if(!loadFlag) {
10508                 char buf[MOVE_LEN*2], *p; int len;
10509             fprintf(serverMoves, "/%d/%d",
10510                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10511             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10512             else                      timeLeft = blackTimeRemaining/1000;
10513             fprintf(serverMoves, "/%d", timeLeft);
10514                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10515                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10516                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10517                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10518             fprintf(serverMoves, "/%s", buf);
10519         }
10520         fflush(serverMoves);
10521     }
10522
10523     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10524         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10525       return;
10526     }
10527     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10528     if (commentList[forwardMostMove+1] != NULL) {
10529         free(commentList[forwardMostMove+1]);
10530         commentList[forwardMostMove+1] = NULL;
10531     }
10532     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10533     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10534     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10535     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10536     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10537     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10538     adjustedClock = FALSE;
10539     gameInfo.result = GameUnfinished;
10540     if (gameInfo.resultDetails != NULL) {
10541         free(gameInfo.resultDetails);
10542         gameInfo.resultDetails = NULL;
10543     }
10544     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10545                               moveList[forwardMostMove - 1]);
10546     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10547       case MT_NONE:
10548       case MT_STALEMATE:
10549       default:
10550         break;
10551       case MT_CHECK:
10552         if(!IS_SHOGI(gameInfo.variant))
10553             strcat(parseList[forwardMostMove - 1], "+");
10554         break;
10555       case MT_CHECKMATE:
10556       case MT_STAINMATE:
10557         strcat(parseList[forwardMostMove - 1], "#");
10558         break;
10559     }
10560 }
10561
10562 /* Updates currentMove if not pausing */
10563 void
10564 ShowMove (int fromX, int fromY, int toX, int toY)
10565 {
10566     int instant = (gameMode == PlayFromGameFile) ?
10567         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10568     if(appData.noGUI) return;
10569     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10570         if (!instant) {
10571             if (forwardMostMove == currentMove + 1) {
10572                 AnimateMove(boards[forwardMostMove - 1],
10573                             fromX, fromY, toX, toY);
10574             }
10575         }
10576         currentMove = forwardMostMove;
10577     }
10578
10579     killX = killY = -1; // [HGM] lion: used up
10580
10581     if (instant) return;
10582
10583     DisplayMove(currentMove - 1);
10584     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10585             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10586                 SetHighlights(fromX, fromY, toX, toY);
10587             }
10588     }
10589     DrawPosition(FALSE, boards[currentMove]);
10590     DisplayBothClocks();
10591     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10592 }
10593
10594 void
10595 SendEgtPath (ChessProgramState *cps)
10596 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10597         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10598
10599         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10600
10601         while(*p) {
10602             char c, *q = name+1, *r, *s;
10603
10604             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10605             while(*p && *p != ',') *q++ = *p++;
10606             *q++ = ':'; *q = 0;
10607             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10608                 strcmp(name, ",nalimov:") == 0 ) {
10609                 // take nalimov path from the menu-changeable option first, if it is defined
10610               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10611                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10612             } else
10613             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10614                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10615                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10616                 s = r = StrStr(s, ":") + 1; // beginning of path info
10617                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10618                 c = *r; *r = 0;             // temporarily null-terminate path info
10619                     *--q = 0;               // strip of trailig ':' from name
10620                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10621                 *r = c;
10622                 SendToProgram(buf,cps);     // send egtbpath command for this format
10623             }
10624             if(*p == ',') p++; // read away comma to position for next format name
10625         }
10626 }
10627
10628 static int
10629 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10630 {
10631       int width = 8, height = 8, holdings = 0;             // most common sizes
10632       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10633       // correct the deviations default for each variant
10634       if( v == VariantXiangqi ) width = 9,  height = 10;
10635       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10636       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10637       if( v == VariantCapablanca || v == VariantCapaRandom ||
10638           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10639                                 width = 10;
10640       if( v == VariantCourier ) width = 12;
10641       if( v == VariantSuper )                            holdings = 8;
10642       if( v == VariantGreat )   width = 10,              holdings = 8;
10643       if( v == VariantSChess )                           holdings = 7;
10644       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10645       if( v == VariantChuChess) width = 10, height = 10;
10646       if( v == VariantChu )     width = 12, height = 12;
10647       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10648              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10649              holdingsSize >= 0 && holdingsSize != holdings;
10650 }
10651
10652 char variantError[MSG_SIZ];
10653
10654 char *
10655 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10656 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10657       char *p, *variant = VariantName(v);
10658       static char b[MSG_SIZ];
10659       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10660            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10661                                                holdingsSize, variant); // cook up sized variant name
10662            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10663            if(StrStr(list, b) == NULL) {
10664                // specific sized variant not known, check if general sizing allowed
10665                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10666                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10667                             boardWidth, boardHeight, holdingsSize, engine);
10668                    return NULL;
10669                }
10670                /* [HGM] here we really should compare with the maximum supported board size */
10671            }
10672       } else snprintf(b, MSG_SIZ,"%s", variant);
10673       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10674       p = StrStr(list, b);
10675       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10676       if(p == NULL) {
10677           // occurs not at all in list, or only as sub-string
10678           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10679           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10680               int l = strlen(variantError);
10681               char *q;
10682               while(p != list && p[-1] != ',') p--;
10683               q = strchr(p, ',');
10684               if(q) *q = NULLCHAR;
10685               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10686               if(q) *q= ',';
10687           }
10688           return NULL;
10689       }
10690       return b;
10691 }
10692
10693 void
10694 InitChessProgram (ChessProgramState *cps, int setup)
10695 /* setup needed to setup FRC opening position */
10696 {
10697     char buf[MSG_SIZ], *b;
10698     if (appData.noChessProgram) return;
10699     hintRequested = FALSE;
10700     bookRequested = FALSE;
10701
10702     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10703     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10704     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10705     if(cps->memSize) { /* [HGM] memory */
10706       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10707         SendToProgram(buf, cps);
10708     }
10709     SendEgtPath(cps); /* [HGM] EGT */
10710     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10711       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10712         SendToProgram(buf, cps);
10713     }
10714
10715     setboardSpoiledMachineBlack = FALSE;
10716     SendToProgram(cps->initString, cps);
10717     if (gameInfo.variant != VariantNormal &&
10718         gameInfo.variant != VariantLoadable
10719         /* [HGM] also send variant if board size non-standard */
10720         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10721
10722       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10723                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10724       if (b == NULL) {
10725         VariantClass v;
10726         char c, *q = cps->variants, *p = strchr(q, ',');
10727         if(p) *p = NULLCHAR;
10728         v = StringToVariant(q);
10729         DisplayError(variantError, 0);
10730         if(v != VariantUnknown && cps == &first) {
10731             int w, h, s;
10732             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10733                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10734             ASSIGN(appData.variant, q);
10735             Reset(TRUE, FALSE);
10736         }
10737         if(p) *p = ',';
10738         return;
10739       }
10740
10741       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10742       SendToProgram(buf, cps);
10743     }
10744     currentlyInitializedVariant = gameInfo.variant;
10745
10746     /* [HGM] send opening position in FRC to first engine */
10747     if(setup) {
10748           SendToProgram("force\n", cps);
10749           SendBoard(cps, 0);
10750           /* engine is now in force mode! Set flag to wake it up after first move. */
10751           setboardSpoiledMachineBlack = 1;
10752     }
10753
10754     if (cps->sendICS) {
10755       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10756       SendToProgram(buf, cps);
10757     }
10758     cps->maybeThinking = FALSE;
10759     cps->offeredDraw = 0;
10760     if (!appData.icsActive) {
10761         SendTimeControl(cps, movesPerSession, timeControl,
10762                         timeIncrement, appData.searchDepth,
10763                         searchTime);
10764     }
10765     if (appData.showThinking
10766         // [HGM] thinking: four options require thinking output to be sent
10767         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10768                                 ) {
10769         SendToProgram("post\n", cps);
10770     }
10771     SendToProgram("hard\n", cps);
10772     if (!appData.ponderNextMove) {
10773         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10774            it without being sure what state we are in first.  "hard"
10775            is not a toggle, so that one is OK.
10776          */
10777         SendToProgram("easy\n", cps);
10778     }
10779     if (cps->usePing) {
10780       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10781       SendToProgram(buf, cps);
10782     }
10783     cps->initDone = TRUE;
10784     ClearEngineOutputPane(cps == &second);
10785 }
10786
10787
10788 void
10789 ResendOptions (ChessProgramState *cps)
10790 { // send the stored value of the options
10791   int i;
10792   char buf[MSG_SIZ];
10793   Option *opt = cps->option;
10794   for(i=0; i<cps->nrOptions; i++, opt++) {
10795       switch(opt->type) {
10796         case Spin:
10797         case Slider:
10798         case CheckBox:
10799             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10800           break;
10801         case ComboBox:
10802           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10803           break;
10804         default:
10805             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10806           break;
10807         case Button:
10808         case SaveButton:
10809           continue;
10810       }
10811       SendToProgram(buf, cps);
10812   }
10813 }
10814
10815 void
10816 StartChessProgram (ChessProgramState *cps)
10817 {
10818     char buf[MSG_SIZ];
10819     int err;
10820
10821     if (appData.noChessProgram) return;
10822     cps->initDone = FALSE;
10823
10824     if (strcmp(cps->host, "localhost") == 0) {
10825         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10826     } else if (*appData.remoteShell == NULLCHAR) {
10827         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10828     } else {
10829         if (*appData.remoteUser == NULLCHAR) {
10830           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10831                     cps->program);
10832         } else {
10833           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10834                     cps->host, appData.remoteUser, cps->program);
10835         }
10836         err = StartChildProcess(buf, "", &cps->pr);
10837     }
10838
10839     if (err != 0) {
10840       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10841         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10842         if(cps != &first) return;
10843         appData.noChessProgram = TRUE;
10844         ThawUI();
10845         SetNCPMode();
10846 //      DisplayFatalError(buf, err, 1);
10847 //      cps->pr = NoProc;
10848 //      cps->isr = NULL;
10849         return;
10850     }
10851
10852     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10853     if (cps->protocolVersion > 1) {
10854       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10855       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10856         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10857         cps->comboCnt = 0;  //                and values of combo boxes
10858       }
10859       SendToProgram(buf, cps);
10860       if(cps->reload) ResendOptions(cps);
10861     } else {
10862       SendToProgram("xboard\n", cps);
10863     }
10864 }
10865
10866 void
10867 TwoMachinesEventIfReady P((void))
10868 {
10869   static int curMess = 0;
10870   if (first.lastPing != first.lastPong) {
10871     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10872     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10873     return;
10874   }
10875   if (second.lastPing != second.lastPong) {
10876     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10877     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10878     return;
10879   }
10880   DisplayMessage("", ""); curMess = 0;
10881   TwoMachinesEvent();
10882 }
10883
10884 char *
10885 MakeName (char *template)
10886 {
10887     time_t clock;
10888     struct tm *tm;
10889     static char buf[MSG_SIZ];
10890     char *p = buf;
10891     int i;
10892
10893     clock = time((time_t *)NULL);
10894     tm = localtime(&clock);
10895
10896     while(*p++ = *template++) if(p[-1] == '%') {
10897         switch(*template++) {
10898           case 0:   *p = 0; return buf;
10899           case 'Y': i = tm->tm_year+1900; break;
10900           case 'y': i = tm->tm_year-100; break;
10901           case 'M': i = tm->tm_mon+1; break;
10902           case 'd': i = tm->tm_mday; break;
10903           case 'h': i = tm->tm_hour; break;
10904           case 'm': i = tm->tm_min; break;
10905           case 's': i = tm->tm_sec; break;
10906           default:  i = 0;
10907         }
10908         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10909     }
10910     return buf;
10911 }
10912
10913 int
10914 CountPlayers (char *p)
10915 {
10916     int n = 0;
10917     while(p = strchr(p, '\n')) p++, n++; // count participants
10918     return n;
10919 }
10920
10921 FILE *
10922 WriteTourneyFile (char *results, FILE *f)
10923 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10924     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10925     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10926         // create a file with tournament description
10927         fprintf(f, "-participants {%s}\n", appData.participants);
10928         fprintf(f, "-seedBase %d\n", appData.seedBase);
10929         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10930         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10931         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10932         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10933         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10934         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10935         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10936         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10937         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10938         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10939         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10940         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10941         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10942         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10943         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10944         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10945         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10946         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10947         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10948         fprintf(f, "-smpCores %d\n", appData.smpCores);
10949         if(searchTime > 0)
10950                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10951         else {
10952                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10953                 fprintf(f, "-tc %s\n", appData.timeControl);
10954                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10955         }
10956         fprintf(f, "-results \"%s\"\n", results);
10957     }
10958     return f;
10959 }
10960
10961 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10962
10963 void
10964 Substitute (char *participants, int expunge)
10965 {
10966     int i, changed, changes=0, nPlayers=0;
10967     char *p, *q, *r, buf[MSG_SIZ];
10968     if(participants == NULL) return;
10969     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10970     r = p = participants; q = appData.participants;
10971     while(*p && *p == *q) {
10972         if(*p == '\n') r = p+1, nPlayers++;
10973         p++; q++;
10974     }
10975     if(*p) { // difference
10976         while(*p && *p++ != '\n');
10977         while(*q && *q++ != '\n');
10978       changed = nPlayers;
10979         changes = 1 + (strcmp(p, q) != 0);
10980     }
10981     if(changes == 1) { // a single engine mnemonic was changed
10982         q = r; while(*q) nPlayers += (*q++ == '\n');
10983         p = buf; while(*r && (*p = *r++) != '\n') p++;
10984         *p = NULLCHAR;
10985         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10986         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10987         if(mnemonic[i]) { // The substitute is valid
10988             FILE *f;
10989             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10990                 flock(fileno(f), LOCK_EX);
10991                 ParseArgsFromFile(f);
10992                 fseek(f, 0, SEEK_SET);
10993                 FREE(appData.participants); appData.participants = participants;
10994                 if(expunge) { // erase results of replaced engine
10995                     int len = strlen(appData.results), w, b, dummy;
10996                     for(i=0; i<len; i++) {
10997                         Pairing(i, nPlayers, &w, &b, &dummy);
10998                         if((w == changed || b == changed) && appData.results[i] == '*') {
10999                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11000                             fclose(f);
11001                             return;
11002                         }
11003                     }
11004                     for(i=0; i<len; i++) {
11005                         Pairing(i, nPlayers, &w, &b, &dummy);
11006                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11007                     }
11008                 }
11009                 WriteTourneyFile(appData.results, f);
11010                 fclose(f); // release lock
11011                 return;
11012             }
11013         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11014     }
11015     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11016     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11017     free(participants);
11018     return;
11019 }
11020
11021 int
11022 CheckPlayers (char *participants)
11023 {
11024         int i;
11025         char buf[MSG_SIZ], *p;
11026         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11027         while(p = strchr(participants, '\n')) {
11028             *p = NULLCHAR;
11029             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11030             if(!mnemonic[i]) {
11031                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11032                 *p = '\n';
11033                 DisplayError(buf, 0);
11034                 return 1;
11035             }
11036             *p = '\n';
11037             participants = p + 1;
11038         }
11039         return 0;
11040 }
11041
11042 int
11043 CreateTourney (char *name)
11044 {
11045         FILE *f;
11046         if(matchMode && strcmp(name, appData.tourneyFile)) {
11047              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11048         }
11049         if(name[0] == NULLCHAR) {
11050             if(appData.participants[0])
11051                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11052             return 0;
11053         }
11054         f = fopen(name, "r");
11055         if(f) { // file exists
11056             ASSIGN(appData.tourneyFile, name);
11057             ParseArgsFromFile(f); // parse it
11058         } else {
11059             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11060             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11061                 DisplayError(_("Not enough participants"), 0);
11062                 return 0;
11063             }
11064             if(CheckPlayers(appData.participants)) return 0;
11065             ASSIGN(appData.tourneyFile, name);
11066             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11067             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11068         }
11069         fclose(f);
11070         appData.noChessProgram = FALSE;
11071         appData.clockMode = TRUE;
11072         SetGNUMode();
11073         return 1;
11074 }
11075
11076 int
11077 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11078 {
11079     char buf[MSG_SIZ], *p, *q;
11080     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11081     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11082     skip = !all && group[0]; // if group requested, we start in skip mode
11083     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11084         p = names; q = buf; header = 0;
11085         while(*p && *p != '\n') *q++ = *p++;
11086         *q = 0;
11087         if(*p == '\n') p++;
11088         if(buf[0] == '#') {
11089             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11090             depth++; // we must be entering a new group
11091             if(all) continue; // suppress printing group headers when complete list requested
11092             header = 1;
11093             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11094         }
11095         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11096         if(engineList[i]) free(engineList[i]);
11097         engineList[i] = strdup(buf);
11098         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11099         if(engineMnemonic[i]) free(engineMnemonic[i]);
11100         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11101             strcat(buf, " (");
11102             sscanf(q + 8, "%s", buf + strlen(buf));
11103             strcat(buf, ")");
11104         }
11105         engineMnemonic[i] = strdup(buf);
11106         i++;
11107     }
11108     engineList[i] = engineMnemonic[i] = NULL;
11109     return i;
11110 }
11111
11112 // following implemented as macro to avoid type limitations
11113 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11114
11115 void
11116 SwapEngines (int n)
11117 {   // swap settings for first engine and other engine (so far only some selected options)
11118     int h;
11119     char *p;
11120     if(n == 0) return;
11121     SWAP(directory, p)
11122     SWAP(chessProgram, p)
11123     SWAP(isUCI, h)
11124     SWAP(hasOwnBookUCI, h)
11125     SWAP(protocolVersion, h)
11126     SWAP(reuse, h)
11127     SWAP(scoreIsAbsolute, h)
11128     SWAP(timeOdds, h)
11129     SWAP(logo, p)
11130     SWAP(pgnName, p)
11131     SWAP(pvSAN, h)
11132     SWAP(engOptions, p)
11133     SWAP(engInitString, p)
11134     SWAP(computerString, p)
11135     SWAP(features, p)
11136     SWAP(fenOverride, p)
11137     SWAP(NPS, h)
11138     SWAP(accumulateTC, h)
11139     SWAP(drawDepth, h)
11140     SWAP(host, p)
11141     SWAP(pseudo, h)
11142 }
11143
11144 int
11145 GetEngineLine (char *s, int n)
11146 {
11147     int i;
11148     char buf[MSG_SIZ];
11149     extern char *icsNames;
11150     if(!s || !*s) return 0;
11151     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11152     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11153     if(!mnemonic[i]) return 0;
11154     if(n == 11) return 1; // just testing if there was a match
11155     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11156     if(n == 1) SwapEngines(n);
11157     ParseArgsFromString(buf);
11158     if(n == 1) SwapEngines(n);
11159     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11160         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11161         ParseArgsFromString(buf);
11162     }
11163     return 1;
11164 }
11165
11166 int
11167 SetPlayer (int player, char *p)
11168 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11169     int i;
11170     char buf[MSG_SIZ], *engineName;
11171     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11172     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11173     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11174     if(mnemonic[i]) {
11175         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11176         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11177         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11178         ParseArgsFromString(buf);
11179     } else { // no engine with this nickname is installed!
11180         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11181         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11182         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11183         ModeHighlight();
11184         DisplayError(buf, 0);
11185         return 0;
11186     }
11187     free(engineName);
11188     return i;
11189 }
11190
11191 char *recentEngines;
11192
11193 void
11194 RecentEngineEvent (int nr)
11195 {
11196     int n;
11197 //    SwapEngines(1); // bump first to second
11198 //    ReplaceEngine(&second, 1); // and load it there
11199     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11200     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11201     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11202         ReplaceEngine(&first, 0);
11203         FloatToFront(&appData.recentEngineList, command[n]);
11204     }
11205 }
11206
11207 int
11208 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11209 {   // determine players from game number
11210     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11211
11212     if(appData.tourneyType == 0) {
11213         roundsPerCycle = (nPlayers - 1) | 1;
11214         pairingsPerRound = nPlayers / 2;
11215     } else if(appData.tourneyType > 0) {
11216         roundsPerCycle = nPlayers - appData.tourneyType;
11217         pairingsPerRound = appData.tourneyType;
11218     }
11219     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11220     gamesPerCycle = gamesPerRound * roundsPerCycle;
11221     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11222     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11223     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11224     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11225     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11226     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11227
11228     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11229     if(appData.roundSync) *syncInterval = gamesPerRound;
11230
11231     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11232
11233     if(appData.tourneyType == 0) {
11234         if(curPairing == (nPlayers-1)/2 ) {
11235             *whitePlayer = curRound;
11236             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11237         } else {
11238             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11239             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11240             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11241             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11242         }
11243     } else if(appData.tourneyType > 1) {
11244         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11245         *whitePlayer = curRound + appData.tourneyType;
11246     } else if(appData.tourneyType > 0) {
11247         *whitePlayer = curPairing;
11248         *blackPlayer = curRound + appData.tourneyType;
11249     }
11250
11251     // take care of white/black alternation per round.
11252     // For cycles and games this is already taken care of by default, derived from matchGame!
11253     return curRound & 1;
11254 }
11255
11256 int
11257 NextTourneyGame (int nr, int *swapColors)
11258 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11259     char *p, *q;
11260     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11261     FILE *tf;
11262     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11263     tf = fopen(appData.tourneyFile, "r");
11264     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11265     ParseArgsFromFile(tf); fclose(tf);
11266     InitTimeControls(); // TC might be altered from tourney file
11267
11268     nPlayers = CountPlayers(appData.participants); // count participants
11269     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11270     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11271
11272     if(syncInterval) {
11273         p = q = appData.results;
11274         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11275         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11276             DisplayMessage(_("Waiting for other game(s)"),"");
11277             waitingForGame = TRUE;
11278             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11279             return 0;
11280         }
11281         waitingForGame = FALSE;
11282     }
11283
11284     if(appData.tourneyType < 0) {
11285         if(nr>=0 && !pairingReceived) {
11286             char buf[1<<16];
11287             if(pairing.pr == NoProc) {
11288                 if(!appData.pairingEngine[0]) {
11289                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11290                     return 0;
11291                 }
11292                 StartChessProgram(&pairing); // starts the pairing engine
11293             }
11294             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11295             SendToProgram(buf, &pairing);
11296             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11297             SendToProgram(buf, &pairing);
11298             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11299         }
11300         pairingReceived = 0;                              // ... so we continue here
11301         *swapColors = 0;
11302         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11303         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11304         matchGame = 1; roundNr = nr / syncInterval + 1;
11305     }
11306
11307     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11308
11309     // redefine engines, engine dir, etc.
11310     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11311     if(first.pr == NoProc) {
11312       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11313       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11314     }
11315     if(second.pr == NoProc) {
11316       SwapEngines(1);
11317       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11318       SwapEngines(1);         // and make that valid for second engine by swapping
11319       InitEngine(&second, 1);
11320     }
11321     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11322     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11323     return OK;
11324 }
11325
11326 void
11327 NextMatchGame ()
11328 {   // performs game initialization that does not invoke engines, and then tries to start the game
11329     int res, firstWhite, swapColors = 0;
11330     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11331     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
11332         char buf[MSG_SIZ];
11333         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11334         if(strcmp(buf, currentDebugFile)) { // name has changed
11335             FILE *f = fopen(buf, "w");
11336             if(f) { // if opening the new file failed, just keep using the old one
11337                 ASSIGN(currentDebugFile, buf);
11338                 fclose(debugFP);
11339                 debugFP = f;
11340             }
11341             if(appData.serverFileName) {
11342                 if(serverFP) fclose(serverFP);
11343                 serverFP = fopen(appData.serverFileName, "w");
11344                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11345                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11346             }
11347         }
11348     }
11349     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11350     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11351     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11352     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11353     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11354     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11355     Reset(FALSE, first.pr != NoProc);
11356     res = LoadGameOrPosition(matchGame); // setup game
11357     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11358     if(!res) return; // abort when bad game/pos file
11359     TwoMachinesEvent();
11360 }
11361
11362 void
11363 UserAdjudicationEvent (int result)
11364 {
11365     ChessMove gameResult = GameIsDrawn;
11366
11367     if( result > 0 ) {
11368         gameResult = WhiteWins;
11369     }
11370     else if( result < 0 ) {
11371         gameResult = BlackWins;
11372     }
11373
11374     if( gameMode == TwoMachinesPlay ) {
11375         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11376     }
11377 }
11378
11379
11380 // [HGM] save: calculate checksum of game to make games easily identifiable
11381 int
11382 StringCheckSum (char *s)
11383 {
11384         int i = 0;
11385         if(s==NULL) return 0;
11386         while(*s) i = i*259 + *s++;
11387         return i;
11388 }
11389
11390 int
11391 GameCheckSum ()
11392 {
11393         int i, sum=0;
11394         for(i=backwardMostMove; i<forwardMostMove; i++) {
11395                 sum += pvInfoList[i].depth;
11396                 sum += StringCheckSum(parseList[i]);
11397                 sum += StringCheckSum(commentList[i]);
11398                 sum *= 261;
11399         }
11400         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11401         return sum + StringCheckSum(commentList[i]);
11402 } // end of save patch
11403
11404 void
11405 GameEnds (ChessMove result, char *resultDetails, int whosays)
11406 {
11407     GameMode nextGameMode;
11408     int isIcsGame;
11409     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11410
11411     if(endingGame) return; /* [HGM] crash: forbid recursion */
11412     endingGame = 1;
11413     if(twoBoards) { // [HGM] dual: switch back to one board
11414         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11415         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11416     }
11417     if (appData.debugMode) {
11418       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11419               result, resultDetails ? resultDetails : "(null)", whosays);
11420     }
11421
11422     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11423
11424     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11425
11426     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11427         /* If we are playing on ICS, the server decides when the
11428            game is over, but the engine can offer to draw, claim
11429            a draw, or resign.
11430          */
11431 #if ZIPPY
11432         if (appData.zippyPlay && first.initDone) {
11433             if (result == GameIsDrawn) {
11434                 /* In case draw still needs to be claimed */
11435                 SendToICS(ics_prefix);
11436                 SendToICS("draw\n");
11437             } else if (StrCaseStr(resultDetails, "resign")) {
11438                 SendToICS(ics_prefix);
11439                 SendToICS("resign\n");
11440             }
11441         }
11442 #endif
11443         endingGame = 0; /* [HGM] crash */
11444         return;
11445     }
11446
11447     /* If we're loading the game from a file, stop */
11448     if (whosays == GE_FILE) {
11449       (void) StopLoadGameTimer();
11450       gameFileFP = NULL;
11451     }
11452
11453     /* Cancel draw offers */
11454     first.offeredDraw = second.offeredDraw = 0;
11455
11456     /* If this is an ICS game, only ICS can really say it's done;
11457        if not, anyone can. */
11458     isIcsGame = (gameMode == IcsPlayingWhite ||
11459                  gameMode == IcsPlayingBlack ||
11460                  gameMode == IcsObserving    ||
11461                  gameMode == IcsExamining);
11462
11463     if (!isIcsGame || whosays == GE_ICS) {
11464         /* OK -- not an ICS game, or ICS said it was done */
11465         StopClocks();
11466         if (!isIcsGame && !appData.noChessProgram)
11467           SetUserThinkingEnables();
11468
11469         /* [HGM] if a machine claims the game end we verify this claim */
11470         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11471             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11472                 char claimer;
11473                 ChessMove trueResult = (ChessMove) -1;
11474
11475                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11476                                             first.twoMachinesColor[0] :
11477                                             second.twoMachinesColor[0] ;
11478
11479                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11480                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11481                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11482                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11483                 } else
11484                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11485                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11486                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11487                 } else
11488                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11489                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11490                 }
11491
11492                 // now verify win claims, but not in drop games, as we don't understand those yet
11493                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11494                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11495                     (result == WhiteWins && claimer == 'w' ||
11496                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11497                       if (appData.debugMode) {
11498                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11499                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11500                       }
11501                       if(result != trueResult) {
11502                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11503                               result = claimer == 'w' ? BlackWins : WhiteWins;
11504                               resultDetails = buf;
11505                       }
11506                 } else
11507                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11508                     && (forwardMostMove <= backwardMostMove ||
11509                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11510                         (claimer=='b')==(forwardMostMove&1))
11511                                                                                   ) {
11512                       /* [HGM] verify: draws that were not flagged are false claims */
11513                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11514                       result = claimer == 'w' ? BlackWins : WhiteWins;
11515                       resultDetails = buf;
11516                 }
11517                 /* (Claiming a loss is accepted no questions asked!) */
11518             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11519                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11520                 result = GameUnfinished;
11521                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11522             }
11523             /* [HGM] bare: don't allow bare King to win */
11524             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11525                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11526                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11527                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11528                && result != GameIsDrawn)
11529             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11530                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11531                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11532                         if(p >= 0 && p <= (int)WhiteKing) k++;
11533                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11534                 }
11535                 if (appData.debugMode) {
11536                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11537                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11538                 }
11539                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11540                         result = GameIsDrawn;
11541                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11542                         resultDetails = buf;
11543                 }
11544             }
11545         }
11546
11547
11548         if(serverMoves != NULL && !loadFlag) { char c = '=';
11549             if(result==WhiteWins) c = '+';
11550             if(result==BlackWins) c = '-';
11551             if(resultDetails != NULL)
11552                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11553         }
11554         if (resultDetails != NULL) {
11555             gameInfo.result = result;
11556             gameInfo.resultDetails = StrSave(resultDetails);
11557
11558             /* display last move only if game was not loaded from file */
11559             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11560                 DisplayMove(currentMove - 1);
11561
11562             if (forwardMostMove != 0) {
11563                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11564                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11565                                                                 ) {
11566                     if (*appData.saveGameFile != NULLCHAR) {
11567                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11568                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11569                         else
11570                         SaveGameToFile(appData.saveGameFile, TRUE);
11571                     } else if (appData.autoSaveGames) {
11572                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11573                     }
11574                     if (*appData.savePositionFile != NULLCHAR) {
11575                         SavePositionToFile(appData.savePositionFile);
11576                     }
11577                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11578                 }
11579             }
11580
11581             /* Tell program how game ended in case it is learning */
11582             /* [HGM] Moved this to after saving the PGN, just in case */
11583             /* engine died and we got here through time loss. In that */
11584             /* case we will get a fatal error writing the pipe, which */
11585             /* would otherwise lose us the PGN.                       */
11586             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11587             /* output during GameEnds should never be fatal anymore   */
11588             if (gameMode == MachinePlaysWhite ||
11589                 gameMode == MachinePlaysBlack ||
11590                 gameMode == TwoMachinesPlay ||
11591                 gameMode == IcsPlayingWhite ||
11592                 gameMode == IcsPlayingBlack ||
11593                 gameMode == BeginningOfGame) {
11594                 char buf[MSG_SIZ];
11595                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11596                         resultDetails);
11597                 if (first.pr != NoProc) {
11598                     SendToProgram(buf, &first);
11599                 }
11600                 if (second.pr != NoProc &&
11601                     gameMode == TwoMachinesPlay) {
11602                     SendToProgram(buf, &second);
11603                 }
11604             }
11605         }
11606
11607         if (appData.icsActive) {
11608             if (appData.quietPlay &&
11609                 (gameMode == IcsPlayingWhite ||
11610                  gameMode == IcsPlayingBlack)) {
11611                 SendToICS(ics_prefix);
11612                 SendToICS("set shout 1\n");
11613             }
11614             nextGameMode = IcsIdle;
11615             ics_user_moved = FALSE;
11616             /* clean up premove.  It's ugly when the game has ended and the
11617              * premove highlights are still on the board.
11618              */
11619             if (gotPremove) {
11620               gotPremove = FALSE;
11621               ClearPremoveHighlights();
11622               DrawPosition(FALSE, boards[currentMove]);
11623             }
11624             if (whosays == GE_ICS) {
11625                 switch (result) {
11626                 case WhiteWins:
11627                     if (gameMode == IcsPlayingWhite)
11628                         PlayIcsWinSound();
11629                     else if(gameMode == IcsPlayingBlack)
11630                         PlayIcsLossSound();
11631                     break;
11632                 case BlackWins:
11633                     if (gameMode == IcsPlayingBlack)
11634                         PlayIcsWinSound();
11635                     else if(gameMode == IcsPlayingWhite)
11636                         PlayIcsLossSound();
11637                     break;
11638                 case GameIsDrawn:
11639                     PlayIcsDrawSound();
11640                     break;
11641                 default:
11642                     PlayIcsUnfinishedSound();
11643                 }
11644             }
11645             if(appData.quitNext) { ExitEvent(0); return; }
11646         } else if (gameMode == EditGame ||
11647                    gameMode == PlayFromGameFile ||
11648                    gameMode == AnalyzeMode ||
11649                    gameMode == AnalyzeFile) {
11650             nextGameMode = gameMode;
11651         } else {
11652             nextGameMode = EndOfGame;
11653         }
11654         pausing = FALSE;
11655         ModeHighlight();
11656     } else {
11657         nextGameMode = gameMode;
11658     }
11659
11660     if (appData.noChessProgram) {
11661         gameMode = nextGameMode;
11662         ModeHighlight();
11663         endingGame = 0; /* [HGM] crash */
11664         return;
11665     }
11666
11667     if (first.reuse) {
11668         /* Put first chess program into idle state */
11669         if (first.pr != NoProc &&
11670             (gameMode == MachinePlaysWhite ||
11671              gameMode == MachinePlaysBlack ||
11672              gameMode == TwoMachinesPlay ||
11673              gameMode == IcsPlayingWhite ||
11674              gameMode == IcsPlayingBlack ||
11675              gameMode == BeginningOfGame)) {
11676             SendToProgram("force\n", &first);
11677             if (first.usePing) {
11678               char buf[MSG_SIZ];
11679               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11680               SendToProgram(buf, &first);
11681             }
11682         }
11683     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11684         /* Kill off first chess program */
11685         if (first.isr != NULL)
11686           RemoveInputSource(first.isr);
11687         first.isr = NULL;
11688
11689         if (first.pr != NoProc) {
11690             ExitAnalyzeMode();
11691             DoSleep( appData.delayBeforeQuit );
11692             SendToProgram("quit\n", &first);
11693             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11694             first.reload = TRUE;
11695         }
11696         first.pr = NoProc;
11697     }
11698     if (second.reuse) {
11699         /* Put second chess program into idle state */
11700         if (second.pr != NoProc &&
11701             gameMode == TwoMachinesPlay) {
11702             SendToProgram("force\n", &second);
11703             if (second.usePing) {
11704               char buf[MSG_SIZ];
11705               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11706               SendToProgram(buf, &second);
11707             }
11708         }
11709     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11710         /* Kill off second chess program */
11711         if (second.isr != NULL)
11712           RemoveInputSource(second.isr);
11713         second.isr = NULL;
11714
11715         if (second.pr != NoProc) {
11716             DoSleep( appData.delayBeforeQuit );
11717             SendToProgram("quit\n", &second);
11718             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11719             second.reload = TRUE;
11720         }
11721         second.pr = NoProc;
11722     }
11723
11724     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11725         char resChar = '=';
11726         switch (result) {
11727         case WhiteWins:
11728           resChar = '+';
11729           if (first.twoMachinesColor[0] == 'w') {
11730             first.matchWins++;
11731           } else {
11732             second.matchWins++;
11733           }
11734           break;
11735         case BlackWins:
11736           resChar = '-';
11737           if (first.twoMachinesColor[0] == 'b') {
11738             first.matchWins++;
11739           } else {
11740             second.matchWins++;
11741           }
11742           break;
11743         case GameUnfinished:
11744           resChar = ' ';
11745         default:
11746           break;
11747         }
11748
11749         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11750         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11751             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11752             ReserveGame(nextGame, resChar); // sets nextGame
11753             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11754             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11755         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11756
11757         if (nextGame <= appData.matchGames && !abortMatch) {
11758             gameMode = nextGameMode;
11759             matchGame = nextGame; // this will be overruled in tourney mode!
11760             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11761             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11762             endingGame = 0; /* [HGM] crash */
11763             return;
11764         } else {
11765             gameMode = nextGameMode;
11766             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11767                      first.tidy, second.tidy,
11768                      first.matchWins, second.matchWins,
11769                      appData.matchGames - (first.matchWins + second.matchWins));
11770             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11771             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11772             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11773             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11774                 first.twoMachinesColor = "black\n";
11775                 second.twoMachinesColor = "white\n";
11776             } else {
11777                 first.twoMachinesColor = "white\n";
11778                 second.twoMachinesColor = "black\n";
11779             }
11780         }
11781     }
11782     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11783         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11784       ExitAnalyzeMode();
11785     gameMode = nextGameMode;
11786     ModeHighlight();
11787     endingGame = 0;  /* [HGM] crash */
11788     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11789         if(matchMode == TRUE) { // match through command line: exit with or without popup
11790             if(ranking) {
11791                 ToNrEvent(forwardMostMove);
11792                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11793                 else ExitEvent(0);
11794             } else DisplayFatalError(buf, 0, 0);
11795         } else { // match through menu; just stop, with or without popup
11796             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11797             ModeHighlight();
11798             if(ranking){
11799                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11800             } else DisplayNote(buf);
11801       }
11802       if(ranking) free(ranking);
11803     }
11804 }
11805
11806 /* Assumes program was just initialized (initString sent).
11807    Leaves program in force mode. */
11808 void
11809 FeedMovesToProgram (ChessProgramState *cps, int upto)
11810 {
11811     int i;
11812
11813     if (appData.debugMode)
11814       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11815               startedFromSetupPosition ? "position and " : "",
11816               backwardMostMove, upto, cps->which);
11817     if(currentlyInitializedVariant != gameInfo.variant) {
11818       char buf[MSG_SIZ];
11819         // [HGM] variantswitch: make engine aware of new variant
11820         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11821                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11822                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11823         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11824         SendToProgram(buf, cps);
11825         currentlyInitializedVariant = gameInfo.variant;
11826     }
11827     SendToProgram("force\n", cps);
11828     if (startedFromSetupPosition) {
11829         SendBoard(cps, backwardMostMove);
11830     if (appData.debugMode) {
11831         fprintf(debugFP, "feedMoves\n");
11832     }
11833     }
11834     for (i = backwardMostMove; i < upto; i++) {
11835         SendMoveToProgram(i, cps);
11836     }
11837 }
11838
11839
11840 int
11841 ResurrectChessProgram ()
11842 {
11843      /* The chess program may have exited.
11844         If so, restart it and feed it all the moves made so far. */
11845     static int doInit = 0;
11846
11847     if (appData.noChessProgram) return 1;
11848
11849     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11850         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11851         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11852         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11853     } else {
11854         if (first.pr != NoProc) return 1;
11855         StartChessProgram(&first);
11856     }
11857     InitChessProgram(&first, FALSE);
11858     FeedMovesToProgram(&first, currentMove);
11859
11860     if (!first.sendTime) {
11861         /* can't tell gnuchess what its clock should read,
11862            so we bow to its notion. */
11863         ResetClocks();
11864         timeRemaining[0][currentMove] = whiteTimeRemaining;
11865         timeRemaining[1][currentMove] = blackTimeRemaining;
11866     }
11867
11868     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11869                 appData.icsEngineAnalyze) && first.analysisSupport) {
11870       SendToProgram("analyze\n", &first);
11871       first.analyzing = TRUE;
11872     }
11873     return 1;
11874 }
11875
11876 /*
11877  * Button procedures
11878  */
11879 void
11880 Reset (int redraw, int init)
11881 {
11882     int i;
11883
11884     if (appData.debugMode) {
11885         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11886                 redraw, init, gameMode);
11887     }
11888     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11889     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11890     CleanupTail(); // [HGM] vari: delete any stored variations
11891     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11892     pausing = pauseExamInvalid = FALSE;
11893     startedFromSetupPosition = blackPlaysFirst = FALSE;
11894     firstMove = TRUE;
11895     whiteFlag = blackFlag = FALSE;
11896     userOfferedDraw = FALSE;
11897     hintRequested = bookRequested = FALSE;
11898     first.maybeThinking = FALSE;
11899     second.maybeThinking = FALSE;
11900     first.bookSuspend = FALSE; // [HGM] book
11901     second.bookSuspend = FALSE;
11902     thinkOutput[0] = NULLCHAR;
11903     lastHint[0] = NULLCHAR;
11904     ClearGameInfo(&gameInfo);
11905     gameInfo.variant = StringToVariant(appData.variant);
11906     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11907     ics_user_moved = ics_clock_paused = FALSE;
11908     ics_getting_history = H_FALSE;
11909     ics_gamenum = -1;
11910     white_holding[0] = black_holding[0] = NULLCHAR;
11911     ClearProgramStats();
11912     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11913
11914     ResetFrontEnd();
11915     ClearHighlights();
11916     flipView = appData.flipView;
11917     ClearPremoveHighlights();
11918     gotPremove = FALSE;
11919     alarmSounded = FALSE;
11920     killX = killY = -1; // [HGM] lion
11921
11922     GameEnds(EndOfFile, NULL, GE_PLAYER);
11923     if(appData.serverMovesName != NULL) {
11924         /* [HGM] prepare to make moves file for broadcasting */
11925         clock_t t = clock();
11926         if(serverMoves != NULL) fclose(serverMoves);
11927         serverMoves = fopen(appData.serverMovesName, "r");
11928         if(serverMoves != NULL) {
11929             fclose(serverMoves);
11930             /* delay 15 sec before overwriting, so all clients can see end */
11931             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11932         }
11933         serverMoves = fopen(appData.serverMovesName, "w");
11934     }
11935
11936     ExitAnalyzeMode();
11937     gameMode = BeginningOfGame;
11938     ModeHighlight();
11939     if(appData.icsActive) gameInfo.variant = VariantNormal;
11940     currentMove = forwardMostMove = backwardMostMove = 0;
11941     MarkTargetSquares(1);
11942     InitPosition(redraw);
11943     for (i = 0; i < MAX_MOVES; i++) {
11944         if (commentList[i] != NULL) {
11945             free(commentList[i]);
11946             commentList[i] = NULL;
11947         }
11948     }
11949     ResetClocks();
11950     timeRemaining[0][0] = whiteTimeRemaining;
11951     timeRemaining[1][0] = blackTimeRemaining;
11952
11953     if (first.pr == NoProc) {
11954         StartChessProgram(&first);
11955     }
11956     if (init) {
11957             InitChessProgram(&first, startedFromSetupPosition);
11958     }
11959     DisplayTitle("");
11960     DisplayMessage("", "");
11961     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11962     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11963     ClearMap();        // [HGM] exclude: invalidate map
11964 }
11965
11966 void
11967 AutoPlayGameLoop ()
11968 {
11969     for (;;) {
11970         if (!AutoPlayOneMove())
11971           return;
11972         if (matchMode || appData.timeDelay == 0)
11973           continue;
11974         if (appData.timeDelay < 0)
11975           return;
11976         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11977         break;
11978     }
11979 }
11980
11981 void
11982 AnalyzeNextGame()
11983 {
11984     ReloadGame(1); // next game
11985 }
11986
11987 int
11988 AutoPlayOneMove ()
11989 {
11990     int fromX, fromY, toX, toY;
11991
11992     if (appData.debugMode) {
11993       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11994     }
11995
11996     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11997       return FALSE;
11998
11999     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12000       pvInfoList[currentMove].depth = programStats.depth;
12001       pvInfoList[currentMove].score = programStats.score;
12002       pvInfoList[currentMove].time  = 0;
12003       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12004       else { // append analysis of final position as comment
12005         char buf[MSG_SIZ];
12006         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12007         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12008       }
12009       programStats.depth = 0;
12010     }
12011
12012     if (currentMove >= forwardMostMove) {
12013       if(gameMode == AnalyzeFile) {
12014           if(appData.loadGameIndex == -1) {
12015             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12016           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12017           } else {
12018           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12019         }
12020       }
12021 //      gameMode = EndOfGame;
12022 //      ModeHighlight();
12023
12024       /* [AS] Clear current move marker at the end of a game */
12025       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12026
12027       return FALSE;
12028     }
12029
12030     toX = moveList[currentMove][2] - AAA;
12031     toY = moveList[currentMove][3] - ONE;
12032
12033     if (moveList[currentMove][1] == '@') {
12034         if (appData.highlightLastMove) {
12035             SetHighlights(-1, -1, toX, toY);
12036         }
12037     } else {
12038         int viaX = moveList[currentMove][5] - AAA;
12039         int viaY = moveList[currentMove][6] - ONE;
12040         fromX = moveList[currentMove][0] - AAA;
12041         fromY = moveList[currentMove][1] - ONE;
12042
12043         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12044
12045         if(moveList[currentMove][4] == ';') { // multi-leg
12046             ChessSquare piece = boards[currentMove][viaY][viaX];
12047             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12048             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12049             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12050             boards[currentMove][viaY][viaX] = piece;
12051         } else
12052         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12053
12054         if (appData.highlightLastMove) {
12055             SetHighlights(fromX, fromY, toX, toY);
12056         }
12057     }
12058     DisplayMove(currentMove);
12059     SendMoveToProgram(currentMove++, &first);
12060     DisplayBothClocks();
12061     DrawPosition(FALSE, boards[currentMove]);
12062     // [HGM] PV info: always display, routine tests if empty
12063     DisplayComment(currentMove - 1, commentList[currentMove]);
12064     return TRUE;
12065 }
12066
12067
12068 int
12069 LoadGameOneMove (ChessMove readAhead)
12070 {
12071     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12072     char promoChar = NULLCHAR;
12073     ChessMove moveType;
12074     char move[MSG_SIZ];
12075     char *p, *q;
12076
12077     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12078         gameMode != AnalyzeMode && gameMode != Training) {
12079         gameFileFP = NULL;
12080         return FALSE;
12081     }
12082
12083     yyboardindex = forwardMostMove;
12084     if (readAhead != EndOfFile) {
12085       moveType = readAhead;
12086     } else {
12087       if (gameFileFP == NULL)
12088           return FALSE;
12089       moveType = (ChessMove) Myylex();
12090     }
12091
12092     done = FALSE;
12093     switch (moveType) {
12094       case Comment:
12095         if (appData.debugMode)
12096           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12097         p = yy_text;
12098
12099         /* append the comment but don't display it */
12100         AppendComment(currentMove, p, FALSE);
12101         return TRUE;
12102
12103       case WhiteCapturesEnPassant:
12104       case BlackCapturesEnPassant:
12105       case WhitePromotion:
12106       case BlackPromotion:
12107       case WhiteNonPromotion:
12108       case BlackNonPromotion:
12109       case NormalMove:
12110       case FirstLeg:
12111       case WhiteKingSideCastle:
12112       case WhiteQueenSideCastle:
12113       case BlackKingSideCastle:
12114       case BlackQueenSideCastle:
12115       case WhiteKingSideCastleWild:
12116       case WhiteQueenSideCastleWild:
12117       case BlackKingSideCastleWild:
12118       case BlackQueenSideCastleWild:
12119       /* PUSH Fabien */
12120       case WhiteHSideCastleFR:
12121       case WhiteASideCastleFR:
12122       case BlackHSideCastleFR:
12123       case BlackASideCastleFR:
12124       /* POP Fabien */
12125         if (appData.debugMode)
12126           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12127         fromX = currentMoveString[0] - AAA;
12128         fromY = currentMoveString[1] - ONE;
12129         toX = currentMoveString[2] - AAA;
12130         toY = currentMoveString[3] - ONE;
12131         promoChar = currentMoveString[4];
12132         if(promoChar == ';') promoChar = NULLCHAR;
12133         break;
12134
12135       case WhiteDrop:
12136       case BlackDrop:
12137         if (appData.debugMode)
12138           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12139         fromX = moveType == WhiteDrop ?
12140           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12141         (int) CharToPiece(ToLower(currentMoveString[0]));
12142         fromY = DROP_RANK;
12143         toX = currentMoveString[2] - AAA;
12144         toY = currentMoveString[3] - ONE;
12145         break;
12146
12147       case WhiteWins:
12148       case BlackWins:
12149       case GameIsDrawn:
12150       case GameUnfinished:
12151         if (appData.debugMode)
12152           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12153         p = strchr(yy_text, '{');
12154         if (p == NULL) p = strchr(yy_text, '(');
12155         if (p == NULL) {
12156             p = yy_text;
12157             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12158         } else {
12159             q = strchr(p, *p == '{' ? '}' : ')');
12160             if (q != NULL) *q = NULLCHAR;
12161             p++;
12162         }
12163         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12164         GameEnds(moveType, p, GE_FILE);
12165         done = TRUE;
12166         if (cmailMsgLoaded) {
12167             ClearHighlights();
12168             flipView = WhiteOnMove(currentMove);
12169             if (moveType == GameUnfinished) flipView = !flipView;
12170             if (appData.debugMode)
12171               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12172         }
12173         break;
12174
12175       case EndOfFile:
12176         if (appData.debugMode)
12177           fprintf(debugFP, "Parser hit end of file\n");
12178         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12179           case MT_NONE:
12180           case MT_CHECK:
12181             break;
12182           case MT_CHECKMATE:
12183           case MT_STAINMATE:
12184             if (WhiteOnMove(currentMove)) {
12185                 GameEnds(BlackWins, "Black mates", GE_FILE);
12186             } else {
12187                 GameEnds(WhiteWins, "White mates", GE_FILE);
12188             }
12189             break;
12190           case MT_STALEMATE:
12191             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12192             break;
12193         }
12194         done = TRUE;
12195         break;
12196
12197       case MoveNumberOne:
12198         if (lastLoadGameStart == GNUChessGame) {
12199             /* GNUChessGames have numbers, but they aren't move numbers */
12200             if (appData.debugMode)
12201               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12202                       yy_text, (int) moveType);
12203             return LoadGameOneMove(EndOfFile); /* tail recursion */
12204         }
12205         /* else fall thru */
12206
12207       case XBoardGame:
12208       case GNUChessGame:
12209       case PGNTag:
12210         /* Reached start of next game in file */
12211         if (appData.debugMode)
12212           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12213         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12214           case MT_NONE:
12215           case MT_CHECK:
12216             break;
12217           case MT_CHECKMATE:
12218           case MT_STAINMATE:
12219             if (WhiteOnMove(currentMove)) {
12220                 GameEnds(BlackWins, "Black mates", GE_FILE);
12221             } else {
12222                 GameEnds(WhiteWins, "White mates", GE_FILE);
12223             }
12224             break;
12225           case MT_STALEMATE:
12226             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12227             break;
12228         }
12229         done = TRUE;
12230         break;
12231
12232       case PositionDiagram:     /* should not happen; ignore */
12233       case ElapsedTime:         /* ignore */
12234       case NAG:                 /* ignore */
12235         if (appData.debugMode)
12236           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12237                   yy_text, (int) moveType);
12238         return LoadGameOneMove(EndOfFile); /* tail recursion */
12239
12240       case IllegalMove:
12241         if (appData.testLegality) {
12242             if (appData.debugMode)
12243               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12244             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12245                     (forwardMostMove / 2) + 1,
12246                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12247             DisplayError(move, 0);
12248             done = TRUE;
12249         } else {
12250             if (appData.debugMode)
12251               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12252                       yy_text, currentMoveString);
12253             if(currentMoveString[1] == '@') {
12254                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12255                 fromY = DROP_RANK;
12256             } else {
12257                 fromX = currentMoveString[0] - AAA;
12258                 fromY = currentMoveString[1] - ONE;
12259             }
12260             toX = currentMoveString[2] - AAA;
12261             toY = currentMoveString[3] - ONE;
12262             promoChar = currentMoveString[4];
12263         }
12264         break;
12265
12266       case AmbiguousMove:
12267         if (appData.debugMode)
12268           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12269         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12270                 (forwardMostMove / 2) + 1,
12271                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12272         DisplayError(move, 0);
12273         done = TRUE;
12274         break;
12275
12276       default:
12277       case ImpossibleMove:
12278         if (appData.debugMode)
12279           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12280         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12281                 (forwardMostMove / 2) + 1,
12282                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12283         DisplayError(move, 0);
12284         done = TRUE;
12285         break;
12286     }
12287
12288     if (done) {
12289         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12290             DrawPosition(FALSE, boards[currentMove]);
12291             DisplayBothClocks();
12292             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12293               DisplayComment(currentMove - 1, commentList[currentMove]);
12294         }
12295         (void) StopLoadGameTimer();
12296         gameFileFP = NULL;
12297         cmailOldMove = forwardMostMove;
12298         return FALSE;
12299     } else {
12300         /* currentMoveString is set as a side-effect of yylex */
12301
12302         thinkOutput[0] = NULLCHAR;
12303         MakeMove(fromX, fromY, toX, toY, promoChar);
12304         killX = killY = -1; // [HGM] lion: used up
12305         currentMove = forwardMostMove;
12306         return TRUE;
12307     }
12308 }
12309
12310 /* Load the nth game from the given file */
12311 int
12312 LoadGameFromFile (char *filename, int n, char *title, int useList)
12313 {
12314     FILE *f;
12315     char buf[MSG_SIZ];
12316
12317     if (strcmp(filename, "-") == 0) {
12318         f = stdin;
12319         title = "stdin";
12320     } else {
12321         f = fopen(filename, "rb");
12322         if (f == NULL) {
12323           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12324             DisplayError(buf, errno);
12325             return FALSE;
12326         }
12327     }
12328     if (fseek(f, 0, 0) == -1) {
12329         /* f is not seekable; probably a pipe */
12330         useList = FALSE;
12331     }
12332     if (useList && n == 0) {
12333         int error = GameListBuild(f);
12334         if (error) {
12335             DisplayError(_("Cannot build game list"), error);
12336         } else if (!ListEmpty(&gameList) &&
12337                    ((ListGame *) gameList.tailPred)->number > 1) {
12338             GameListPopUp(f, title);
12339             return TRUE;
12340         }
12341         GameListDestroy();
12342         n = 1;
12343     }
12344     if (n == 0) n = 1;
12345     return LoadGame(f, n, title, FALSE);
12346 }
12347
12348
12349 void
12350 MakeRegisteredMove ()
12351 {
12352     int fromX, fromY, toX, toY;
12353     char promoChar;
12354     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12355         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12356           case CMAIL_MOVE:
12357           case CMAIL_DRAW:
12358             if (appData.debugMode)
12359               fprintf(debugFP, "Restoring %s for game %d\n",
12360                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12361
12362             thinkOutput[0] = NULLCHAR;
12363             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12364             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12365             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12366             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12367             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12368             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12369             MakeMove(fromX, fromY, toX, toY, promoChar);
12370             ShowMove(fromX, fromY, toX, toY);
12371
12372             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12373               case MT_NONE:
12374               case MT_CHECK:
12375                 break;
12376
12377               case MT_CHECKMATE:
12378               case MT_STAINMATE:
12379                 if (WhiteOnMove(currentMove)) {
12380                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12381                 } else {
12382                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12383                 }
12384                 break;
12385
12386               case MT_STALEMATE:
12387                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12388                 break;
12389             }
12390
12391             break;
12392
12393           case CMAIL_RESIGN:
12394             if (WhiteOnMove(currentMove)) {
12395                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12396             } else {
12397                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12398             }
12399             break;
12400
12401           case CMAIL_ACCEPT:
12402             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12403             break;
12404
12405           default:
12406             break;
12407         }
12408     }
12409
12410     return;
12411 }
12412
12413 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12414 int
12415 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12416 {
12417     int retVal;
12418
12419     if (gameNumber > nCmailGames) {
12420         DisplayError(_("No more games in this message"), 0);
12421         return FALSE;
12422     }
12423     if (f == lastLoadGameFP) {
12424         int offset = gameNumber - lastLoadGameNumber;
12425         if (offset == 0) {
12426             cmailMsg[0] = NULLCHAR;
12427             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12428                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12429                 nCmailMovesRegistered--;
12430             }
12431             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12432             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12433                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12434             }
12435         } else {
12436             if (! RegisterMove()) return FALSE;
12437         }
12438     }
12439
12440     retVal = LoadGame(f, gameNumber, title, useList);
12441
12442     /* Make move registered during previous look at this game, if any */
12443     MakeRegisteredMove();
12444
12445     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12446         commentList[currentMove]
12447           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12448         DisplayComment(currentMove - 1, commentList[currentMove]);
12449     }
12450
12451     return retVal;
12452 }
12453
12454 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12455 int
12456 ReloadGame (int offset)
12457 {
12458     int gameNumber = lastLoadGameNumber + offset;
12459     if (lastLoadGameFP == NULL) {
12460         DisplayError(_("No game has been loaded yet"), 0);
12461         return FALSE;
12462     }
12463     if (gameNumber <= 0) {
12464         DisplayError(_("Can't back up any further"), 0);
12465         return FALSE;
12466     }
12467     if (cmailMsgLoaded) {
12468         return CmailLoadGame(lastLoadGameFP, gameNumber,
12469                              lastLoadGameTitle, lastLoadGameUseList);
12470     } else {
12471         return LoadGame(lastLoadGameFP, gameNumber,
12472                         lastLoadGameTitle, lastLoadGameUseList);
12473     }
12474 }
12475
12476 int keys[EmptySquare+1];
12477
12478 int
12479 PositionMatches (Board b1, Board b2)
12480 {
12481     int r, f, sum=0;
12482     switch(appData.searchMode) {
12483         case 1: return CompareWithRights(b1, b2);
12484         case 2:
12485             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12486                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12487             }
12488             return TRUE;
12489         case 3:
12490             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12491               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12492                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12493             }
12494             return sum==0;
12495         case 4:
12496             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12497                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12498             }
12499             return sum==0;
12500     }
12501     return TRUE;
12502 }
12503
12504 #define Q_PROMO  4
12505 #define Q_EP     3
12506 #define Q_BCASTL 2
12507 #define Q_WCASTL 1
12508
12509 int pieceList[256], quickBoard[256];
12510 ChessSquare pieceType[256] = { EmptySquare };
12511 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12512 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12513 int soughtTotal, turn;
12514 Boolean epOK, flipSearch;
12515
12516 typedef struct {
12517     unsigned char piece, to;
12518 } Move;
12519
12520 #define DSIZE (250000)
12521
12522 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12523 Move *moveDatabase = initialSpace;
12524 unsigned int movePtr, dataSize = DSIZE;
12525
12526 int
12527 MakePieceList (Board board, int *counts)
12528 {
12529     int r, f, n=Q_PROMO, total=0;
12530     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12531     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12532         int sq = f + (r<<4);
12533         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12534             quickBoard[sq] = ++n;
12535             pieceList[n] = sq;
12536             pieceType[n] = board[r][f];
12537             counts[board[r][f]]++;
12538             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12539             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12540             total++;
12541         }
12542     }
12543     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12544     return total;
12545 }
12546
12547 void
12548 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12549 {
12550     int sq = fromX + (fromY<<4);
12551     int piece = quickBoard[sq], rook;
12552     quickBoard[sq] = 0;
12553     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12554     if(piece == pieceList[1] && fromY == toY) {
12555       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12556         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12557         moveDatabase[movePtr++].piece = Q_WCASTL;
12558         quickBoard[sq] = piece;
12559         piece = quickBoard[from]; quickBoard[from] = 0;
12560         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12561       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12562         quickBoard[sq] = 0; // remove Rook
12563         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12564         moveDatabase[movePtr++].piece = Q_WCASTL;
12565         quickBoard[sq] = pieceList[1]; // put King
12566         piece = rook;
12567         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12568       }
12569     } else
12570     if(piece == pieceList[2] && fromY == toY) {
12571       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12572         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12573         moveDatabase[movePtr++].piece = Q_BCASTL;
12574         quickBoard[sq] = piece;
12575         piece = quickBoard[from]; quickBoard[from] = 0;
12576         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12577       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12578         quickBoard[sq] = 0; // remove Rook
12579         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12580         moveDatabase[movePtr++].piece = Q_BCASTL;
12581         quickBoard[sq] = pieceList[2]; // put King
12582         piece = rook;
12583         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12584       }
12585     } else
12586     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12587         quickBoard[(fromY<<4)+toX] = 0;
12588         moveDatabase[movePtr].piece = Q_EP;
12589         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12590         moveDatabase[movePtr].to = sq;
12591     } else
12592     if(promoPiece != pieceType[piece]) {
12593         moveDatabase[movePtr++].piece = Q_PROMO;
12594         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12595     }
12596     moveDatabase[movePtr].piece = piece;
12597     quickBoard[sq] = piece;
12598     movePtr++;
12599 }
12600
12601 int
12602 PackGame (Board board)
12603 {
12604     Move *newSpace = NULL;
12605     moveDatabase[movePtr].piece = 0; // terminate previous game
12606     if(movePtr > dataSize) {
12607         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12608         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12609         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12610         if(newSpace) {
12611             int i;
12612             Move *p = moveDatabase, *q = newSpace;
12613             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12614             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12615             moveDatabase = newSpace;
12616         } else { // calloc failed, we must be out of memory. Too bad...
12617             dataSize = 0; // prevent calloc events for all subsequent games
12618             return 0;     // and signal this one isn't cached
12619         }
12620     }
12621     movePtr++;
12622     MakePieceList(board, counts);
12623     return movePtr;
12624 }
12625
12626 int
12627 QuickCompare (Board board, int *minCounts, int *maxCounts)
12628 {   // compare according to search mode
12629     int r, f;
12630     switch(appData.searchMode)
12631     {
12632       case 1: // exact position match
12633         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12634         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12635             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12636         }
12637         break;
12638       case 2: // can have extra material on empty squares
12639         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12640             if(board[r][f] == EmptySquare) continue;
12641             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12642         }
12643         break;
12644       case 3: // material with exact Pawn structure
12645         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12646             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12647             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12648         } // fall through to material comparison
12649       case 4: // exact material
12650         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12651         break;
12652       case 6: // material range with given imbalance
12653         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12654         // fall through to range comparison
12655       case 5: // material range
12656         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12657     }
12658     return TRUE;
12659 }
12660
12661 int
12662 QuickScan (Board board, Move *move)
12663 {   // reconstruct game,and compare all positions in it
12664     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12665     do {
12666         int piece = move->piece;
12667         int to = move->to, from = pieceList[piece];
12668         if(found < 0) { // if already found just scan to game end for final piece count
12669           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12670            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12671            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12672                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12673             ) {
12674             static int lastCounts[EmptySquare+1];
12675             int i;
12676             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12677             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12678           } else stretch = 0;
12679           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12680           if(found >= 0 && !appData.minPieces) return found;
12681         }
12682         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12683           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12684           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12685             piece = (++move)->piece;
12686             from = pieceList[piece];
12687             counts[pieceType[piece]]--;
12688             pieceType[piece] = (ChessSquare) move->to;
12689             counts[move->to]++;
12690           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12691             counts[pieceType[quickBoard[to]]]--;
12692             quickBoard[to] = 0; total--;
12693             move++;
12694             continue;
12695           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12696             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12697             from  = pieceList[piece]; // so this must be King
12698             quickBoard[from] = 0;
12699             pieceList[piece] = to;
12700             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12701             quickBoard[from] = 0; // rook
12702             quickBoard[to] = piece;
12703             to = move->to; piece = move->piece;
12704             goto aftercastle;
12705           }
12706         }
12707         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12708         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12709         quickBoard[from] = 0;
12710       aftercastle:
12711         quickBoard[to] = piece;
12712         pieceList[piece] = to;
12713         cnt++; turn ^= 3;
12714         move++;
12715     } while(1);
12716 }
12717
12718 void
12719 InitSearch ()
12720 {
12721     int r, f;
12722     flipSearch = FALSE;
12723     CopyBoard(soughtBoard, boards[currentMove]);
12724     soughtTotal = MakePieceList(soughtBoard, maxSought);
12725     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12726     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12727     CopyBoard(reverseBoard, boards[currentMove]);
12728     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12729         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12730         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12731         reverseBoard[r][f] = piece;
12732     }
12733     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12734     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12735     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12736                  || (boards[currentMove][CASTLING][2] == NoRights ||
12737                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12738                  && (boards[currentMove][CASTLING][5] == NoRights ||
12739                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12740       ) {
12741         flipSearch = TRUE;
12742         CopyBoard(flipBoard, soughtBoard);
12743         CopyBoard(rotateBoard, reverseBoard);
12744         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12745             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12746             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12747         }
12748     }
12749     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12750     if(appData.searchMode >= 5) {
12751         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12752         MakePieceList(soughtBoard, minSought);
12753         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12754     }
12755     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12756         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12757 }
12758
12759 GameInfo dummyInfo;
12760 static int creatingBook;
12761
12762 int
12763 GameContainsPosition (FILE *f, ListGame *lg)
12764 {
12765     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12766     int fromX, fromY, toX, toY;
12767     char promoChar;
12768     static int initDone=FALSE;
12769
12770     // weed out games based on numerical tag comparison
12771     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12772     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12773     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12774     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12775     if(!initDone) {
12776         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12777         initDone = TRUE;
12778     }
12779     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12780     else CopyBoard(boards[scratch], initialPosition); // default start position
12781     if(lg->moves) {
12782         turn = btm + 1;
12783         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12784         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12785     }
12786     if(btm) plyNr++;
12787     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12788     fseek(f, lg->offset, 0);
12789     yynewfile(f);
12790     while(1) {
12791         yyboardindex = scratch;
12792         quickFlag = plyNr+1;
12793         next = Myylex();
12794         quickFlag = 0;
12795         switch(next) {
12796             case PGNTag:
12797                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12798             default:
12799                 continue;
12800
12801             case XBoardGame:
12802             case GNUChessGame:
12803                 if(plyNr) return -1; // after we have seen moves, this is for new game
12804               continue;
12805
12806             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12807             case ImpossibleMove:
12808             case WhiteWins: // game ends here with these four
12809             case BlackWins:
12810             case GameIsDrawn:
12811             case GameUnfinished:
12812                 return -1;
12813
12814             case IllegalMove:
12815                 if(appData.testLegality) return -1;
12816             case WhiteCapturesEnPassant:
12817             case BlackCapturesEnPassant:
12818             case WhitePromotion:
12819             case BlackPromotion:
12820             case WhiteNonPromotion:
12821             case BlackNonPromotion:
12822             case NormalMove:
12823             case FirstLeg:
12824             case WhiteKingSideCastle:
12825             case WhiteQueenSideCastle:
12826             case BlackKingSideCastle:
12827             case BlackQueenSideCastle:
12828             case WhiteKingSideCastleWild:
12829             case WhiteQueenSideCastleWild:
12830             case BlackKingSideCastleWild:
12831             case BlackQueenSideCastleWild:
12832             case WhiteHSideCastleFR:
12833             case WhiteASideCastleFR:
12834             case BlackHSideCastleFR:
12835             case BlackASideCastleFR:
12836                 fromX = currentMoveString[0] - AAA;
12837                 fromY = currentMoveString[1] - ONE;
12838                 toX = currentMoveString[2] - AAA;
12839                 toY = currentMoveString[3] - ONE;
12840                 promoChar = currentMoveString[4];
12841                 break;
12842             case WhiteDrop:
12843             case BlackDrop:
12844                 fromX = next == WhiteDrop ?
12845                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12846                   (int) CharToPiece(ToLower(currentMoveString[0]));
12847                 fromY = DROP_RANK;
12848                 toX = currentMoveString[2] - AAA;
12849                 toY = currentMoveString[3] - ONE;
12850                 promoChar = 0;
12851                 break;
12852         }
12853         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12854         plyNr++;
12855         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12856         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12857         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12858         if(appData.findMirror) {
12859             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12860             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12861         }
12862     }
12863 }
12864
12865 /* Load the nth game from open file f */
12866 int
12867 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12868 {
12869     ChessMove cm;
12870     char buf[MSG_SIZ];
12871     int gn = gameNumber;
12872     ListGame *lg = NULL;
12873     int numPGNTags = 0;
12874     int err, pos = -1;
12875     GameMode oldGameMode;
12876     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12877     char oldName[MSG_SIZ];
12878
12879     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12880
12881     if (appData.debugMode)
12882         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12883
12884     if (gameMode == Training )
12885         SetTrainingModeOff();
12886
12887     oldGameMode = gameMode;
12888     if (gameMode != BeginningOfGame) {
12889       Reset(FALSE, TRUE);
12890     }
12891     killX = killY = -1; // [HGM] lion: in case we did not Reset
12892
12893     gameFileFP = f;
12894     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12895         fclose(lastLoadGameFP);
12896     }
12897
12898     if (useList) {
12899         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12900
12901         if (lg) {
12902             fseek(f, lg->offset, 0);
12903             GameListHighlight(gameNumber);
12904             pos = lg->position;
12905             gn = 1;
12906         }
12907         else {
12908             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12909               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12910             else
12911             DisplayError(_("Game number out of range"), 0);
12912             return FALSE;
12913         }
12914     } else {
12915         GameListDestroy();
12916         if (fseek(f, 0, 0) == -1) {
12917             if (f == lastLoadGameFP ?
12918                 gameNumber == lastLoadGameNumber + 1 :
12919                 gameNumber == 1) {
12920                 gn = 1;
12921             } else {
12922                 DisplayError(_("Can't seek on game file"), 0);
12923                 return FALSE;
12924             }
12925         }
12926     }
12927     lastLoadGameFP = f;
12928     lastLoadGameNumber = gameNumber;
12929     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12930     lastLoadGameUseList = useList;
12931
12932     yynewfile(f);
12933
12934     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12935       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12936                 lg->gameInfo.black);
12937             DisplayTitle(buf);
12938     } else if (*title != NULLCHAR) {
12939         if (gameNumber > 1) {
12940           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12941             DisplayTitle(buf);
12942         } else {
12943             DisplayTitle(title);
12944         }
12945     }
12946
12947     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12948         gameMode = PlayFromGameFile;
12949         ModeHighlight();
12950     }
12951
12952     currentMove = forwardMostMove = backwardMostMove = 0;
12953     CopyBoard(boards[0], initialPosition);
12954     StopClocks();
12955
12956     /*
12957      * Skip the first gn-1 games in the file.
12958      * Also skip over anything that precedes an identifiable
12959      * start of game marker, to avoid being confused by
12960      * garbage at the start of the file.  Currently
12961      * recognized start of game markers are the move number "1",
12962      * the pattern "gnuchess .* game", the pattern
12963      * "^[#;%] [^ ]* game file", and a PGN tag block.
12964      * A game that starts with one of the latter two patterns
12965      * will also have a move number 1, possibly
12966      * following a position diagram.
12967      * 5-4-02: Let's try being more lenient and allowing a game to
12968      * start with an unnumbered move.  Does that break anything?
12969      */
12970     cm = lastLoadGameStart = EndOfFile;
12971     while (gn > 0) {
12972         yyboardindex = forwardMostMove;
12973         cm = (ChessMove) Myylex();
12974         switch (cm) {
12975           case EndOfFile:
12976             if (cmailMsgLoaded) {
12977                 nCmailGames = CMAIL_MAX_GAMES - gn;
12978             } else {
12979                 Reset(TRUE, TRUE);
12980                 DisplayError(_("Game not found in file"), 0);
12981             }
12982             return FALSE;
12983
12984           case GNUChessGame:
12985           case XBoardGame:
12986             gn--;
12987             lastLoadGameStart = cm;
12988             break;
12989
12990           case MoveNumberOne:
12991             switch (lastLoadGameStart) {
12992               case GNUChessGame:
12993               case XBoardGame:
12994               case PGNTag:
12995                 break;
12996               case MoveNumberOne:
12997               case EndOfFile:
12998                 gn--;           /* count this game */
12999                 lastLoadGameStart = cm;
13000                 break;
13001               default:
13002                 /* impossible */
13003                 break;
13004             }
13005             break;
13006
13007           case PGNTag:
13008             switch (lastLoadGameStart) {
13009               case GNUChessGame:
13010               case PGNTag:
13011               case MoveNumberOne:
13012               case EndOfFile:
13013                 gn--;           /* count this game */
13014                 lastLoadGameStart = cm;
13015                 break;
13016               case XBoardGame:
13017                 lastLoadGameStart = cm; /* game counted already */
13018                 break;
13019               default:
13020                 /* impossible */
13021                 break;
13022             }
13023             if (gn > 0) {
13024                 do {
13025                     yyboardindex = forwardMostMove;
13026                     cm = (ChessMove) Myylex();
13027                 } while (cm == PGNTag || cm == Comment);
13028             }
13029             break;
13030
13031           case WhiteWins:
13032           case BlackWins:
13033           case GameIsDrawn:
13034             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13035                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13036                     != CMAIL_OLD_RESULT) {
13037                     nCmailResults ++ ;
13038                     cmailResult[  CMAIL_MAX_GAMES
13039                                 - gn - 1] = CMAIL_OLD_RESULT;
13040                 }
13041             }
13042             break;
13043
13044           case NormalMove:
13045           case FirstLeg:
13046             /* Only a NormalMove can be at the start of a game
13047              * without a position diagram. */
13048             if (lastLoadGameStart == EndOfFile ) {
13049               gn--;
13050               lastLoadGameStart = MoveNumberOne;
13051             }
13052             break;
13053
13054           default:
13055             break;
13056         }
13057     }
13058
13059     if (appData.debugMode)
13060       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13061
13062     if (cm == XBoardGame) {
13063         /* Skip any header junk before position diagram and/or move 1 */
13064         for (;;) {
13065             yyboardindex = forwardMostMove;
13066             cm = (ChessMove) Myylex();
13067
13068             if (cm == EndOfFile ||
13069                 cm == GNUChessGame || cm == XBoardGame) {
13070                 /* Empty game; pretend end-of-file and handle later */
13071                 cm = EndOfFile;
13072                 break;
13073             }
13074
13075             if (cm == MoveNumberOne || cm == PositionDiagram ||
13076                 cm == PGNTag || cm == Comment)
13077               break;
13078         }
13079     } else if (cm == GNUChessGame) {
13080         if (gameInfo.event != NULL) {
13081             free(gameInfo.event);
13082         }
13083         gameInfo.event = StrSave(yy_text);
13084     }
13085
13086     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13087     while (cm == PGNTag) {
13088         if (appData.debugMode)
13089           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13090         err = ParsePGNTag(yy_text, &gameInfo);
13091         if (!err) numPGNTags++;
13092
13093         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13094         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13095             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13096             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13097             InitPosition(TRUE);
13098             oldVariant = gameInfo.variant;
13099             if (appData.debugMode)
13100               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13101         }
13102
13103
13104         if (gameInfo.fen != NULL) {
13105           Board initial_position;
13106           startedFromSetupPosition = TRUE;
13107           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13108             Reset(TRUE, TRUE);
13109             DisplayError(_("Bad FEN position in file"), 0);
13110             return FALSE;
13111           }
13112           CopyBoard(boards[0], initial_position);
13113           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13114             CopyBoard(initialPosition, initial_position);
13115           if (blackPlaysFirst) {
13116             currentMove = forwardMostMove = backwardMostMove = 1;
13117             CopyBoard(boards[1], initial_position);
13118             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13119             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13120             timeRemaining[0][1] = whiteTimeRemaining;
13121             timeRemaining[1][1] = blackTimeRemaining;
13122             if (commentList[0] != NULL) {
13123               commentList[1] = commentList[0];
13124               commentList[0] = NULL;
13125             }
13126           } else {
13127             currentMove = forwardMostMove = backwardMostMove = 0;
13128           }
13129           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13130           {   int i;
13131               initialRulePlies = FENrulePlies;
13132               for( i=0; i< nrCastlingRights; i++ )
13133                   initialRights[i] = initial_position[CASTLING][i];
13134           }
13135           yyboardindex = forwardMostMove;
13136           free(gameInfo.fen);
13137           gameInfo.fen = NULL;
13138         }
13139
13140         yyboardindex = forwardMostMove;
13141         cm = (ChessMove) Myylex();
13142
13143         /* Handle comments interspersed among the tags */
13144         while (cm == Comment) {
13145             char *p;
13146             if (appData.debugMode)
13147               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13148             p = yy_text;
13149             AppendComment(currentMove, p, FALSE);
13150             yyboardindex = forwardMostMove;
13151             cm = (ChessMove) Myylex();
13152         }
13153     }
13154
13155     /* don't rely on existence of Event tag since if game was
13156      * pasted from clipboard the Event tag may not exist
13157      */
13158     if (numPGNTags > 0){
13159         char *tags;
13160         if (gameInfo.variant == VariantNormal) {
13161           VariantClass v = StringToVariant(gameInfo.event);
13162           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13163           if(v < VariantShogi) gameInfo.variant = v;
13164         }
13165         if (!matchMode) {
13166           if( appData.autoDisplayTags ) {
13167             tags = PGNTags(&gameInfo);
13168             TagsPopUp(tags, CmailMsg());
13169             free(tags);
13170           }
13171         }
13172     } else {
13173         /* Make something up, but don't display it now */
13174         SetGameInfo();
13175         TagsPopDown();
13176     }
13177
13178     if (cm == PositionDiagram) {
13179         int i, j;
13180         char *p;
13181         Board initial_position;
13182
13183         if (appData.debugMode)
13184           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13185
13186         if (!startedFromSetupPosition) {
13187             p = yy_text;
13188             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13189               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13190                 switch (*p) {
13191                   case '{':
13192                   case '[':
13193                   case '-':
13194                   case ' ':
13195                   case '\t':
13196                   case '\n':
13197                   case '\r':
13198                     break;
13199                   default:
13200                     initial_position[i][j++] = CharToPiece(*p);
13201                     break;
13202                 }
13203             while (*p == ' ' || *p == '\t' ||
13204                    *p == '\n' || *p == '\r') p++;
13205
13206             if (strncmp(p, "black", strlen("black"))==0)
13207               blackPlaysFirst = TRUE;
13208             else
13209               blackPlaysFirst = FALSE;
13210             startedFromSetupPosition = TRUE;
13211
13212             CopyBoard(boards[0], initial_position);
13213             if (blackPlaysFirst) {
13214                 currentMove = forwardMostMove = backwardMostMove = 1;
13215                 CopyBoard(boards[1], initial_position);
13216                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13217                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13218                 timeRemaining[0][1] = whiteTimeRemaining;
13219                 timeRemaining[1][1] = blackTimeRemaining;
13220                 if (commentList[0] != NULL) {
13221                     commentList[1] = commentList[0];
13222                     commentList[0] = NULL;
13223                 }
13224             } else {
13225                 currentMove = forwardMostMove = backwardMostMove = 0;
13226             }
13227         }
13228         yyboardindex = forwardMostMove;
13229         cm = (ChessMove) Myylex();
13230     }
13231
13232   if(!creatingBook) {
13233     if (first.pr == NoProc) {
13234         StartChessProgram(&first);
13235     }
13236     InitChessProgram(&first, FALSE);
13237     if(gameInfo.variant == VariantUnknown && *oldName) {
13238         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13239         gameInfo.variant = v;
13240     }
13241     SendToProgram("force\n", &first);
13242     if (startedFromSetupPosition) {
13243         SendBoard(&first, forwardMostMove);
13244     if (appData.debugMode) {
13245         fprintf(debugFP, "Load Game\n");
13246     }
13247         DisplayBothClocks();
13248     }
13249   }
13250
13251     /* [HGM] server: flag to write setup moves in broadcast file as one */
13252     loadFlag = appData.suppressLoadMoves;
13253
13254     while (cm == Comment) {
13255         char *p;
13256         if (appData.debugMode)
13257           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13258         p = yy_text;
13259         AppendComment(currentMove, p, FALSE);
13260         yyboardindex = forwardMostMove;
13261         cm = (ChessMove) Myylex();
13262     }
13263
13264     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13265         cm == WhiteWins || cm == BlackWins ||
13266         cm == GameIsDrawn || cm == GameUnfinished) {
13267         DisplayMessage("", _("No moves in game"));
13268         if (cmailMsgLoaded) {
13269             if (appData.debugMode)
13270               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13271             ClearHighlights();
13272             flipView = FALSE;
13273         }
13274         DrawPosition(FALSE, boards[currentMove]);
13275         DisplayBothClocks();
13276         gameMode = EditGame;
13277         ModeHighlight();
13278         gameFileFP = NULL;
13279         cmailOldMove = 0;
13280         return TRUE;
13281     }
13282
13283     // [HGM] PV info: routine tests if comment empty
13284     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13285         DisplayComment(currentMove - 1, commentList[currentMove]);
13286     }
13287     if (!matchMode && appData.timeDelay != 0)
13288       DrawPosition(FALSE, boards[currentMove]);
13289
13290     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13291       programStats.ok_to_send = 1;
13292     }
13293
13294     /* if the first token after the PGN tags is a move
13295      * and not move number 1, retrieve it from the parser
13296      */
13297     if (cm != MoveNumberOne)
13298         LoadGameOneMove(cm);
13299
13300     /* load the remaining moves from the file */
13301     while (LoadGameOneMove(EndOfFile)) {
13302       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13303       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13304     }
13305
13306     /* rewind to the start of the game */
13307     currentMove = backwardMostMove;
13308
13309     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13310
13311     if (oldGameMode == AnalyzeFile) {
13312       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13313       AnalyzeFileEvent();
13314     } else
13315     if (oldGameMode == AnalyzeMode) {
13316       AnalyzeFileEvent();
13317     }
13318
13319     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13320         long int w, b; // [HGM] adjourn: restore saved clock times
13321         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13322         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13323             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13324             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13325         }
13326     }
13327
13328     if(creatingBook) return TRUE;
13329     if (!matchMode && pos > 0) {
13330         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13331     } else
13332     if (matchMode || appData.timeDelay == 0) {
13333       ToEndEvent();
13334     } else if (appData.timeDelay > 0) {
13335       AutoPlayGameLoop();
13336     }
13337
13338     if (appData.debugMode)
13339         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13340
13341     loadFlag = 0; /* [HGM] true game starts */
13342     return TRUE;
13343 }
13344
13345 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13346 int
13347 ReloadPosition (int offset)
13348 {
13349     int positionNumber = lastLoadPositionNumber + offset;
13350     if (lastLoadPositionFP == NULL) {
13351         DisplayError(_("No position has been loaded yet"), 0);
13352         return FALSE;
13353     }
13354     if (positionNumber <= 0) {
13355         DisplayError(_("Can't back up any further"), 0);
13356         return FALSE;
13357     }
13358     return LoadPosition(lastLoadPositionFP, positionNumber,
13359                         lastLoadPositionTitle);
13360 }
13361
13362 /* Load the nth position from the given file */
13363 int
13364 LoadPositionFromFile (char *filename, int n, char *title)
13365 {
13366     FILE *f;
13367     char buf[MSG_SIZ];
13368
13369     if (strcmp(filename, "-") == 0) {
13370         return LoadPosition(stdin, n, "stdin");
13371     } else {
13372         f = fopen(filename, "rb");
13373         if (f == NULL) {
13374             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13375             DisplayError(buf, errno);
13376             return FALSE;
13377         } else {
13378             return LoadPosition(f, n, title);
13379         }
13380     }
13381 }
13382
13383 /* Load the nth position from the given open file, and close it */
13384 int
13385 LoadPosition (FILE *f, int positionNumber, char *title)
13386 {
13387     char *p, line[MSG_SIZ];
13388     Board initial_position;
13389     int i, j, fenMode, pn;
13390
13391     if (gameMode == Training )
13392         SetTrainingModeOff();
13393
13394     if (gameMode != BeginningOfGame) {
13395         Reset(FALSE, TRUE);
13396     }
13397     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13398         fclose(lastLoadPositionFP);
13399     }
13400     if (positionNumber == 0) positionNumber = 1;
13401     lastLoadPositionFP = f;
13402     lastLoadPositionNumber = positionNumber;
13403     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13404     if (first.pr == NoProc && !appData.noChessProgram) {
13405       StartChessProgram(&first);
13406       InitChessProgram(&first, FALSE);
13407     }
13408     pn = positionNumber;
13409     if (positionNumber < 0) {
13410         /* Negative position number means to seek to that byte offset */
13411         if (fseek(f, -positionNumber, 0) == -1) {
13412             DisplayError(_("Can't seek on position file"), 0);
13413             return FALSE;
13414         };
13415         pn = 1;
13416     } else {
13417         if (fseek(f, 0, 0) == -1) {
13418             if (f == lastLoadPositionFP ?
13419                 positionNumber == lastLoadPositionNumber + 1 :
13420                 positionNumber == 1) {
13421                 pn = 1;
13422             } else {
13423                 DisplayError(_("Can't seek on position file"), 0);
13424                 return FALSE;
13425             }
13426         }
13427     }
13428     /* See if this file is FEN or old-style xboard */
13429     if (fgets(line, MSG_SIZ, f) == NULL) {
13430         DisplayError(_("Position not found in file"), 0);
13431         return FALSE;
13432     }
13433     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13434     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13435
13436     if (pn >= 2) {
13437         if (fenMode || line[0] == '#') pn--;
13438         while (pn > 0) {
13439             /* skip positions before number pn */
13440             if (fgets(line, MSG_SIZ, f) == NULL) {
13441                 Reset(TRUE, TRUE);
13442                 DisplayError(_("Position not found in file"), 0);
13443                 return FALSE;
13444             }
13445             if (fenMode || line[0] == '#') pn--;
13446         }
13447     }
13448
13449     if (fenMode) {
13450         char *p;
13451         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13452             DisplayError(_("Bad FEN position in file"), 0);
13453             return FALSE;
13454         }
13455         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13456             sscanf(p+3, "%s", bestMove);
13457         } else *bestMove = NULLCHAR;
13458     } else {
13459         (void) fgets(line, MSG_SIZ, f);
13460         (void) fgets(line, MSG_SIZ, f);
13461
13462         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13463             (void) fgets(line, MSG_SIZ, f);
13464             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13465                 if (*p == ' ')
13466                   continue;
13467                 initial_position[i][j++] = CharToPiece(*p);
13468             }
13469         }
13470
13471         blackPlaysFirst = FALSE;
13472         if (!feof(f)) {
13473             (void) fgets(line, MSG_SIZ, f);
13474             if (strncmp(line, "black", strlen("black"))==0)
13475               blackPlaysFirst = TRUE;
13476         }
13477     }
13478     startedFromSetupPosition = TRUE;
13479
13480     CopyBoard(boards[0], initial_position);
13481     if (blackPlaysFirst) {
13482         currentMove = forwardMostMove = backwardMostMove = 1;
13483         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13484         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13485         CopyBoard(boards[1], initial_position);
13486         DisplayMessage("", _("Black to play"));
13487     } else {
13488         currentMove = forwardMostMove = backwardMostMove = 0;
13489         DisplayMessage("", _("White to play"));
13490     }
13491     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13492     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13493         SendToProgram("force\n", &first);
13494         SendBoard(&first, forwardMostMove);
13495     }
13496     if (appData.debugMode) {
13497 int i, j;
13498   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13499   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13500         fprintf(debugFP, "Load Position\n");
13501     }
13502
13503     if (positionNumber > 1) {
13504       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13505         DisplayTitle(line);
13506     } else {
13507         DisplayTitle(title);
13508     }
13509     gameMode = EditGame;
13510     ModeHighlight();
13511     ResetClocks();
13512     timeRemaining[0][1] = whiteTimeRemaining;
13513     timeRemaining[1][1] = blackTimeRemaining;
13514     DrawPosition(FALSE, boards[currentMove]);
13515
13516     return TRUE;
13517 }
13518
13519
13520 void
13521 CopyPlayerNameIntoFileName (char **dest, char *src)
13522 {
13523     while (*src != NULLCHAR && *src != ',') {
13524         if (*src == ' ') {
13525             *(*dest)++ = '_';
13526             src++;
13527         } else {
13528             *(*dest)++ = *src++;
13529         }
13530     }
13531 }
13532
13533 char *
13534 DefaultFileName (char *ext)
13535 {
13536     static char def[MSG_SIZ];
13537     char *p;
13538
13539     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13540         p = def;
13541         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13542         *p++ = '-';
13543         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13544         *p++ = '.';
13545         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13546     } else {
13547         def[0] = NULLCHAR;
13548     }
13549     return def;
13550 }
13551
13552 /* Save the current game to the given file */
13553 int
13554 SaveGameToFile (char *filename, int append)
13555 {
13556     FILE *f;
13557     char buf[MSG_SIZ];
13558     int result, i, t,tot=0;
13559
13560     if (strcmp(filename, "-") == 0) {
13561         return SaveGame(stdout, 0, NULL);
13562     } else {
13563         for(i=0; i<10; i++) { // upto 10 tries
13564              f = fopen(filename, append ? "a" : "w");
13565              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13566              if(f || errno != 13) break;
13567              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13568              tot += t;
13569         }
13570         if (f == NULL) {
13571             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13572             DisplayError(buf, errno);
13573             return FALSE;
13574         } else {
13575             safeStrCpy(buf, lastMsg, MSG_SIZ);
13576             DisplayMessage(_("Waiting for access to save file"), "");
13577             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13578             DisplayMessage(_("Saving game"), "");
13579             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13580             result = SaveGame(f, 0, NULL);
13581             DisplayMessage(buf, "");
13582             return result;
13583         }
13584     }
13585 }
13586
13587 char *
13588 SavePart (char *str)
13589 {
13590     static char buf[MSG_SIZ];
13591     char *p;
13592
13593     p = strchr(str, ' ');
13594     if (p == NULL) return str;
13595     strncpy(buf, str, p - str);
13596     buf[p - str] = NULLCHAR;
13597     return buf;
13598 }
13599
13600 #define PGN_MAX_LINE 75
13601
13602 #define PGN_SIDE_WHITE  0
13603 #define PGN_SIDE_BLACK  1
13604
13605 static int
13606 FindFirstMoveOutOfBook (int side)
13607 {
13608     int result = -1;
13609
13610     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13611         int index = backwardMostMove;
13612         int has_book_hit = 0;
13613
13614         if( (index % 2) != side ) {
13615             index++;
13616         }
13617
13618         while( index < forwardMostMove ) {
13619             /* Check to see if engine is in book */
13620             int depth = pvInfoList[index].depth;
13621             int score = pvInfoList[index].score;
13622             int in_book = 0;
13623
13624             if( depth <= 2 ) {
13625                 in_book = 1;
13626             }
13627             else if( score == 0 && depth == 63 ) {
13628                 in_book = 1; /* Zappa */
13629             }
13630             else if( score == 2 && depth == 99 ) {
13631                 in_book = 1; /* Abrok */
13632             }
13633
13634             has_book_hit += in_book;
13635
13636             if( ! in_book ) {
13637                 result = index;
13638
13639                 break;
13640             }
13641
13642             index += 2;
13643         }
13644     }
13645
13646     return result;
13647 }
13648
13649 void
13650 GetOutOfBookInfo (char * buf)
13651 {
13652     int oob[2];
13653     int i;
13654     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13655
13656     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13657     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13658
13659     *buf = '\0';
13660
13661     if( oob[0] >= 0 || oob[1] >= 0 ) {
13662         for( i=0; i<2; i++ ) {
13663             int idx = oob[i];
13664
13665             if( idx >= 0 ) {
13666                 if( i > 0 && oob[0] >= 0 ) {
13667                     strcat( buf, "   " );
13668                 }
13669
13670                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13671                 sprintf( buf+strlen(buf), "%s%.2f",
13672                     pvInfoList[idx].score >= 0 ? "+" : "",
13673                     pvInfoList[idx].score / 100.0 );
13674             }
13675         }
13676     }
13677 }
13678
13679 /* Save game in PGN style */
13680 static void
13681 SaveGamePGN2 (FILE *f)
13682 {
13683     int i, offset, linelen, newblock;
13684 //    char *movetext;
13685     char numtext[32];
13686     int movelen, numlen, blank;
13687     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13688
13689     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13690
13691     PrintPGNTags(f, &gameInfo);
13692
13693     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13694
13695     if (backwardMostMove > 0 || startedFromSetupPosition) {
13696         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13697         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13698         fprintf(f, "\n{--------------\n");
13699         PrintPosition(f, backwardMostMove);
13700         fprintf(f, "--------------}\n");
13701         free(fen);
13702     }
13703     else {
13704         /* [AS] Out of book annotation */
13705         if( appData.saveOutOfBookInfo ) {
13706             char buf[64];
13707
13708             GetOutOfBookInfo( buf );
13709
13710             if( buf[0] != '\0' ) {
13711                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13712             }
13713         }
13714
13715         fprintf(f, "\n");
13716     }
13717
13718     i = backwardMostMove;
13719     linelen = 0;
13720     newblock = TRUE;
13721
13722     while (i < forwardMostMove) {
13723         /* Print comments preceding this move */
13724         if (commentList[i] != NULL) {
13725             if (linelen > 0) fprintf(f, "\n");
13726             fprintf(f, "%s", commentList[i]);
13727             linelen = 0;
13728             newblock = TRUE;
13729         }
13730
13731         /* Format move number */
13732         if ((i % 2) == 0)
13733           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13734         else
13735           if (newblock)
13736             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13737           else
13738             numtext[0] = NULLCHAR;
13739
13740         numlen = strlen(numtext);
13741         newblock = FALSE;
13742
13743         /* Print move number */
13744         blank = linelen > 0 && numlen > 0;
13745         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13746             fprintf(f, "\n");
13747             linelen = 0;
13748             blank = 0;
13749         }
13750         if (blank) {
13751             fprintf(f, " ");
13752             linelen++;
13753         }
13754         fprintf(f, "%s", numtext);
13755         linelen += numlen;
13756
13757         /* Get move */
13758         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13759         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13760
13761         /* Print move */
13762         blank = linelen > 0 && movelen > 0;
13763         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13764             fprintf(f, "\n");
13765             linelen = 0;
13766             blank = 0;
13767         }
13768         if (blank) {
13769             fprintf(f, " ");
13770             linelen++;
13771         }
13772         fprintf(f, "%s", move_buffer);
13773         linelen += movelen;
13774
13775         /* [AS] Add PV info if present */
13776         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13777             /* [HGM] add time */
13778             char buf[MSG_SIZ]; int seconds;
13779
13780             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13781
13782             if( seconds <= 0)
13783               buf[0] = 0;
13784             else
13785               if( seconds < 30 )
13786                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13787               else
13788                 {
13789                   seconds = (seconds + 4)/10; // round to full seconds
13790                   if( seconds < 60 )
13791                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13792                   else
13793                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13794                 }
13795
13796             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13797                       pvInfoList[i].score >= 0 ? "+" : "",
13798                       pvInfoList[i].score / 100.0,
13799                       pvInfoList[i].depth,
13800                       buf );
13801
13802             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13803
13804             /* Print score/depth */
13805             blank = linelen > 0 && movelen > 0;
13806             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13807                 fprintf(f, "\n");
13808                 linelen = 0;
13809                 blank = 0;
13810             }
13811             if (blank) {
13812                 fprintf(f, " ");
13813                 linelen++;
13814             }
13815             fprintf(f, "%s", move_buffer);
13816             linelen += movelen;
13817         }
13818
13819         i++;
13820     }
13821
13822     /* Start a new line */
13823     if (linelen > 0) fprintf(f, "\n");
13824
13825     /* Print comments after last move */
13826     if (commentList[i] != NULL) {
13827         fprintf(f, "%s\n", commentList[i]);
13828     }
13829
13830     /* Print result */
13831     if (gameInfo.resultDetails != NULL &&
13832         gameInfo.resultDetails[0] != NULLCHAR) {
13833         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13834         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13835            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13836             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13837         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13838     } else {
13839         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13840     }
13841 }
13842
13843 /* Save game in PGN style and close the file */
13844 int
13845 SaveGamePGN (FILE *f)
13846 {
13847     SaveGamePGN2(f);
13848     fclose(f);
13849     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13850     return TRUE;
13851 }
13852
13853 /* Save game in old style and close the file */
13854 int
13855 SaveGameOldStyle (FILE *f)
13856 {
13857     int i, offset;
13858     time_t tm;
13859
13860     tm = time((time_t *) NULL);
13861
13862     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13863     PrintOpponents(f);
13864
13865     if (backwardMostMove > 0 || startedFromSetupPosition) {
13866         fprintf(f, "\n[--------------\n");
13867         PrintPosition(f, backwardMostMove);
13868         fprintf(f, "--------------]\n");
13869     } else {
13870         fprintf(f, "\n");
13871     }
13872
13873     i = backwardMostMove;
13874     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13875
13876     while (i < forwardMostMove) {
13877         if (commentList[i] != NULL) {
13878             fprintf(f, "[%s]\n", commentList[i]);
13879         }
13880
13881         if ((i % 2) == 1) {
13882             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13883             i++;
13884         } else {
13885             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13886             i++;
13887             if (commentList[i] != NULL) {
13888                 fprintf(f, "\n");
13889                 continue;
13890             }
13891             if (i >= forwardMostMove) {
13892                 fprintf(f, "\n");
13893                 break;
13894             }
13895             fprintf(f, "%s\n", parseList[i]);
13896             i++;
13897         }
13898     }
13899
13900     if (commentList[i] != NULL) {
13901         fprintf(f, "[%s]\n", commentList[i]);
13902     }
13903
13904     /* This isn't really the old style, but it's close enough */
13905     if (gameInfo.resultDetails != NULL &&
13906         gameInfo.resultDetails[0] != NULLCHAR) {
13907         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13908                 gameInfo.resultDetails);
13909     } else {
13910         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13911     }
13912
13913     fclose(f);
13914     return TRUE;
13915 }
13916
13917 /* Save the current game to open file f and close the file */
13918 int
13919 SaveGame (FILE *f, int dummy, char *dummy2)
13920 {
13921     if (gameMode == EditPosition) EditPositionDone(TRUE);
13922     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13923     if (appData.oldSaveStyle)
13924       return SaveGameOldStyle(f);
13925     else
13926       return SaveGamePGN(f);
13927 }
13928
13929 /* Save the current position to the given file */
13930 int
13931 SavePositionToFile (char *filename)
13932 {
13933     FILE *f;
13934     char buf[MSG_SIZ];
13935
13936     if (strcmp(filename, "-") == 0) {
13937         return SavePosition(stdout, 0, NULL);
13938     } else {
13939         f = fopen(filename, "a");
13940         if (f == NULL) {
13941             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13942             DisplayError(buf, errno);
13943             return FALSE;
13944         } else {
13945             safeStrCpy(buf, lastMsg, MSG_SIZ);
13946             DisplayMessage(_("Waiting for access to save file"), "");
13947             flock(fileno(f), LOCK_EX); // [HGM] lock
13948             DisplayMessage(_("Saving position"), "");
13949             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13950             SavePosition(f, 0, NULL);
13951             DisplayMessage(buf, "");
13952             return TRUE;
13953         }
13954     }
13955 }
13956
13957 /* Save the current position to the given open file and close the file */
13958 int
13959 SavePosition (FILE *f, int dummy, char *dummy2)
13960 {
13961     time_t tm;
13962     char *fen;
13963
13964     if (gameMode == EditPosition) EditPositionDone(TRUE);
13965     if (appData.oldSaveStyle) {
13966         tm = time((time_t *) NULL);
13967
13968         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13969         PrintOpponents(f);
13970         fprintf(f, "[--------------\n");
13971         PrintPosition(f, currentMove);
13972         fprintf(f, "--------------]\n");
13973     } else {
13974         fen = PositionToFEN(currentMove, NULL, 1);
13975         fprintf(f, "%s\n", fen);
13976         free(fen);
13977     }
13978     fclose(f);
13979     return TRUE;
13980 }
13981
13982 void
13983 ReloadCmailMsgEvent (int unregister)
13984 {
13985 #if !WIN32
13986     static char *inFilename = NULL;
13987     static char *outFilename;
13988     int i;
13989     struct stat inbuf, outbuf;
13990     int status;
13991
13992     /* Any registered moves are unregistered if unregister is set, */
13993     /* i.e. invoked by the signal handler */
13994     if (unregister) {
13995         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13996             cmailMoveRegistered[i] = FALSE;
13997             if (cmailCommentList[i] != NULL) {
13998                 free(cmailCommentList[i]);
13999                 cmailCommentList[i] = NULL;
14000             }
14001         }
14002         nCmailMovesRegistered = 0;
14003     }
14004
14005     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14006         cmailResult[i] = CMAIL_NOT_RESULT;
14007     }
14008     nCmailResults = 0;
14009
14010     if (inFilename == NULL) {
14011         /* Because the filenames are static they only get malloced once  */
14012         /* and they never get freed                                      */
14013         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14014         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14015
14016         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14017         sprintf(outFilename, "%s.out", appData.cmailGameName);
14018     }
14019
14020     status = stat(outFilename, &outbuf);
14021     if (status < 0) {
14022         cmailMailedMove = FALSE;
14023     } else {
14024         status = stat(inFilename, &inbuf);
14025         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14026     }
14027
14028     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14029        counts the games, notes how each one terminated, etc.
14030
14031        It would be nice to remove this kludge and instead gather all
14032        the information while building the game list.  (And to keep it
14033        in the game list nodes instead of having a bunch of fixed-size
14034        parallel arrays.)  Note this will require getting each game's
14035        termination from the PGN tags, as the game list builder does
14036        not process the game moves.  --mann
14037        */
14038     cmailMsgLoaded = TRUE;
14039     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14040
14041     /* Load first game in the file or popup game menu */
14042     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14043
14044 #endif /* !WIN32 */
14045     return;
14046 }
14047
14048 int
14049 RegisterMove ()
14050 {
14051     FILE *f;
14052     char string[MSG_SIZ];
14053
14054     if (   cmailMailedMove
14055         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14056         return TRUE;            /* Allow free viewing  */
14057     }
14058
14059     /* Unregister move to ensure that we don't leave RegisterMove        */
14060     /* with the move registered when the conditions for registering no   */
14061     /* longer hold                                                       */
14062     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14063         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14064         nCmailMovesRegistered --;
14065
14066         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14067           {
14068               free(cmailCommentList[lastLoadGameNumber - 1]);
14069               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14070           }
14071     }
14072
14073     if (cmailOldMove == -1) {
14074         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14075         return FALSE;
14076     }
14077
14078     if (currentMove > cmailOldMove + 1) {
14079         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14080         return FALSE;
14081     }
14082
14083     if (currentMove < cmailOldMove) {
14084         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14085         return FALSE;
14086     }
14087
14088     if (forwardMostMove > currentMove) {
14089         /* Silently truncate extra moves */
14090         TruncateGame();
14091     }
14092
14093     if (   (currentMove == cmailOldMove + 1)
14094         || (   (currentMove == cmailOldMove)
14095             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14096                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14097         if (gameInfo.result != GameUnfinished) {
14098             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14099         }
14100
14101         if (commentList[currentMove] != NULL) {
14102             cmailCommentList[lastLoadGameNumber - 1]
14103               = StrSave(commentList[currentMove]);
14104         }
14105         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14106
14107         if (appData.debugMode)
14108           fprintf(debugFP, "Saving %s for game %d\n",
14109                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14110
14111         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14112
14113         f = fopen(string, "w");
14114         if (appData.oldSaveStyle) {
14115             SaveGameOldStyle(f); /* also closes the file */
14116
14117             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14118             f = fopen(string, "w");
14119             SavePosition(f, 0, NULL); /* also closes the file */
14120         } else {
14121             fprintf(f, "{--------------\n");
14122             PrintPosition(f, currentMove);
14123             fprintf(f, "--------------}\n\n");
14124
14125             SaveGame(f, 0, NULL); /* also closes the file*/
14126         }
14127
14128         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14129         nCmailMovesRegistered ++;
14130     } else if (nCmailGames == 1) {
14131         DisplayError(_("You have not made a move yet"), 0);
14132         return FALSE;
14133     }
14134
14135     return TRUE;
14136 }
14137
14138 void
14139 MailMoveEvent ()
14140 {
14141 #if !WIN32
14142     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14143     FILE *commandOutput;
14144     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14145     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14146     int nBuffers;
14147     int i;
14148     int archived;
14149     char *arcDir;
14150
14151     if (! cmailMsgLoaded) {
14152         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14153         return;
14154     }
14155
14156     if (nCmailGames == nCmailResults) {
14157         DisplayError(_("No unfinished games"), 0);
14158         return;
14159     }
14160
14161 #if CMAIL_PROHIBIT_REMAIL
14162     if (cmailMailedMove) {
14163       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);
14164         DisplayError(msg, 0);
14165         return;
14166     }
14167 #endif
14168
14169     if (! (cmailMailedMove || RegisterMove())) return;
14170
14171     if (   cmailMailedMove
14172         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14173       snprintf(string, MSG_SIZ, partCommandString,
14174                appData.debugMode ? " -v" : "", appData.cmailGameName);
14175         commandOutput = popen(string, "r");
14176
14177         if (commandOutput == NULL) {
14178             DisplayError(_("Failed to invoke cmail"), 0);
14179         } else {
14180             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14181                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14182             }
14183             if (nBuffers > 1) {
14184                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14185                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14186                 nBytes = MSG_SIZ - 1;
14187             } else {
14188                 (void) memcpy(msg, buffer, nBytes);
14189             }
14190             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14191
14192             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14193                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14194
14195                 archived = TRUE;
14196                 for (i = 0; i < nCmailGames; i ++) {
14197                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14198                         archived = FALSE;
14199                     }
14200                 }
14201                 if (   archived
14202                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14203                         != NULL)) {
14204                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14205                            arcDir,
14206                            appData.cmailGameName,
14207                            gameInfo.date);
14208                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14209                     cmailMsgLoaded = FALSE;
14210                 }
14211             }
14212
14213             DisplayInformation(msg);
14214             pclose(commandOutput);
14215         }
14216     } else {
14217         if ((*cmailMsg) != '\0') {
14218             DisplayInformation(cmailMsg);
14219         }
14220     }
14221
14222     return;
14223 #endif /* !WIN32 */
14224 }
14225
14226 char *
14227 CmailMsg ()
14228 {
14229 #if WIN32
14230     return NULL;
14231 #else
14232     int  prependComma = 0;
14233     char number[5];
14234     char string[MSG_SIZ];       /* Space for game-list */
14235     int  i;
14236
14237     if (!cmailMsgLoaded) return "";
14238
14239     if (cmailMailedMove) {
14240       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14241     } else {
14242         /* Create a list of games left */
14243       snprintf(string, MSG_SIZ, "[");
14244         for (i = 0; i < nCmailGames; i ++) {
14245             if (! (   cmailMoveRegistered[i]
14246                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14247                 if (prependComma) {
14248                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14249                 } else {
14250                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14251                     prependComma = 1;
14252                 }
14253
14254                 strcat(string, number);
14255             }
14256         }
14257         strcat(string, "]");
14258
14259         if (nCmailMovesRegistered + nCmailResults == 0) {
14260             switch (nCmailGames) {
14261               case 1:
14262                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14263                 break;
14264
14265               case 2:
14266                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14267                 break;
14268
14269               default:
14270                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14271                          nCmailGames);
14272                 break;
14273             }
14274         } else {
14275             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14276               case 1:
14277                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14278                          string);
14279                 break;
14280
14281               case 0:
14282                 if (nCmailResults == nCmailGames) {
14283                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14284                 } else {
14285                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14286                 }
14287                 break;
14288
14289               default:
14290                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14291                          string);
14292             }
14293         }
14294     }
14295     return cmailMsg;
14296 #endif /* WIN32 */
14297 }
14298
14299 void
14300 ResetGameEvent ()
14301 {
14302     if (gameMode == Training)
14303       SetTrainingModeOff();
14304
14305     Reset(TRUE, TRUE);
14306     cmailMsgLoaded = FALSE;
14307     if (appData.icsActive) {
14308       SendToICS(ics_prefix);
14309       SendToICS("refresh\n");
14310     }
14311 }
14312
14313 void
14314 ExitEvent (int status)
14315 {
14316     exiting++;
14317     if (exiting > 2) {
14318       /* Give up on clean exit */
14319       exit(status);
14320     }
14321     if (exiting > 1) {
14322       /* Keep trying for clean exit */
14323       return;
14324     }
14325
14326     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14327     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14328
14329     if (telnetISR != NULL) {
14330       RemoveInputSource(telnetISR);
14331     }
14332     if (icsPR != NoProc) {
14333       DestroyChildProcess(icsPR, TRUE);
14334     }
14335
14336     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14337     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14338
14339     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14340     /* make sure this other one finishes before killing it!                  */
14341     if(endingGame) { int count = 0;
14342         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14343         while(endingGame && count++ < 10) DoSleep(1);
14344         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14345     }
14346
14347     /* Kill off chess programs */
14348     if (first.pr != NoProc) {
14349         ExitAnalyzeMode();
14350
14351         DoSleep( appData.delayBeforeQuit );
14352         SendToProgram("quit\n", &first);
14353         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14354     }
14355     if (second.pr != NoProc) {
14356         DoSleep( appData.delayBeforeQuit );
14357         SendToProgram("quit\n", &second);
14358         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14359     }
14360     if (first.isr != NULL) {
14361         RemoveInputSource(first.isr);
14362     }
14363     if (second.isr != NULL) {
14364         RemoveInputSource(second.isr);
14365     }
14366
14367     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14368     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14369
14370     ShutDownFrontEnd();
14371     exit(status);
14372 }
14373
14374 void
14375 PauseEngine (ChessProgramState *cps)
14376 {
14377     SendToProgram("pause\n", cps);
14378     cps->pause = 2;
14379 }
14380
14381 void
14382 UnPauseEngine (ChessProgramState *cps)
14383 {
14384     SendToProgram("resume\n", cps);
14385     cps->pause = 1;
14386 }
14387
14388 void
14389 PauseEvent ()
14390 {
14391     if (appData.debugMode)
14392         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14393     if (pausing) {
14394         pausing = FALSE;
14395         ModeHighlight();
14396         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14397             StartClocks();
14398             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14399                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14400                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14401             }
14402             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14403             HandleMachineMove(stashedInputMove, stalledEngine);
14404             stalledEngine = NULL;
14405             return;
14406         }
14407         if (gameMode == MachinePlaysWhite ||
14408             gameMode == TwoMachinesPlay   ||
14409             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14410             if(first.pause)  UnPauseEngine(&first);
14411             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14412             if(second.pause) UnPauseEngine(&second);
14413             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14414             StartClocks();
14415         } else {
14416             DisplayBothClocks();
14417         }
14418         if (gameMode == PlayFromGameFile) {
14419             if (appData.timeDelay >= 0)
14420                 AutoPlayGameLoop();
14421         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14422             Reset(FALSE, TRUE);
14423             SendToICS(ics_prefix);
14424             SendToICS("refresh\n");
14425         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14426             ForwardInner(forwardMostMove);
14427         }
14428         pauseExamInvalid = FALSE;
14429     } else {
14430         switch (gameMode) {
14431           default:
14432             return;
14433           case IcsExamining:
14434             pauseExamForwardMostMove = forwardMostMove;
14435             pauseExamInvalid = FALSE;
14436             /* fall through */
14437           case IcsObserving:
14438           case IcsPlayingWhite:
14439           case IcsPlayingBlack:
14440             pausing = TRUE;
14441             ModeHighlight();
14442             return;
14443           case PlayFromGameFile:
14444             (void) StopLoadGameTimer();
14445             pausing = TRUE;
14446             ModeHighlight();
14447             break;
14448           case BeginningOfGame:
14449             if (appData.icsActive) return;
14450             /* else fall through */
14451           case MachinePlaysWhite:
14452           case MachinePlaysBlack:
14453           case TwoMachinesPlay:
14454             if (forwardMostMove == 0)
14455               return;           /* don't pause if no one has moved */
14456             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14457                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14458                 if(onMove->pause) {           // thinking engine can be paused
14459                     PauseEngine(onMove);      // do it
14460                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14461                         PauseEngine(onMove->other);
14462                     else
14463                         SendToProgram("easy\n", onMove->other);
14464                     StopClocks();
14465                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14466             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14467                 if(first.pause) {
14468                     PauseEngine(&first);
14469                     StopClocks();
14470                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14471             } else { // human on move, pause pondering by either method
14472                 if(first.pause)
14473                     PauseEngine(&first);
14474                 else if(appData.ponderNextMove)
14475                     SendToProgram("easy\n", &first);
14476                 StopClocks();
14477             }
14478             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14479           case AnalyzeMode:
14480             pausing = TRUE;
14481             ModeHighlight();
14482             break;
14483         }
14484     }
14485 }
14486
14487 void
14488 EditCommentEvent ()
14489 {
14490     char title[MSG_SIZ];
14491
14492     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14493       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14494     } else {
14495       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14496                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14497                parseList[currentMove - 1]);
14498     }
14499
14500     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14501 }
14502
14503
14504 void
14505 EditTagsEvent ()
14506 {
14507     char *tags = PGNTags(&gameInfo);
14508     bookUp = FALSE;
14509     EditTagsPopUp(tags, NULL);
14510     free(tags);
14511 }
14512
14513 void
14514 ToggleSecond ()
14515 {
14516   if(second.analyzing) {
14517     SendToProgram("exit\n", &second);
14518     second.analyzing = FALSE;
14519   } else {
14520     if (second.pr == NoProc) StartChessProgram(&second);
14521     InitChessProgram(&second, FALSE);
14522     FeedMovesToProgram(&second, currentMove);
14523
14524     SendToProgram("analyze\n", &second);
14525     second.analyzing = TRUE;
14526   }
14527 }
14528
14529 /* Toggle ShowThinking */
14530 void
14531 ToggleShowThinking()
14532 {
14533   appData.showThinking = !appData.showThinking;
14534   ShowThinkingEvent();
14535 }
14536
14537 int
14538 AnalyzeModeEvent ()
14539 {
14540     char buf[MSG_SIZ];
14541
14542     if (!first.analysisSupport) {
14543       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14544       DisplayError(buf, 0);
14545       return 0;
14546     }
14547     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14548     if (appData.icsActive) {
14549         if (gameMode != IcsObserving) {
14550           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14551             DisplayError(buf, 0);
14552             /* secure check */
14553             if (appData.icsEngineAnalyze) {
14554                 if (appData.debugMode)
14555                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14556                 ExitAnalyzeMode();
14557                 ModeHighlight();
14558             }
14559             return 0;
14560         }
14561         /* if enable, user wants to disable icsEngineAnalyze */
14562         if (appData.icsEngineAnalyze) {
14563                 ExitAnalyzeMode();
14564                 ModeHighlight();
14565                 return 0;
14566         }
14567         appData.icsEngineAnalyze = TRUE;
14568         if (appData.debugMode)
14569             fprintf(debugFP, "ICS engine analyze starting... \n");
14570     }
14571
14572     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14573     if (appData.noChessProgram || gameMode == AnalyzeMode)
14574       return 0;
14575
14576     if (gameMode != AnalyzeFile) {
14577         if (!appData.icsEngineAnalyze) {
14578                EditGameEvent();
14579                if (gameMode != EditGame) return 0;
14580         }
14581         if (!appData.showThinking) ToggleShowThinking();
14582         ResurrectChessProgram();
14583         SendToProgram("analyze\n", &first);
14584         first.analyzing = TRUE;
14585         /*first.maybeThinking = TRUE;*/
14586         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14587         EngineOutputPopUp();
14588     }
14589     if (!appData.icsEngineAnalyze) {
14590         gameMode = AnalyzeMode;
14591         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14592     }
14593     pausing = FALSE;
14594     ModeHighlight();
14595     SetGameInfo();
14596
14597     StartAnalysisClock();
14598     GetTimeMark(&lastNodeCountTime);
14599     lastNodeCount = 0;
14600     return 1;
14601 }
14602
14603 void
14604 AnalyzeFileEvent ()
14605 {
14606     if (appData.noChessProgram || gameMode == AnalyzeFile)
14607       return;
14608
14609     if (!first.analysisSupport) {
14610       char buf[MSG_SIZ];
14611       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14612       DisplayError(buf, 0);
14613       return;
14614     }
14615
14616     if (gameMode != AnalyzeMode) {
14617         keepInfo = 1; // mere annotating should not alter PGN tags
14618         EditGameEvent();
14619         keepInfo = 0;
14620         if (gameMode != EditGame) return;
14621         if (!appData.showThinking) ToggleShowThinking();
14622         ResurrectChessProgram();
14623         SendToProgram("analyze\n", &first);
14624         first.analyzing = TRUE;
14625         /*first.maybeThinking = TRUE;*/
14626         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14627         EngineOutputPopUp();
14628     }
14629     gameMode = AnalyzeFile;
14630     pausing = FALSE;
14631     ModeHighlight();
14632
14633     StartAnalysisClock();
14634     GetTimeMark(&lastNodeCountTime);
14635     lastNodeCount = 0;
14636     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14637     AnalysisPeriodicEvent(1);
14638 }
14639
14640 void
14641 MachineWhiteEvent ()
14642 {
14643     char buf[MSG_SIZ];
14644     char *bookHit = NULL;
14645
14646     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14647       return;
14648
14649
14650     if (gameMode == PlayFromGameFile ||
14651         gameMode == TwoMachinesPlay  ||
14652         gameMode == Training         ||
14653         gameMode == AnalyzeMode      ||
14654         gameMode == EndOfGame)
14655         EditGameEvent();
14656
14657     if (gameMode == EditPosition)
14658         EditPositionDone(TRUE);
14659
14660     if (!WhiteOnMove(currentMove)) {
14661         DisplayError(_("It is not White's turn"), 0);
14662         return;
14663     }
14664
14665     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14666       ExitAnalyzeMode();
14667
14668     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14669         gameMode == AnalyzeFile)
14670         TruncateGame();
14671
14672     ResurrectChessProgram();    /* in case it isn't running */
14673     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14674         gameMode = MachinePlaysWhite;
14675         ResetClocks();
14676     } else
14677     gameMode = MachinePlaysWhite;
14678     pausing = FALSE;
14679     ModeHighlight();
14680     SetGameInfo();
14681     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14682     DisplayTitle(buf);
14683     if (first.sendName) {
14684       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14685       SendToProgram(buf, &first);
14686     }
14687     if (first.sendTime) {
14688       if (first.useColors) {
14689         SendToProgram("black\n", &first); /*gnu kludge*/
14690       }
14691       SendTimeRemaining(&first, TRUE);
14692     }
14693     if (first.useColors) {
14694       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14695     }
14696     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14697     SetMachineThinkingEnables();
14698     first.maybeThinking = TRUE;
14699     StartClocks();
14700     firstMove = FALSE;
14701
14702     if (appData.autoFlipView && !flipView) {
14703       flipView = !flipView;
14704       DrawPosition(FALSE, NULL);
14705       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14706     }
14707
14708     if(bookHit) { // [HGM] book: simulate book reply
14709         static char bookMove[MSG_SIZ]; // a bit generous?
14710
14711         programStats.nodes = programStats.depth = programStats.time =
14712         programStats.score = programStats.got_only_move = 0;
14713         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14714
14715         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14716         strcat(bookMove, bookHit);
14717         HandleMachineMove(bookMove, &first);
14718     }
14719 }
14720
14721 void
14722 MachineBlackEvent ()
14723 {
14724   char buf[MSG_SIZ];
14725   char *bookHit = NULL;
14726
14727     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14728         return;
14729
14730
14731     if (gameMode == PlayFromGameFile ||
14732         gameMode == TwoMachinesPlay  ||
14733         gameMode == Training         ||
14734         gameMode == AnalyzeMode      ||
14735         gameMode == EndOfGame)
14736         EditGameEvent();
14737
14738     if (gameMode == EditPosition)
14739         EditPositionDone(TRUE);
14740
14741     if (WhiteOnMove(currentMove)) {
14742         DisplayError(_("It is not Black's turn"), 0);
14743         return;
14744     }
14745
14746     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14747       ExitAnalyzeMode();
14748
14749     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14750         gameMode == AnalyzeFile)
14751         TruncateGame();
14752
14753     ResurrectChessProgram();    /* in case it isn't running */
14754     gameMode = MachinePlaysBlack;
14755     pausing = FALSE;
14756     ModeHighlight();
14757     SetGameInfo();
14758     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14759     DisplayTitle(buf);
14760     if (first.sendName) {
14761       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14762       SendToProgram(buf, &first);
14763     }
14764     if (first.sendTime) {
14765       if (first.useColors) {
14766         SendToProgram("white\n", &first); /*gnu kludge*/
14767       }
14768       SendTimeRemaining(&first, FALSE);
14769     }
14770     if (first.useColors) {
14771       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14772     }
14773     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14774     SetMachineThinkingEnables();
14775     first.maybeThinking = TRUE;
14776     StartClocks();
14777
14778     if (appData.autoFlipView && flipView) {
14779       flipView = !flipView;
14780       DrawPosition(FALSE, NULL);
14781       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14782     }
14783     if(bookHit) { // [HGM] book: simulate book reply
14784         static char bookMove[MSG_SIZ]; // a bit generous?
14785
14786         programStats.nodes = programStats.depth = programStats.time =
14787         programStats.score = programStats.got_only_move = 0;
14788         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14789
14790         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14791         strcat(bookMove, bookHit);
14792         HandleMachineMove(bookMove, &first);
14793     }
14794 }
14795
14796
14797 void
14798 DisplayTwoMachinesTitle ()
14799 {
14800     char buf[MSG_SIZ];
14801     if (appData.matchGames > 0) {
14802         if(appData.tourneyFile[0]) {
14803           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14804                    gameInfo.white, _("vs."), gameInfo.black,
14805                    nextGame+1, appData.matchGames+1,
14806                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14807         } else
14808         if (first.twoMachinesColor[0] == 'w') {
14809           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14810                    gameInfo.white, _("vs."),  gameInfo.black,
14811                    first.matchWins, second.matchWins,
14812                    matchGame - 1 - (first.matchWins + second.matchWins));
14813         } else {
14814           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14815                    gameInfo.white, _("vs."), gameInfo.black,
14816                    second.matchWins, first.matchWins,
14817                    matchGame - 1 - (first.matchWins + second.matchWins));
14818         }
14819     } else {
14820       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14821     }
14822     DisplayTitle(buf);
14823 }
14824
14825 void
14826 SettingsMenuIfReady ()
14827 {
14828   if (second.lastPing != second.lastPong) {
14829     DisplayMessage("", _("Waiting for second chess program"));
14830     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14831     return;
14832   }
14833   ThawUI();
14834   DisplayMessage("", "");
14835   SettingsPopUp(&second);
14836 }
14837
14838 int
14839 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14840 {
14841     char buf[MSG_SIZ];
14842     if (cps->pr == NoProc) {
14843         StartChessProgram(cps);
14844         if (cps->protocolVersion == 1) {
14845           retry();
14846           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14847         } else {
14848           /* kludge: allow timeout for initial "feature" command */
14849           if(retry != TwoMachinesEventIfReady) FreezeUI();
14850           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14851           DisplayMessage("", buf);
14852           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14853         }
14854         return 1;
14855     }
14856     return 0;
14857 }
14858
14859 void
14860 TwoMachinesEvent P((void))
14861 {
14862     int i;
14863     char buf[MSG_SIZ];
14864     ChessProgramState *onmove;
14865     char *bookHit = NULL;
14866     static int stalling = 0;
14867     TimeMark now;
14868     long wait;
14869
14870     if (appData.noChessProgram) return;
14871
14872     switch (gameMode) {
14873       case TwoMachinesPlay:
14874         return;
14875       case MachinePlaysWhite:
14876       case MachinePlaysBlack:
14877         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14878             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14879             return;
14880         }
14881         /* fall through */
14882       case BeginningOfGame:
14883       case PlayFromGameFile:
14884       case EndOfGame:
14885         EditGameEvent();
14886         if (gameMode != EditGame) return;
14887         break;
14888       case EditPosition:
14889         EditPositionDone(TRUE);
14890         break;
14891       case AnalyzeMode:
14892       case AnalyzeFile:
14893         ExitAnalyzeMode();
14894         break;
14895       case EditGame:
14896       default:
14897         break;
14898     }
14899
14900 //    forwardMostMove = currentMove;
14901     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14902     startingEngine = TRUE;
14903
14904     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14905
14906     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14907     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14908       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14909       return;
14910     }
14911     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14912
14913     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14914                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14915         startingEngine = matchMode = FALSE;
14916         DisplayError("second engine does not play this", 0);
14917         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14918         EditGameEvent(); // switch back to EditGame mode
14919         return;
14920     }
14921
14922     if(!stalling) {
14923       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14924       SendToProgram("force\n", &second);
14925       stalling = 1;
14926       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14927       return;
14928     }
14929     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14930     if(appData.matchPause>10000 || appData.matchPause<10)
14931                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14932     wait = SubtractTimeMarks(&now, &pauseStart);
14933     if(wait < appData.matchPause) {
14934         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14935         return;
14936     }
14937     // we are now committed to starting the game
14938     stalling = 0;
14939     DisplayMessage("", "");
14940     if (startedFromSetupPosition) {
14941         SendBoard(&second, backwardMostMove);
14942     if (appData.debugMode) {
14943         fprintf(debugFP, "Two Machines\n");
14944     }
14945     }
14946     for (i = backwardMostMove; i < forwardMostMove; i++) {
14947         SendMoveToProgram(i, &second);
14948     }
14949
14950     gameMode = TwoMachinesPlay;
14951     pausing = startingEngine = FALSE;
14952     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14953     SetGameInfo();
14954     DisplayTwoMachinesTitle();
14955     firstMove = TRUE;
14956     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14957         onmove = &first;
14958     } else {
14959         onmove = &second;
14960     }
14961     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14962     SendToProgram(first.computerString, &first);
14963     if (first.sendName) {
14964       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14965       SendToProgram(buf, &first);
14966     }
14967     SendToProgram(second.computerString, &second);
14968     if (second.sendName) {
14969       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14970       SendToProgram(buf, &second);
14971     }
14972
14973     ResetClocks();
14974     if (!first.sendTime || !second.sendTime) {
14975         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14976         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14977     }
14978     if (onmove->sendTime) {
14979       if (onmove->useColors) {
14980         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14981       }
14982       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14983     }
14984     if (onmove->useColors) {
14985       SendToProgram(onmove->twoMachinesColor, onmove);
14986     }
14987     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14988 //    SendToProgram("go\n", onmove);
14989     onmove->maybeThinking = TRUE;
14990     SetMachineThinkingEnables();
14991
14992     StartClocks();
14993
14994     if(bookHit) { // [HGM] book: simulate book reply
14995         static char bookMove[MSG_SIZ]; // a bit generous?
14996
14997         programStats.nodes = programStats.depth = programStats.time =
14998         programStats.score = programStats.got_only_move = 0;
14999         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15000
15001         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15002         strcat(bookMove, bookHit);
15003         savedMessage = bookMove; // args for deferred call
15004         savedState = onmove;
15005         ScheduleDelayedEvent(DeferredBookMove, 1);
15006     }
15007 }
15008
15009 void
15010 TrainingEvent ()
15011 {
15012     if (gameMode == Training) {
15013       SetTrainingModeOff();
15014       gameMode = PlayFromGameFile;
15015       DisplayMessage("", _("Training mode off"));
15016     } else {
15017       gameMode = Training;
15018       animateTraining = appData.animate;
15019
15020       /* make sure we are not already at the end of the game */
15021       if (currentMove < forwardMostMove) {
15022         SetTrainingModeOn();
15023         DisplayMessage("", _("Training mode on"));
15024       } else {
15025         gameMode = PlayFromGameFile;
15026         DisplayError(_("Already at end of game"), 0);
15027       }
15028     }
15029     ModeHighlight();
15030 }
15031
15032 void
15033 IcsClientEvent ()
15034 {
15035     if (!appData.icsActive) return;
15036     switch (gameMode) {
15037       case IcsPlayingWhite:
15038       case IcsPlayingBlack:
15039       case IcsObserving:
15040       case IcsIdle:
15041       case BeginningOfGame:
15042       case IcsExamining:
15043         return;
15044
15045       case EditGame:
15046         break;
15047
15048       case EditPosition:
15049         EditPositionDone(TRUE);
15050         break;
15051
15052       case AnalyzeMode:
15053       case AnalyzeFile:
15054         ExitAnalyzeMode();
15055         break;
15056
15057       default:
15058         EditGameEvent();
15059         break;
15060     }
15061
15062     gameMode = IcsIdle;
15063     ModeHighlight();
15064     return;
15065 }
15066
15067 void
15068 EditGameEvent ()
15069 {
15070     int i;
15071
15072     switch (gameMode) {
15073       case Training:
15074         SetTrainingModeOff();
15075         break;
15076       case MachinePlaysWhite:
15077       case MachinePlaysBlack:
15078       case BeginningOfGame:
15079         SendToProgram("force\n", &first);
15080         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15081             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15082                 char buf[MSG_SIZ];
15083                 abortEngineThink = TRUE;
15084                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15085                 SendToProgram(buf, &first);
15086                 DisplayMessage("Aborting engine think", "");
15087                 FreezeUI();
15088             }
15089         }
15090         SetUserThinkingEnables();
15091         break;
15092       case PlayFromGameFile:
15093         (void) StopLoadGameTimer();
15094         if (gameFileFP != NULL) {
15095             gameFileFP = NULL;
15096         }
15097         break;
15098       case EditPosition:
15099         EditPositionDone(TRUE);
15100         break;
15101       case AnalyzeMode:
15102       case AnalyzeFile:
15103         ExitAnalyzeMode();
15104         SendToProgram("force\n", &first);
15105         break;
15106       case TwoMachinesPlay:
15107         GameEnds(EndOfFile, NULL, GE_PLAYER);
15108         ResurrectChessProgram();
15109         SetUserThinkingEnables();
15110         break;
15111       case EndOfGame:
15112         ResurrectChessProgram();
15113         break;
15114       case IcsPlayingBlack:
15115       case IcsPlayingWhite:
15116         DisplayError(_("Warning: You are still playing a game"), 0);
15117         break;
15118       case IcsObserving:
15119         DisplayError(_("Warning: You are still observing a game"), 0);
15120         break;
15121       case IcsExamining:
15122         DisplayError(_("Warning: You are still examining a game"), 0);
15123         break;
15124       case IcsIdle:
15125         break;
15126       case EditGame:
15127       default:
15128         return;
15129     }
15130
15131     pausing = FALSE;
15132     StopClocks();
15133     first.offeredDraw = second.offeredDraw = 0;
15134
15135     if (gameMode == PlayFromGameFile) {
15136         whiteTimeRemaining = timeRemaining[0][currentMove];
15137         blackTimeRemaining = timeRemaining[1][currentMove];
15138         DisplayTitle("");
15139     }
15140
15141     if (gameMode == MachinePlaysWhite ||
15142         gameMode == MachinePlaysBlack ||
15143         gameMode == TwoMachinesPlay ||
15144         gameMode == EndOfGame) {
15145         i = forwardMostMove;
15146         while (i > currentMove) {
15147             SendToProgram("undo\n", &first);
15148             i--;
15149         }
15150         if(!adjustedClock) {
15151         whiteTimeRemaining = timeRemaining[0][currentMove];
15152         blackTimeRemaining = timeRemaining[1][currentMove];
15153         DisplayBothClocks();
15154         }
15155         if (whiteFlag || blackFlag) {
15156             whiteFlag = blackFlag = 0;
15157         }
15158         DisplayTitle("");
15159     }
15160
15161     gameMode = EditGame;
15162     ModeHighlight();
15163     SetGameInfo();
15164 }
15165
15166
15167 void
15168 EditPositionEvent ()
15169 {
15170     if (gameMode == EditPosition) {
15171         EditGameEvent();
15172         return;
15173     }
15174
15175     EditGameEvent();
15176     if (gameMode != EditGame) return;
15177
15178     gameMode = EditPosition;
15179     ModeHighlight();
15180     SetGameInfo();
15181     if (currentMove > 0)
15182       CopyBoard(boards[0], boards[currentMove]);
15183
15184     blackPlaysFirst = !WhiteOnMove(currentMove);
15185     ResetClocks();
15186     currentMove = forwardMostMove = backwardMostMove = 0;
15187     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15188     DisplayMove(-1);
15189     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15190 }
15191
15192 void
15193 ExitAnalyzeMode ()
15194 {
15195     /* [DM] icsEngineAnalyze - possible call from other functions */
15196     if (appData.icsEngineAnalyze) {
15197         appData.icsEngineAnalyze = FALSE;
15198
15199         DisplayMessage("",_("Close ICS engine analyze..."));
15200     }
15201     if (first.analysisSupport && first.analyzing) {
15202       SendToBoth("exit\n");
15203       first.analyzing = second.analyzing = FALSE;
15204     }
15205     thinkOutput[0] = NULLCHAR;
15206 }
15207
15208 void
15209 EditPositionDone (Boolean fakeRights)
15210 {
15211     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15212
15213     startedFromSetupPosition = TRUE;
15214     InitChessProgram(&first, FALSE);
15215     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15216       boards[0][EP_STATUS] = EP_NONE;
15217       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15218       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15219         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15220         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15221       } else boards[0][CASTLING][2] = NoRights;
15222       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15223         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15224         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15225       } else boards[0][CASTLING][5] = NoRights;
15226       if(gameInfo.variant == VariantSChess) {
15227         int i;
15228         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15229           boards[0][VIRGIN][i] = 0;
15230           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15231           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15232         }
15233       }
15234     }
15235     SendToProgram("force\n", &first);
15236     if (blackPlaysFirst) {
15237         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15238         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15239         currentMove = forwardMostMove = backwardMostMove = 1;
15240         CopyBoard(boards[1], boards[0]);
15241     } else {
15242         currentMove = forwardMostMove = backwardMostMove = 0;
15243     }
15244     SendBoard(&first, forwardMostMove);
15245     if (appData.debugMode) {
15246         fprintf(debugFP, "EditPosDone\n");
15247     }
15248     DisplayTitle("");
15249     DisplayMessage("", "");
15250     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15251     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15252     gameMode = EditGame;
15253     ModeHighlight();
15254     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15255     ClearHighlights(); /* [AS] */
15256 }
15257
15258 /* Pause for `ms' milliseconds */
15259 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15260 void
15261 TimeDelay (long ms)
15262 {
15263     TimeMark m1, m2;
15264
15265     GetTimeMark(&m1);
15266     do {
15267         GetTimeMark(&m2);
15268     } while (SubtractTimeMarks(&m2, &m1) < ms);
15269 }
15270
15271 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15272 void
15273 SendMultiLineToICS (char *buf)
15274 {
15275     char temp[MSG_SIZ+1], *p;
15276     int len;
15277
15278     len = strlen(buf);
15279     if (len > MSG_SIZ)
15280       len = MSG_SIZ;
15281
15282     strncpy(temp, buf, len);
15283     temp[len] = 0;
15284
15285     p = temp;
15286     while (*p) {
15287         if (*p == '\n' || *p == '\r')
15288           *p = ' ';
15289         ++p;
15290     }
15291
15292     strcat(temp, "\n");
15293     SendToICS(temp);
15294     SendToPlayer(temp, strlen(temp));
15295 }
15296
15297 void
15298 SetWhiteToPlayEvent ()
15299 {
15300     if (gameMode == EditPosition) {
15301         blackPlaysFirst = FALSE;
15302         DisplayBothClocks();    /* works because currentMove is 0 */
15303     } else if (gameMode == IcsExamining) {
15304         SendToICS(ics_prefix);
15305         SendToICS("tomove white\n");
15306     }
15307 }
15308
15309 void
15310 SetBlackToPlayEvent ()
15311 {
15312     if (gameMode == EditPosition) {
15313         blackPlaysFirst = TRUE;
15314         currentMove = 1;        /* kludge */
15315         DisplayBothClocks();
15316         currentMove = 0;
15317     } else if (gameMode == IcsExamining) {
15318         SendToICS(ics_prefix);
15319         SendToICS("tomove black\n");
15320     }
15321 }
15322
15323 void
15324 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15325 {
15326     char buf[MSG_SIZ];
15327     ChessSquare piece = boards[0][y][x];
15328     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15329     static int lastVariant;
15330
15331     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15332
15333     switch (selection) {
15334       case ClearBoard:
15335         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15336         MarkTargetSquares(1);
15337         CopyBoard(currentBoard, boards[0]);
15338         CopyBoard(menuBoard, initialPosition);
15339         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15340             SendToICS(ics_prefix);
15341             SendToICS("bsetup clear\n");
15342         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15343             SendToICS(ics_prefix);
15344             SendToICS("clearboard\n");
15345         } else {
15346             int nonEmpty = 0;
15347             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15348                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15349                 for (y = 0; y < BOARD_HEIGHT; y++) {
15350                     if (gameMode == IcsExamining) {
15351                         if (boards[currentMove][y][x] != EmptySquare) {
15352                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15353                                     AAA + x, ONE + y);
15354                             SendToICS(buf);
15355                         }
15356                     } else if(boards[0][y][x] != DarkSquare) {
15357                         if(boards[0][y][x] != p) nonEmpty++;
15358                         boards[0][y][x] = p;
15359                     }
15360                 }
15361             }
15362             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15363                 int r;
15364                 for(r = 0; r < BOARD_HEIGHT; r++) {
15365                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15366                     ChessSquare p = menuBoard[r][x];
15367                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15368                   }
15369                 }
15370                 DisplayMessage("Clicking clock again restores position", "");
15371                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15372                 if(!nonEmpty) { // asked to clear an empty board
15373                     CopyBoard(boards[0], menuBoard);
15374                 } else
15375                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15376                     CopyBoard(boards[0], initialPosition);
15377                 } else
15378                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15379                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15380                     CopyBoard(boards[0], erasedBoard);
15381                 } else
15382                     CopyBoard(erasedBoard, currentBoard);
15383
15384             }
15385         }
15386         if (gameMode == EditPosition) {
15387             DrawPosition(FALSE, boards[0]);
15388         }
15389         break;
15390
15391       case WhitePlay:
15392         SetWhiteToPlayEvent();
15393         break;
15394
15395       case BlackPlay:
15396         SetBlackToPlayEvent();
15397         break;
15398
15399       case EmptySquare:
15400         if (gameMode == IcsExamining) {
15401             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15402             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15403             SendToICS(buf);
15404         } else {
15405             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15406                 if(x == BOARD_LEFT-2) {
15407                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15408                     boards[0][y][1] = 0;
15409                 } else
15410                 if(x == BOARD_RGHT+1) {
15411                     if(y >= gameInfo.holdingsSize) break;
15412                     boards[0][y][BOARD_WIDTH-2] = 0;
15413                 } else break;
15414             }
15415             boards[0][y][x] = EmptySquare;
15416             DrawPosition(FALSE, boards[0]);
15417         }
15418         break;
15419
15420       case PromotePiece:
15421         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15422            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15423             selection = (ChessSquare) (PROMOTED(piece));
15424         } else if(piece == EmptySquare) selection = WhiteSilver;
15425         else selection = (ChessSquare)((int)piece - 1);
15426         goto defaultlabel;
15427
15428       case DemotePiece:
15429         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15430            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15431             selection = (ChessSquare) (DEMOTED(piece));
15432         } else if(piece == EmptySquare) selection = BlackSilver;
15433         else selection = (ChessSquare)((int)piece + 1);
15434         goto defaultlabel;
15435
15436       case WhiteQueen:
15437       case BlackQueen:
15438         if(gameInfo.variant == VariantShatranj ||
15439            gameInfo.variant == VariantXiangqi  ||
15440            gameInfo.variant == VariantCourier  ||
15441            gameInfo.variant == VariantASEAN    ||
15442            gameInfo.variant == VariantMakruk     )
15443             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15444         goto defaultlabel;
15445
15446       case WhiteKing:
15447       case BlackKing:
15448         if(gameInfo.variant == VariantXiangqi)
15449             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15450         if(gameInfo.variant == VariantKnightmate)
15451             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15452       default:
15453         defaultlabel:
15454         if (gameMode == IcsExamining) {
15455             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15456             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15457                      PieceToChar(selection), AAA + x, ONE + y);
15458             SendToICS(buf);
15459         } else {
15460             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15461                 int n;
15462                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15463                     n = PieceToNumber(selection - BlackPawn);
15464                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15465                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15466                     boards[0][BOARD_HEIGHT-1-n][1]++;
15467                 } else
15468                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15469                     n = PieceToNumber(selection);
15470                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15471                     boards[0][n][BOARD_WIDTH-1] = selection;
15472                     boards[0][n][BOARD_WIDTH-2]++;
15473                 }
15474             } else
15475             boards[0][y][x] = selection;
15476             DrawPosition(TRUE, boards[0]);
15477             ClearHighlights();
15478             fromX = fromY = -1;
15479         }
15480         break;
15481     }
15482 }
15483
15484
15485 void
15486 DropMenuEvent (ChessSquare selection, int x, int y)
15487 {
15488     ChessMove moveType;
15489
15490     switch (gameMode) {
15491       case IcsPlayingWhite:
15492       case MachinePlaysBlack:
15493         if (!WhiteOnMove(currentMove)) {
15494             DisplayMoveError(_("It is Black's turn"));
15495             return;
15496         }
15497         moveType = WhiteDrop;
15498         break;
15499       case IcsPlayingBlack:
15500       case MachinePlaysWhite:
15501         if (WhiteOnMove(currentMove)) {
15502             DisplayMoveError(_("It is White's turn"));
15503             return;
15504         }
15505         moveType = BlackDrop;
15506         break;
15507       case EditGame:
15508         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15509         break;
15510       default:
15511         return;
15512     }
15513
15514     if (moveType == BlackDrop && selection < BlackPawn) {
15515       selection = (ChessSquare) ((int) selection
15516                                  + (int) BlackPawn - (int) WhitePawn);
15517     }
15518     if (boards[currentMove][y][x] != EmptySquare) {
15519         DisplayMoveError(_("That square is occupied"));
15520         return;
15521     }
15522
15523     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15524 }
15525
15526 void
15527 AcceptEvent ()
15528 {
15529     /* Accept a pending offer of any kind from opponent */
15530
15531     if (appData.icsActive) {
15532         SendToICS(ics_prefix);
15533         SendToICS("accept\n");
15534     } else if (cmailMsgLoaded) {
15535         if (currentMove == cmailOldMove &&
15536             commentList[cmailOldMove] != NULL &&
15537             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15538                    "Black offers a draw" : "White offers a draw")) {
15539             TruncateGame();
15540             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15541             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15542         } else {
15543             DisplayError(_("There is no pending offer on this move"), 0);
15544             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15545         }
15546     } else {
15547         /* Not used for offers from chess program */
15548     }
15549 }
15550
15551 void
15552 DeclineEvent ()
15553 {
15554     /* Decline a pending offer of any kind from opponent */
15555
15556     if (appData.icsActive) {
15557         SendToICS(ics_prefix);
15558         SendToICS("decline\n");
15559     } else if (cmailMsgLoaded) {
15560         if (currentMove == cmailOldMove &&
15561             commentList[cmailOldMove] != NULL &&
15562             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15563                    "Black offers a draw" : "White offers a draw")) {
15564 #ifdef NOTDEF
15565             AppendComment(cmailOldMove, "Draw declined", TRUE);
15566             DisplayComment(cmailOldMove - 1, "Draw declined");
15567 #endif /*NOTDEF*/
15568         } else {
15569             DisplayError(_("There is no pending offer on this move"), 0);
15570         }
15571     } else {
15572         /* Not used for offers from chess program */
15573     }
15574 }
15575
15576 void
15577 RematchEvent ()
15578 {
15579     /* Issue ICS rematch command */
15580     if (appData.icsActive) {
15581         SendToICS(ics_prefix);
15582         SendToICS("rematch\n");
15583     }
15584 }
15585
15586 void
15587 CallFlagEvent ()
15588 {
15589     /* Call your opponent's flag (claim a win on time) */
15590     if (appData.icsActive) {
15591         SendToICS(ics_prefix);
15592         SendToICS("flag\n");
15593     } else {
15594         switch (gameMode) {
15595           default:
15596             return;
15597           case MachinePlaysWhite:
15598             if (whiteFlag) {
15599                 if (blackFlag)
15600                   GameEnds(GameIsDrawn, "Both players ran out of time",
15601                            GE_PLAYER);
15602                 else
15603                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15604             } else {
15605                 DisplayError(_("Your opponent is not out of time"), 0);
15606             }
15607             break;
15608           case MachinePlaysBlack:
15609             if (blackFlag) {
15610                 if (whiteFlag)
15611                   GameEnds(GameIsDrawn, "Both players ran out of time",
15612                            GE_PLAYER);
15613                 else
15614                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15615             } else {
15616                 DisplayError(_("Your opponent is not out of time"), 0);
15617             }
15618             break;
15619         }
15620     }
15621 }
15622
15623 void
15624 ClockClick (int which)
15625 {       // [HGM] code moved to back-end from winboard.c
15626         if(which) { // black clock
15627           if (gameMode == EditPosition || gameMode == IcsExamining) {
15628             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15629             SetBlackToPlayEvent();
15630           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15631                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15632           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15633           } else if (shiftKey) {
15634             AdjustClock(which, -1);
15635           } else if (gameMode == IcsPlayingWhite ||
15636                      gameMode == MachinePlaysBlack) {
15637             CallFlagEvent();
15638           }
15639         } else { // white clock
15640           if (gameMode == EditPosition || gameMode == IcsExamining) {
15641             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15642             SetWhiteToPlayEvent();
15643           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15644                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15645           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15646           } else if (shiftKey) {
15647             AdjustClock(which, -1);
15648           } else if (gameMode == IcsPlayingBlack ||
15649                    gameMode == MachinePlaysWhite) {
15650             CallFlagEvent();
15651           }
15652         }
15653 }
15654
15655 void
15656 DrawEvent ()
15657 {
15658     /* Offer draw or accept pending draw offer from opponent */
15659
15660     if (appData.icsActive) {
15661         /* Note: tournament rules require draw offers to be
15662            made after you make your move but before you punch
15663            your clock.  Currently ICS doesn't let you do that;
15664            instead, you immediately punch your clock after making
15665            a move, but you can offer a draw at any time. */
15666
15667         SendToICS(ics_prefix);
15668         SendToICS("draw\n");
15669         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15670     } else if (cmailMsgLoaded) {
15671         if (currentMove == cmailOldMove &&
15672             commentList[cmailOldMove] != NULL &&
15673             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15674                    "Black offers a draw" : "White offers a draw")) {
15675             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15676             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15677         } else if (currentMove == cmailOldMove + 1) {
15678             char *offer = WhiteOnMove(cmailOldMove) ?
15679               "White offers a draw" : "Black offers a draw";
15680             AppendComment(currentMove, offer, TRUE);
15681             DisplayComment(currentMove - 1, offer);
15682             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15683         } else {
15684             DisplayError(_("You must make your move before offering a draw"), 0);
15685             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15686         }
15687     } else if (first.offeredDraw) {
15688         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15689     } else {
15690         if (first.sendDrawOffers) {
15691             SendToProgram("draw\n", &first);
15692             userOfferedDraw = TRUE;
15693         }
15694     }
15695 }
15696
15697 void
15698 AdjournEvent ()
15699 {
15700     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15701
15702     if (appData.icsActive) {
15703         SendToICS(ics_prefix);
15704         SendToICS("adjourn\n");
15705     } else {
15706         /* Currently GNU Chess doesn't offer or accept Adjourns */
15707     }
15708 }
15709
15710
15711 void
15712 AbortEvent ()
15713 {
15714     /* Offer Abort or accept pending Abort offer from opponent */
15715
15716     if (appData.icsActive) {
15717         SendToICS(ics_prefix);
15718         SendToICS("abort\n");
15719     } else {
15720         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15721     }
15722 }
15723
15724 void
15725 ResignEvent ()
15726 {
15727     /* Resign.  You can do this even if it's not your turn. */
15728
15729     if (appData.icsActive) {
15730         SendToICS(ics_prefix);
15731         SendToICS("resign\n");
15732     } else {
15733         switch (gameMode) {
15734           case MachinePlaysWhite:
15735             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15736             break;
15737           case MachinePlaysBlack:
15738             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15739             break;
15740           case EditGame:
15741             if (cmailMsgLoaded) {
15742                 TruncateGame();
15743                 if (WhiteOnMove(cmailOldMove)) {
15744                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15745                 } else {
15746                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15747                 }
15748                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15749             }
15750             break;
15751           default:
15752             break;
15753         }
15754     }
15755 }
15756
15757
15758 void
15759 StopObservingEvent ()
15760 {
15761     /* Stop observing current games */
15762     SendToICS(ics_prefix);
15763     SendToICS("unobserve\n");
15764 }
15765
15766 void
15767 StopExaminingEvent ()
15768 {
15769     /* Stop observing current game */
15770     SendToICS(ics_prefix);
15771     SendToICS("unexamine\n");
15772 }
15773
15774 void
15775 ForwardInner (int target)
15776 {
15777     int limit; int oldSeekGraphUp = seekGraphUp;
15778
15779     if (appData.debugMode)
15780         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15781                 target, currentMove, forwardMostMove);
15782
15783     if (gameMode == EditPosition)
15784       return;
15785
15786     seekGraphUp = FALSE;
15787     MarkTargetSquares(1);
15788     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15789
15790     if (gameMode == PlayFromGameFile && !pausing)
15791       PauseEvent();
15792
15793     if (gameMode == IcsExamining && pausing)
15794       limit = pauseExamForwardMostMove;
15795     else
15796       limit = forwardMostMove;
15797
15798     if (target > limit) target = limit;
15799
15800     if (target > 0 && moveList[target - 1][0]) {
15801         int fromX, fromY, toX, toY;
15802         toX = moveList[target - 1][2] - AAA;
15803         toY = moveList[target - 1][3] - ONE;
15804         if (moveList[target - 1][1] == '@') {
15805             if (appData.highlightLastMove) {
15806                 SetHighlights(-1, -1, toX, toY);
15807             }
15808         } else {
15809             int viaX = moveList[target - 1][5] - AAA;
15810             int viaY = moveList[target - 1][6] - ONE;
15811             fromX = moveList[target - 1][0] - AAA;
15812             fromY = moveList[target - 1][1] - ONE;
15813             if (target == currentMove + 1) {
15814                 if(moveList[target - 1][4] == ';') { // multi-leg
15815                     ChessSquare piece = boards[currentMove][viaY][viaX];
15816                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15817                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15818                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15819                     boards[currentMove][viaY][viaX] = piece;
15820                 } else
15821                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15822             }
15823             if (appData.highlightLastMove) {
15824                 SetHighlights(fromX, fromY, toX, toY);
15825             }
15826         }
15827     }
15828     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15829         gameMode == Training || gameMode == PlayFromGameFile ||
15830         gameMode == AnalyzeFile) {
15831         while (currentMove < target) {
15832             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15833             SendMoveToProgram(currentMove++, &first);
15834         }
15835     } else {
15836         currentMove = target;
15837     }
15838
15839     if (gameMode == EditGame || gameMode == EndOfGame) {
15840         whiteTimeRemaining = timeRemaining[0][currentMove];
15841         blackTimeRemaining = timeRemaining[1][currentMove];
15842     }
15843     DisplayBothClocks();
15844     DisplayMove(currentMove - 1);
15845     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15846     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15847     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15848         DisplayComment(currentMove - 1, commentList[currentMove]);
15849     }
15850     ClearMap(); // [HGM] exclude: invalidate map
15851 }
15852
15853
15854 void
15855 ForwardEvent ()
15856 {
15857     if (gameMode == IcsExamining && !pausing) {
15858         SendToICS(ics_prefix);
15859         SendToICS("forward\n");
15860     } else {
15861         ForwardInner(currentMove + 1);
15862     }
15863 }
15864
15865 void
15866 ToEndEvent ()
15867 {
15868     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15869         /* to optimze, we temporarily turn off analysis mode while we feed
15870          * the remaining moves to the engine. Otherwise we get analysis output
15871          * after each move.
15872          */
15873         if (first.analysisSupport) {
15874           SendToProgram("exit\nforce\n", &first);
15875           first.analyzing = FALSE;
15876         }
15877     }
15878
15879     if (gameMode == IcsExamining && !pausing) {
15880         SendToICS(ics_prefix);
15881         SendToICS("forward 999999\n");
15882     } else {
15883         ForwardInner(forwardMostMove);
15884     }
15885
15886     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15887         /* we have fed all the moves, so reactivate analysis mode */
15888         SendToProgram("analyze\n", &first);
15889         first.analyzing = TRUE;
15890         /*first.maybeThinking = TRUE;*/
15891         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15892     }
15893 }
15894
15895 void
15896 BackwardInner (int target)
15897 {
15898     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15899
15900     if (appData.debugMode)
15901         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15902                 target, currentMove, forwardMostMove);
15903
15904     if (gameMode == EditPosition) return;
15905     seekGraphUp = FALSE;
15906     MarkTargetSquares(1);
15907     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15908     if (currentMove <= backwardMostMove) {
15909         ClearHighlights();
15910         DrawPosition(full_redraw, boards[currentMove]);
15911         return;
15912     }
15913     if (gameMode == PlayFromGameFile && !pausing)
15914       PauseEvent();
15915
15916     if (moveList[target][0]) {
15917         int fromX, fromY, toX, toY;
15918         toX = moveList[target][2] - AAA;
15919         toY = moveList[target][3] - ONE;
15920         if (moveList[target][1] == '@') {
15921             if (appData.highlightLastMove) {
15922                 SetHighlights(-1, -1, toX, toY);
15923             }
15924         } else {
15925             fromX = moveList[target][0] - AAA;
15926             fromY = moveList[target][1] - ONE;
15927             if (target == currentMove - 1) {
15928                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15929             }
15930             if (appData.highlightLastMove) {
15931                 SetHighlights(fromX, fromY, toX, toY);
15932             }
15933         }
15934     }
15935     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15936         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15937         while (currentMove > target) {
15938             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15939                 // null move cannot be undone. Reload program with move history before it.
15940                 int i;
15941                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15942                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15943                 }
15944                 SendBoard(&first, i);
15945               if(second.analyzing) SendBoard(&second, i);
15946                 for(currentMove=i; currentMove<target; currentMove++) {
15947                     SendMoveToProgram(currentMove, &first);
15948                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15949                 }
15950                 break;
15951             }
15952             SendToBoth("undo\n");
15953             currentMove--;
15954         }
15955     } else {
15956         currentMove = target;
15957     }
15958
15959     if (gameMode == EditGame || gameMode == EndOfGame) {
15960         whiteTimeRemaining = timeRemaining[0][currentMove];
15961         blackTimeRemaining = timeRemaining[1][currentMove];
15962     }
15963     DisplayBothClocks();
15964     DisplayMove(currentMove - 1);
15965     DrawPosition(full_redraw, boards[currentMove]);
15966     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15967     // [HGM] PV info: routine tests if comment empty
15968     DisplayComment(currentMove - 1, commentList[currentMove]);
15969     ClearMap(); // [HGM] exclude: invalidate map
15970 }
15971
15972 void
15973 BackwardEvent ()
15974 {
15975     if (gameMode == IcsExamining && !pausing) {
15976         SendToICS(ics_prefix);
15977         SendToICS("backward\n");
15978     } else {
15979         BackwardInner(currentMove - 1);
15980     }
15981 }
15982
15983 void
15984 ToStartEvent ()
15985 {
15986     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15987         /* to optimize, we temporarily turn off analysis mode while we undo
15988          * all the moves. Otherwise we get analysis output after each undo.
15989          */
15990         if (first.analysisSupport) {
15991           SendToProgram("exit\nforce\n", &first);
15992           first.analyzing = FALSE;
15993         }
15994     }
15995
15996     if (gameMode == IcsExamining && !pausing) {
15997         SendToICS(ics_prefix);
15998         SendToICS("backward 999999\n");
15999     } else {
16000         BackwardInner(backwardMostMove);
16001     }
16002
16003     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16004         /* we have fed all the moves, so reactivate analysis mode */
16005         SendToProgram("analyze\n", &first);
16006         first.analyzing = TRUE;
16007         /*first.maybeThinking = TRUE;*/
16008         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16009     }
16010 }
16011
16012 void
16013 ToNrEvent (int to)
16014 {
16015   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16016   if (to >= forwardMostMove) to = forwardMostMove;
16017   if (to <= backwardMostMove) to = backwardMostMove;
16018   if (to < currentMove) {
16019     BackwardInner(to);
16020   } else {
16021     ForwardInner(to);
16022   }
16023 }
16024
16025 void
16026 RevertEvent (Boolean annotate)
16027 {
16028     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16029         return;
16030     }
16031     if (gameMode != IcsExamining) {
16032         DisplayError(_("You are not examining a game"), 0);
16033         return;
16034     }
16035     if (pausing) {
16036         DisplayError(_("You can't revert while pausing"), 0);
16037         return;
16038     }
16039     SendToICS(ics_prefix);
16040     SendToICS("revert\n");
16041 }
16042
16043 void
16044 RetractMoveEvent ()
16045 {
16046     switch (gameMode) {
16047       case MachinePlaysWhite:
16048       case MachinePlaysBlack:
16049         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16050             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16051             return;
16052         }
16053         if (forwardMostMove < 2) return;
16054         currentMove = forwardMostMove = forwardMostMove - 2;
16055         whiteTimeRemaining = timeRemaining[0][currentMove];
16056         blackTimeRemaining = timeRemaining[1][currentMove];
16057         DisplayBothClocks();
16058         DisplayMove(currentMove - 1);
16059         ClearHighlights();/*!! could figure this out*/
16060         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16061         SendToProgram("remove\n", &first);
16062         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16063         break;
16064
16065       case BeginningOfGame:
16066       default:
16067         break;
16068
16069       case IcsPlayingWhite:
16070       case IcsPlayingBlack:
16071         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16072             SendToICS(ics_prefix);
16073             SendToICS("takeback 2\n");
16074         } else {
16075             SendToICS(ics_prefix);
16076             SendToICS("takeback 1\n");
16077         }
16078         break;
16079     }
16080 }
16081
16082 void
16083 MoveNowEvent ()
16084 {
16085     ChessProgramState *cps;
16086
16087     switch (gameMode) {
16088       case MachinePlaysWhite:
16089         if (!WhiteOnMove(forwardMostMove)) {
16090             DisplayError(_("It is your turn"), 0);
16091             return;
16092         }
16093         cps = &first;
16094         break;
16095       case MachinePlaysBlack:
16096         if (WhiteOnMove(forwardMostMove)) {
16097             DisplayError(_("It is your turn"), 0);
16098             return;
16099         }
16100         cps = &first;
16101         break;
16102       case TwoMachinesPlay:
16103         if (WhiteOnMove(forwardMostMove) ==
16104             (first.twoMachinesColor[0] == 'w')) {
16105             cps = &first;
16106         } else {
16107             cps = &second;
16108         }
16109         break;
16110       case BeginningOfGame:
16111       default:
16112         return;
16113     }
16114     SendToProgram("?\n", cps);
16115 }
16116
16117 void
16118 TruncateGameEvent ()
16119 {
16120     EditGameEvent();
16121     if (gameMode != EditGame) return;
16122     TruncateGame();
16123 }
16124
16125 void
16126 TruncateGame ()
16127 {
16128     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16129     if (forwardMostMove > currentMove) {
16130         if (gameInfo.resultDetails != NULL) {
16131             free(gameInfo.resultDetails);
16132             gameInfo.resultDetails = NULL;
16133             gameInfo.result = GameUnfinished;
16134         }
16135         forwardMostMove = currentMove;
16136         HistorySet(parseList, backwardMostMove, forwardMostMove,
16137                    currentMove-1);
16138     }
16139 }
16140
16141 void
16142 HintEvent ()
16143 {
16144     if (appData.noChessProgram) return;
16145     switch (gameMode) {
16146       case MachinePlaysWhite:
16147         if (WhiteOnMove(forwardMostMove)) {
16148             DisplayError(_("Wait until your turn."), 0);
16149             return;
16150         }
16151         break;
16152       case BeginningOfGame:
16153       case MachinePlaysBlack:
16154         if (!WhiteOnMove(forwardMostMove)) {
16155             DisplayError(_("Wait until your turn."), 0);
16156             return;
16157         }
16158         break;
16159       default:
16160         DisplayError(_("No hint available"), 0);
16161         return;
16162     }
16163     SendToProgram("hint\n", &first);
16164     hintRequested = TRUE;
16165 }
16166
16167 int
16168 SaveSelected (FILE *g, int dummy, char *dummy2)
16169 {
16170     ListGame * lg = (ListGame *) gameList.head;
16171     int nItem, cnt=0;
16172     FILE *f;
16173
16174     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16175         DisplayError(_("Game list not loaded or empty"), 0);
16176         return 0;
16177     }
16178
16179     creatingBook = TRUE; // suppresses stuff during load game
16180
16181     /* Get list size */
16182     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16183         if(lg->position >= 0) { // selected?
16184             LoadGame(f, nItem, "", TRUE);
16185             SaveGamePGN2(g); // leaves g open
16186             cnt++; DoEvents();
16187         }
16188         lg = (ListGame *) lg->node.succ;
16189     }
16190
16191     fclose(g);
16192     creatingBook = FALSE;
16193
16194     return cnt;
16195 }
16196
16197 void
16198 CreateBookEvent ()
16199 {
16200     ListGame * lg = (ListGame *) gameList.head;
16201     FILE *f, *g;
16202     int nItem;
16203     static int secondTime = FALSE;
16204
16205     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16206         DisplayError(_("Game list not loaded or empty"), 0);
16207         return;
16208     }
16209
16210     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16211         fclose(g);
16212         secondTime++;
16213         DisplayNote(_("Book file exists! Try again for overwrite."));
16214         return;
16215     }
16216
16217     creatingBook = TRUE;
16218     secondTime = FALSE;
16219
16220     /* Get list size */
16221     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16222         if(lg->position >= 0) {
16223             LoadGame(f, nItem, "", TRUE);
16224             AddGameToBook(TRUE);
16225             DoEvents();
16226         }
16227         lg = (ListGame *) lg->node.succ;
16228     }
16229
16230     creatingBook = FALSE;
16231     FlushBook();
16232 }
16233
16234 void
16235 BookEvent ()
16236 {
16237     if (appData.noChessProgram) return;
16238     switch (gameMode) {
16239       case MachinePlaysWhite:
16240         if (WhiteOnMove(forwardMostMove)) {
16241             DisplayError(_("Wait until your turn."), 0);
16242             return;
16243         }
16244         break;
16245       case BeginningOfGame:
16246       case MachinePlaysBlack:
16247         if (!WhiteOnMove(forwardMostMove)) {
16248             DisplayError(_("Wait until your turn."), 0);
16249             return;
16250         }
16251         break;
16252       case EditPosition:
16253         EditPositionDone(TRUE);
16254         break;
16255       case TwoMachinesPlay:
16256         return;
16257       default:
16258         break;
16259     }
16260     SendToProgram("bk\n", &first);
16261     bookOutput[0] = NULLCHAR;
16262     bookRequested = TRUE;
16263 }
16264
16265 void
16266 AboutGameEvent ()
16267 {
16268     char *tags = PGNTags(&gameInfo);
16269     TagsPopUp(tags, CmailMsg());
16270     free(tags);
16271 }
16272
16273 /* end button procedures */
16274
16275 void
16276 PrintPosition (FILE *fp, int move)
16277 {
16278     int i, j;
16279
16280     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16281         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16282             char c = PieceToChar(boards[move][i][j]);
16283             fputc(c == 'x' ? '.' : c, fp);
16284             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16285         }
16286     }
16287     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16288       fprintf(fp, "white to play\n");
16289     else
16290       fprintf(fp, "black to play\n");
16291 }
16292
16293 void
16294 PrintOpponents (FILE *fp)
16295 {
16296     if (gameInfo.white != NULL) {
16297         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16298     } else {
16299         fprintf(fp, "\n");
16300     }
16301 }
16302
16303 /* Find last component of program's own name, using some heuristics */
16304 void
16305 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16306 {
16307     char *p, *q, c;
16308     int local = (strcmp(host, "localhost") == 0);
16309     while (!local && (p = strchr(prog, ';')) != NULL) {
16310         p++;
16311         while (*p == ' ') p++;
16312         prog = p;
16313     }
16314     if (*prog == '"' || *prog == '\'') {
16315         q = strchr(prog + 1, *prog);
16316     } else {
16317         q = strchr(prog, ' ');
16318     }
16319     if (q == NULL) q = prog + strlen(prog);
16320     p = q;
16321     while (p >= prog && *p != '/' && *p != '\\') p--;
16322     p++;
16323     if(p == prog && *p == '"') p++;
16324     c = *q; *q = 0;
16325     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16326     memcpy(buf, p, q - p);
16327     buf[q - p] = NULLCHAR;
16328     if (!local) {
16329         strcat(buf, "@");
16330         strcat(buf, host);
16331     }
16332 }
16333
16334 char *
16335 TimeControlTagValue ()
16336 {
16337     char buf[MSG_SIZ];
16338     if (!appData.clockMode) {
16339       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16340     } else if (movesPerSession > 0) {
16341       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16342     } else if (timeIncrement == 0) {
16343       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16344     } else {
16345       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16346     }
16347     return StrSave(buf);
16348 }
16349
16350 void
16351 SetGameInfo ()
16352 {
16353     /* This routine is used only for certain modes */
16354     VariantClass v = gameInfo.variant;
16355     ChessMove r = GameUnfinished;
16356     char *p = NULL;
16357
16358     if(keepInfo) return;
16359
16360     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16361         r = gameInfo.result;
16362         p = gameInfo.resultDetails;
16363         gameInfo.resultDetails = NULL;
16364     }
16365     ClearGameInfo(&gameInfo);
16366     gameInfo.variant = v;
16367
16368     switch (gameMode) {
16369       case MachinePlaysWhite:
16370         gameInfo.event = StrSave( appData.pgnEventHeader );
16371         gameInfo.site = StrSave(HostName());
16372         gameInfo.date = PGNDate();
16373         gameInfo.round = StrSave("-");
16374         gameInfo.white = StrSave(first.tidy);
16375         gameInfo.black = StrSave(UserName());
16376         gameInfo.timeControl = TimeControlTagValue();
16377         break;
16378
16379       case MachinePlaysBlack:
16380         gameInfo.event = StrSave( appData.pgnEventHeader );
16381         gameInfo.site = StrSave(HostName());
16382         gameInfo.date = PGNDate();
16383         gameInfo.round = StrSave("-");
16384         gameInfo.white = StrSave(UserName());
16385         gameInfo.black = StrSave(first.tidy);
16386         gameInfo.timeControl = TimeControlTagValue();
16387         break;
16388
16389       case TwoMachinesPlay:
16390         gameInfo.event = StrSave( appData.pgnEventHeader );
16391         gameInfo.site = StrSave(HostName());
16392         gameInfo.date = PGNDate();
16393         if (roundNr > 0) {
16394             char buf[MSG_SIZ];
16395             snprintf(buf, MSG_SIZ, "%d", roundNr);
16396             gameInfo.round = StrSave(buf);
16397         } else {
16398             gameInfo.round = StrSave("-");
16399         }
16400         if (first.twoMachinesColor[0] == 'w') {
16401             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16402             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16403         } else {
16404             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16405             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16406         }
16407         gameInfo.timeControl = TimeControlTagValue();
16408         break;
16409
16410       case EditGame:
16411         gameInfo.event = StrSave("Edited game");
16412         gameInfo.site = StrSave(HostName());
16413         gameInfo.date = PGNDate();
16414         gameInfo.round = StrSave("-");
16415         gameInfo.white = StrSave("-");
16416         gameInfo.black = StrSave("-");
16417         gameInfo.result = r;
16418         gameInfo.resultDetails = p;
16419         break;
16420
16421       case EditPosition:
16422         gameInfo.event = StrSave("Edited position");
16423         gameInfo.site = StrSave(HostName());
16424         gameInfo.date = PGNDate();
16425         gameInfo.round = StrSave("-");
16426         gameInfo.white = StrSave("-");
16427         gameInfo.black = StrSave("-");
16428         break;
16429
16430       case IcsPlayingWhite:
16431       case IcsPlayingBlack:
16432       case IcsObserving:
16433       case IcsExamining:
16434         break;
16435
16436       case PlayFromGameFile:
16437         gameInfo.event = StrSave("Game from non-PGN file");
16438         gameInfo.site = StrSave(HostName());
16439         gameInfo.date = PGNDate();
16440         gameInfo.round = StrSave("-");
16441         gameInfo.white = StrSave("?");
16442         gameInfo.black = StrSave("?");
16443         break;
16444
16445       default:
16446         break;
16447     }
16448 }
16449
16450 void
16451 ReplaceComment (int index, char *text)
16452 {
16453     int len;
16454     char *p;
16455     float score;
16456
16457     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16458        pvInfoList[index-1].depth == len &&
16459        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16460        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16461     while (*text == '\n') text++;
16462     len = strlen(text);
16463     while (len > 0 && text[len - 1] == '\n') len--;
16464
16465     if (commentList[index] != NULL)
16466       free(commentList[index]);
16467
16468     if (len == 0) {
16469         commentList[index] = NULL;
16470         return;
16471     }
16472   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16473       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16474       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16475     commentList[index] = (char *) malloc(len + 2);
16476     strncpy(commentList[index], text, len);
16477     commentList[index][len] = '\n';
16478     commentList[index][len + 1] = NULLCHAR;
16479   } else {
16480     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16481     char *p;
16482     commentList[index] = (char *) malloc(len + 7);
16483     safeStrCpy(commentList[index], "{\n", 3);
16484     safeStrCpy(commentList[index]+2, text, len+1);
16485     commentList[index][len+2] = NULLCHAR;
16486     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16487     strcat(commentList[index], "\n}\n");
16488   }
16489 }
16490
16491 void
16492 CrushCRs (char *text)
16493 {
16494   char *p = text;
16495   char *q = text;
16496   char ch;
16497
16498   do {
16499     ch = *p++;
16500     if (ch == '\r') continue;
16501     *q++ = ch;
16502   } while (ch != '\0');
16503 }
16504
16505 void
16506 AppendComment (int index, char *text, Boolean addBraces)
16507 /* addBraces  tells if we should add {} */
16508 {
16509     int oldlen, len;
16510     char *old;
16511
16512 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16513     if(addBraces == 3) addBraces = 0; else // force appending literally
16514     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16515
16516     CrushCRs(text);
16517     while (*text == '\n') text++;
16518     len = strlen(text);
16519     while (len > 0 && text[len - 1] == '\n') len--;
16520     text[len] = NULLCHAR;
16521
16522     if (len == 0) return;
16523
16524     if (commentList[index] != NULL) {
16525       Boolean addClosingBrace = addBraces;
16526         old = commentList[index];
16527         oldlen = strlen(old);
16528         while(commentList[index][oldlen-1] ==  '\n')
16529           commentList[index][--oldlen] = NULLCHAR;
16530         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16531         safeStrCpy(commentList[index], old, oldlen + len + 6);
16532         free(old);
16533         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16534         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16535           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16536           while (*text == '\n') { text++; len--; }
16537           commentList[index][--oldlen] = NULLCHAR;
16538       }
16539         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16540         else          strcat(commentList[index], "\n");
16541         strcat(commentList[index], text);
16542         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16543         else          strcat(commentList[index], "\n");
16544     } else {
16545         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16546         if(addBraces)
16547           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16548         else commentList[index][0] = NULLCHAR;
16549         strcat(commentList[index], text);
16550         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16551         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16552     }
16553 }
16554
16555 static char *
16556 FindStr (char * text, char * sub_text)
16557 {
16558     char * result = strstr( text, sub_text );
16559
16560     if( result != NULL ) {
16561         result += strlen( sub_text );
16562     }
16563
16564     return result;
16565 }
16566
16567 /* [AS] Try to extract PV info from PGN comment */
16568 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16569 char *
16570 GetInfoFromComment (int index, char * text)
16571 {
16572     char * sep = text, *p;
16573
16574     if( text != NULL && index > 0 ) {
16575         int score = 0;
16576         int depth = 0;
16577         int time = -1, sec = 0, deci;
16578         char * s_eval = FindStr( text, "[%eval " );
16579         char * s_emt = FindStr( text, "[%emt " );
16580 #if 0
16581         if( s_eval != NULL || s_emt != NULL ) {
16582 #else
16583         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16584 #endif
16585             /* New style */
16586             char delim;
16587
16588             if( s_eval != NULL ) {
16589                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16590                     return text;
16591                 }
16592
16593                 if( delim != ']' ) {
16594                     return text;
16595                 }
16596             }
16597
16598             if( s_emt != NULL ) {
16599             }
16600                 return text;
16601         }
16602         else {
16603             /* We expect something like: [+|-]nnn.nn/dd */
16604             int score_lo = 0;
16605
16606             if(*text != '{') return text; // [HGM] braces: must be normal comment
16607
16608             sep = strchr( text, '/' );
16609             if( sep == NULL || sep < (text+4) ) {
16610                 return text;
16611             }
16612
16613             p = text;
16614             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16615             if(p[1] == '(') { // comment starts with PV
16616                p = strchr(p, ')'); // locate end of PV
16617                if(p == NULL || sep < p+5) return text;
16618                // at this point we have something like "{(.*) +0.23/6 ..."
16619                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16620                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16621                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16622             }
16623             time = -1; sec = -1; deci = -1;
16624             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16625                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16626                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16627                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16628                 return text;
16629             }
16630
16631             if( score_lo < 0 || score_lo >= 100 ) {
16632                 return text;
16633             }
16634
16635             if(sec >= 0) time = 600*time + 10*sec; else
16636             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16637
16638             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16639
16640             /* [HGM] PV time: now locate end of PV info */
16641             while( *++sep >= '0' && *sep <= '9'); // strip depth
16642             if(time >= 0)
16643             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16644             if(sec >= 0)
16645             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16646             if(deci >= 0)
16647             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16648             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16649         }
16650
16651         if( depth <= 0 ) {
16652             return text;
16653         }
16654
16655         if( time < 0 ) {
16656             time = -1;
16657         }
16658
16659         pvInfoList[index-1].depth = depth;
16660         pvInfoList[index-1].score = score;
16661         pvInfoList[index-1].time  = 10*time; // centi-sec
16662         if(*sep == '}') *sep = 0; else *--sep = '{';
16663         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16664     }
16665     return sep;
16666 }
16667
16668 void
16669 SendToProgram (char *message, ChessProgramState *cps)
16670 {
16671     int count, outCount, error;
16672     char buf[MSG_SIZ];
16673
16674     if (cps->pr == NoProc) return;
16675     Attention(cps);
16676
16677     if (appData.debugMode) {
16678         TimeMark now;
16679         GetTimeMark(&now);
16680         fprintf(debugFP, "%ld >%-6s: %s",
16681                 SubtractTimeMarks(&now, &programStartTime),
16682                 cps->which, message);
16683         if(serverFP)
16684             fprintf(serverFP, "%ld >%-6s: %s",
16685                 SubtractTimeMarks(&now, &programStartTime),
16686                 cps->which, message), fflush(serverFP);
16687     }
16688
16689     count = strlen(message);
16690     outCount = OutputToProcess(cps->pr, message, count, &error);
16691     if (outCount < count && !exiting
16692                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16693       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16694       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16695         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16696             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16697                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16698                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16699                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16700             } else {
16701                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16702                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16703                 gameInfo.result = res;
16704             }
16705             gameInfo.resultDetails = StrSave(buf);
16706         }
16707         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16708         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16709     }
16710 }
16711
16712 void
16713 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16714 {
16715     char *end_str;
16716     char buf[MSG_SIZ];
16717     ChessProgramState *cps = (ChessProgramState *)closure;
16718
16719     if (isr != cps->isr) return; /* Killed intentionally */
16720     if (count <= 0) {
16721         if (count == 0) {
16722             RemoveInputSource(cps->isr);
16723             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16724                     _(cps->which), cps->program);
16725             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16726             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16727                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16728                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16729                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16730                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16731                 } else {
16732                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16733                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16734                     gameInfo.result = res;
16735                 }
16736                 gameInfo.resultDetails = StrSave(buf);
16737             }
16738             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16739             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16740         } else {
16741             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16742                     _(cps->which), cps->program);
16743             RemoveInputSource(cps->isr);
16744
16745             /* [AS] Program is misbehaving badly... kill it */
16746             if( count == -2 ) {
16747                 DestroyChildProcess( cps->pr, 9 );
16748                 cps->pr = NoProc;
16749             }
16750
16751             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16752         }
16753         return;
16754     }
16755
16756     if ((end_str = strchr(message, '\r')) != NULL)
16757       *end_str = NULLCHAR;
16758     if ((end_str = strchr(message, '\n')) != NULL)
16759       *end_str = NULLCHAR;
16760
16761     if (appData.debugMode) {
16762         TimeMark now; int print = 1;
16763         char *quote = ""; char c; int i;
16764
16765         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16766                 char start = message[0];
16767                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16768                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16769                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16770                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16771                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16772                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16773                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16774                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16775                    sscanf(message, "hint: %c", &c)!=1 &&
16776                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16777                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16778                     print = (appData.engineComments >= 2);
16779                 }
16780                 message[0] = start; // restore original message
16781         }
16782         if(print) {
16783                 GetTimeMark(&now);
16784                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16785                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16786                         quote,
16787                         message);
16788                 if(serverFP)
16789                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16790                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16791                         quote,
16792                         message), fflush(serverFP);
16793         }
16794     }
16795
16796     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16797     if (appData.icsEngineAnalyze) {
16798         if (strstr(message, "whisper") != NULL ||
16799              strstr(message, "kibitz") != NULL ||
16800             strstr(message, "tellics") != NULL) return;
16801     }
16802
16803     HandleMachineMove(message, cps);
16804 }
16805
16806
16807 void
16808 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16809 {
16810     char buf[MSG_SIZ];
16811     int seconds;
16812
16813     if( timeControl_2 > 0 ) {
16814         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16815             tc = timeControl_2;
16816         }
16817     }
16818     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16819     inc /= cps->timeOdds;
16820     st  /= cps->timeOdds;
16821
16822     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16823
16824     if (st > 0) {
16825       /* Set exact time per move, normally using st command */
16826       if (cps->stKludge) {
16827         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16828         seconds = st % 60;
16829         if (seconds == 0) {
16830           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16831         } else {
16832           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16833         }
16834       } else {
16835         snprintf(buf, MSG_SIZ, "st %d\n", st);
16836       }
16837     } else {
16838       /* Set conventional or incremental time control, using level command */
16839       if (seconds == 0) {
16840         /* Note old gnuchess bug -- minutes:seconds used to not work.
16841            Fixed in later versions, but still avoid :seconds
16842            when seconds is 0. */
16843         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16844       } else {
16845         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16846                  seconds, inc/1000.);
16847       }
16848     }
16849     SendToProgram(buf, cps);
16850
16851     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16852     /* Orthogonally, limit search to given depth */
16853     if (sd > 0) {
16854       if (cps->sdKludge) {
16855         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16856       } else {
16857         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16858       }
16859       SendToProgram(buf, cps);
16860     }
16861
16862     if(cps->nps >= 0) { /* [HGM] nps */
16863         if(cps->supportsNPS == FALSE)
16864           cps->nps = -1; // don't use if engine explicitly says not supported!
16865         else {
16866           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16867           SendToProgram(buf, cps);
16868         }
16869     }
16870 }
16871
16872 ChessProgramState *
16873 WhitePlayer ()
16874 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16875 {
16876     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16877        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16878         return &second;
16879     return &first;
16880 }
16881
16882 void
16883 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16884 {
16885     char message[MSG_SIZ];
16886     long time, otime;
16887
16888     /* Note: this routine must be called when the clocks are stopped
16889        or when they have *just* been set or switched; otherwise
16890        it will be off by the time since the current tick started.
16891     */
16892     if (machineWhite) {
16893         time = whiteTimeRemaining / 10;
16894         otime = blackTimeRemaining / 10;
16895     } else {
16896         time = blackTimeRemaining / 10;
16897         otime = whiteTimeRemaining / 10;
16898     }
16899     /* [HGM] translate opponent's time by time-odds factor */
16900     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16901
16902     if (time <= 0) time = 1;
16903     if (otime <= 0) otime = 1;
16904
16905     snprintf(message, MSG_SIZ, "time %ld\n", time);
16906     SendToProgram(message, cps);
16907
16908     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16909     SendToProgram(message, cps);
16910 }
16911
16912 char *
16913 EngineDefinedVariant (ChessProgramState *cps, int n)
16914 {   // return name of n-th unknown variant that engine supports
16915     static char buf[MSG_SIZ];
16916     char *p, *s = cps->variants;
16917     if(!s) return NULL;
16918     do { // parse string from variants feature
16919       VariantClass v;
16920         p = strchr(s, ',');
16921         if(p) *p = NULLCHAR;
16922       v = StringToVariant(s);
16923       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16924         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16925             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16926                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16927                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16928                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16929             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16930         }
16931         if(p) *p++ = ',';
16932         if(n < 0) return buf;
16933     } while(s = p);
16934     return NULL;
16935 }
16936
16937 int
16938 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16939 {
16940   char buf[MSG_SIZ];
16941   int len = strlen(name);
16942   int val;
16943
16944   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16945     (*p) += len + 1;
16946     sscanf(*p, "%d", &val);
16947     *loc = (val != 0);
16948     while (**p && **p != ' ')
16949       (*p)++;
16950     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16951     SendToProgram(buf, cps);
16952     return TRUE;
16953   }
16954   return FALSE;
16955 }
16956
16957 int
16958 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16959 {
16960   char buf[MSG_SIZ];
16961   int len = strlen(name);
16962   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16963     (*p) += len + 1;
16964     sscanf(*p, "%d", loc);
16965     while (**p && **p != ' ') (*p)++;
16966     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16967     SendToProgram(buf, cps);
16968     return TRUE;
16969   }
16970   return FALSE;
16971 }
16972
16973 int
16974 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16975 {
16976   char buf[MSG_SIZ];
16977   int len = strlen(name);
16978   if (strncmp((*p), name, len) == 0
16979       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16980     (*p) += len + 2;
16981     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16982     sscanf(*p, "%[^\"]", *loc);
16983     while (**p && **p != '\"') (*p)++;
16984     if (**p == '\"') (*p)++;
16985     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16986     SendToProgram(buf, cps);
16987     return TRUE;
16988   }
16989   return FALSE;
16990 }
16991
16992 int
16993 ParseOption (Option *opt, ChessProgramState *cps)
16994 // [HGM] options: process the string that defines an engine option, and determine
16995 // name, type, default value, and allowed value range
16996 {
16997         char *p, *q, buf[MSG_SIZ];
16998         int n, min = (-1)<<31, max = 1<<31, def;
16999
17000         opt->target = &opt->value;   // OK for spin/slider and checkbox
17001         if(p = strstr(opt->name, " -spin ")) {
17002             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17003             if(max < min) max = min; // enforce consistency
17004             if(def < min) def = min;
17005             if(def > max) def = max;
17006             opt->value = def;
17007             opt->min = min;
17008             opt->max = max;
17009             opt->type = Spin;
17010         } else if((p = strstr(opt->name, " -slider "))) {
17011             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17012             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17013             if(max < min) max = min; // enforce consistency
17014             if(def < min) def = min;
17015             if(def > max) def = max;
17016             opt->value = def;
17017             opt->min = min;
17018             opt->max = max;
17019             opt->type = Spin; // Slider;
17020         } else if((p = strstr(opt->name, " -string "))) {
17021             opt->textValue = p+9;
17022             opt->type = TextBox;
17023             opt->target = &opt->textValue;
17024         } else if((p = strstr(opt->name, " -file "))) {
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 = FileName; // FileName;
17028             opt->target = &opt->textValue;
17029         } else if((p = strstr(opt->name, " -path "))) {
17030             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17031             opt->target = opt->textValue = p+7;
17032             opt->type = PathName; // PathName;
17033             opt->target = &opt->textValue;
17034         } else if(p = strstr(opt->name, " -check ")) {
17035             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17036             opt->value = (def != 0);
17037             opt->type = CheckBox;
17038         } else if(p = strstr(opt->name, " -combo ")) {
17039             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17040             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17041             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17042             opt->value = n = 0;
17043             while(q = StrStr(q, " /// ")) {
17044                 n++; *q = 0;    // count choices, and null-terminate each of them
17045                 q += 5;
17046                 if(*q == '*') { // remember default, which is marked with * prefix
17047                     q++;
17048                     opt->value = n;
17049                 }
17050                 cps->comboList[cps->comboCnt++] = q;
17051             }
17052             cps->comboList[cps->comboCnt++] = NULL;
17053             opt->max = n + 1;
17054             opt->type = ComboBox;
17055         } else if(p = strstr(opt->name, " -button")) {
17056             opt->type = Button;
17057         } else if(p = strstr(opt->name, " -save")) {
17058             opt->type = SaveButton;
17059         } else return FALSE;
17060         *p = 0; // terminate option name
17061         // now look if the command-line options define a setting for this engine option.
17062         if(cps->optionSettings && cps->optionSettings[0])
17063             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17064         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17065           snprintf(buf, MSG_SIZ, "option %s", p);
17066                 if(p = strstr(buf, ",")) *p = 0;
17067                 if(q = strchr(buf, '=')) switch(opt->type) {
17068                     case ComboBox:
17069                         for(n=0; n<opt->max; n++)
17070                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17071                         break;
17072                     case TextBox:
17073                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17074                         break;
17075                     case Spin:
17076                     case CheckBox:
17077                         opt->value = atoi(q+1);
17078                     default:
17079                         break;
17080                 }
17081                 strcat(buf, "\n");
17082                 SendToProgram(buf, cps);
17083         }
17084         return TRUE;
17085 }
17086
17087 void
17088 FeatureDone (ChessProgramState *cps, int val)
17089 {
17090   DelayedEventCallback cb = GetDelayedEvent();
17091   if ((cb == InitBackEnd3 && cps == &first) ||
17092       (cb == SettingsMenuIfReady && cps == &second) ||
17093       (cb == LoadEngine) ||
17094       (cb == TwoMachinesEventIfReady)) {
17095     CancelDelayedEvent();
17096     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17097   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17098   cps->initDone = val;
17099   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17100 }
17101
17102 /* Parse feature command from engine */
17103 void
17104 ParseFeatures (char *args, ChessProgramState *cps)
17105 {
17106   char *p = args;
17107   char *q = NULL;
17108   int val;
17109   char buf[MSG_SIZ];
17110
17111   for (;;) {
17112     while (*p == ' ') p++;
17113     if (*p == NULLCHAR) return;
17114
17115     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17116     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17117     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17118     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17119     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17120     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17121     if (BoolFeature(&p, "reuse", &val, cps)) {
17122       /* Engine can disable reuse, but can't enable it if user said no */
17123       if (!val) cps->reuse = FALSE;
17124       continue;
17125     }
17126     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17127     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17128       if (gameMode == TwoMachinesPlay) {
17129         DisplayTwoMachinesTitle();
17130       } else {
17131         DisplayTitle("");
17132       }
17133       continue;
17134     }
17135     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17136     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17137     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17138     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17139     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17140     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17141     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17142     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17143     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17144     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17145     if (IntFeature(&p, "done", &val, cps)) {
17146       FeatureDone(cps, val);
17147       continue;
17148     }
17149     /* Added by Tord: */
17150     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17151     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17152     /* End of additions by Tord */
17153
17154     /* [HGM] added features: */
17155     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17156     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17157     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17158     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17159     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17160     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17161     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17162     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17163         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17164         FREE(cps->option[cps->nrOptions].name);
17165         cps->option[cps->nrOptions].name = q; q = NULL;
17166         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17167           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17168             SendToProgram(buf, cps);
17169             continue;
17170         }
17171         if(cps->nrOptions >= MAX_OPTIONS) {
17172             cps->nrOptions--;
17173             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17174             DisplayError(buf, 0);
17175         }
17176         continue;
17177     }
17178     /* End of additions by HGM */
17179
17180     /* unknown feature: complain and skip */
17181     q = p;
17182     while (*q && *q != '=') q++;
17183     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17184     SendToProgram(buf, cps);
17185     p = q;
17186     if (*p == '=') {
17187       p++;
17188       if (*p == '\"') {
17189         p++;
17190         while (*p && *p != '\"') p++;
17191         if (*p == '\"') p++;
17192       } else {
17193         while (*p && *p != ' ') p++;
17194       }
17195     }
17196   }
17197
17198 }
17199
17200 void
17201 PeriodicUpdatesEvent (int newState)
17202 {
17203     if (newState == appData.periodicUpdates)
17204       return;
17205
17206     appData.periodicUpdates=newState;
17207
17208     /* Display type changes, so update it now */
17209 //    DisplayAnalysis();
17210
17211     /* Get the ball rolling again... */
17212     if (newState) {
17213         AnalysisPeriodicEvent(1);
17214         StartAnalysisClock();
17215     }
17216 }
17217
17218 void
17219 PonderNextMoveEvent (int newState)
17220 {
17221     if (newState == appData.ponderNextMove) return;
17222     if (gameMode == EditPosition) EditPositionDone(TRUE);
17223     if (newState) {
17224         SendToProgram("hard\n", &first);
17225         if (gameMode == TwoMachinesPlay) {
17226             SendToProgram("hard\n", &second);
17227         }
17228     } else {
17229         SendToProgram("easy\n", &first);
17230         thinkOutput[0] = NULLCHAR;
17231         if (gameMode == TwoMachinesPlay) {
17232             SendToProgram("easy\n", &second);
17233         }
17234     }
17235     appData.ponderNextMove = newState;
17236 }
17237
17238 void
17239 NewSettingEvent (int option, int *feature, char *command, int value)
17240 {
17241     char buf[MSG_SIZ];
17242
17243     if (gameMode == EditPosition) EditPositionDone(TRUE);
17244     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17245     if(feature == NULL || *feature) SendToProgram(buf, &first);
17246     if (gameMode == TwoMachinesPlay) {
17247         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17248     }
17249 }
17250
17251 void
17252 ShowThinkingEvent ()
17253 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17254 {
17255     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17256     int newState = appData.showThinking
17257         // [HGM] thinking: other features now need thinking output as well
17258         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17259
17260     if (oldState == newState) return;
17261     oldState = newState;
17262     if (gameMode == EditPosition) EditPositionDone(TRUE);
17263     if (oldState) {
17264         SendToProgram("post\n", &first);
17265         if (gameMode == TwoMachinesPlay) {
17266             SendToProgram("post\n", &second);
17267         }
17268     } else {
17269         SendToProgram("nopost\n", &first);
17270         thinkOutput[0] = NULLCHAR;
17271         if (gameMode == TwoMachinesPlay) {
17272             SendToProgram("nopost\n", &second);
17273         }
17274     }
17275 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17276 }
17277
17278 void
17279 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17280 {
17281   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17282   if (pr == NoProc) return;
17283   AskQuestion(title, question, replyPrefix, pr);
17284 }
17285
17286 void
17287 TypeInEvent (char firstChar)
17288 {
17289     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17290         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17291         gameMode == AnalyzeMode || gameMode == EditGame ||
17292         gameMode == EditPosition || gameMode == IcsExamining ||
17293         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17294         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17295                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17296                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17297         gameMode == Training) PopUpMoveDialog(firstChar);
17298 }
17299
17300 void
17301 TypeInDoneEvent (char *move)
17302 {
17303         Board board;
17304         int n, fromX, fromY, toX, toY;
17305         char promoChar;
17306         ChessMove moveType;
17307
17308         // [HGM] FENedit
17309         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17310                 EditPositionPasteFEN(move);
17311                 return;
17312         }
17313         // [HGM] movenum: allow move number to be typed in any mode
17314         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17315           ToNrEvent(2*n-1);
17316           return;
17317         }
17318         // undocumented kludge: allow command-line option to be typed in!
17319         // (potentially fatal, and does not implement the effect of the option.)
17320         // should only be used for options that are values on which future decisions will be made,
17321         // and definitely not on options that would be used during initialization.
17322         if(strstr(move, "!!! -") == move) {
17323             ParseArgsFromString(move+4);
17324             return;
17325         }
17326
17327       if (gameMode != EditGame && currentMove != forwardMostMove &&
17328         gameMode != Training) {
17329         DisplayMoveError(_("Displayed move is not current"));
17330       } else {
17331         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17332           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17333         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17334         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17335           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17336           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17337         } else {
17338           DisplayMoveError(_("Could not parse move"));
17339         }
17340       }
17341 }
17342
17343 void
17344 DisplayMove (int moveNumber)
17345 {
17346     char message[MSG_SIZ];
17347     char res[MSG_SIZ];
17348     char cpThinkOutput[MSG_SIZ];
17349
17350     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17351
17352     if (moveNumber == forwardMostMove - 1 ||
17353         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17354
17355         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17356
17357         if (strchr(cpThinkOutput, '\n')) {
17358             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17359         }
17360     } else {
17361         *cpThinkOutput = NULLCHAR;
17362     }
17363
17364     /* [AS] Hide thinking from human user */
17365     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17366         *cpThinkOutput = NULLCHAR;
17367         if( thinkOutput[0] != NULLCHAR ) {
17368             int i;
17369
17370             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17371                 cpThinkOutput[i] = '.';
17372             }
17373             cpThinkOutput[i] = NULLCHAR;
17374             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17375         }
17376     }
17377
17378     if (moveNumber == forwardMostMove - 1 &&
17379         gameInfo.resultDetails != NULL) {
17380         if (gameInfo.resultDetails[0] == NULLCHAR) {
17381           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17382         } else {
17383           snprintf(res, MSG_SIZ, " {%s} %s",
17384                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17385         }
17386     } else {
17387         res[0] = NULLCHAR;
17388     }
17389
17390     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17391         DisplayMessage(res, cpThinkOutput);
17392     } else {
17393       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17394                 WhiteOnMove(moveNumber) ? " " : ".. ",
17395                 parseList[moveNumber], res);
17396         DisplayMessage(message, cpThinkOutput);
17397     }
17398 }
17399
17400 void
17401 DisplayComment (int moveNumber, char *text)
17402 {
17403     char title[MSG_SIZ];
17404
17405     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17406       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17407     } else {
17408       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17409               WhiteOnMove(moveNumber) ? " " : ".. ",
17410               parseList[moveNumber]);
17411     }
17412     if (text != NULL && (appData.autoDisplayComment || commentUp))
17413         CommentPopUp(title, text);
17414 }
17415
17416 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17417  * might be busy thinking or pondering.  It can be omitted if your
17418  * gnuchess is configured to stop thinking immediately on any user
17419  * input.  However, that gnuchess feature depends on the FIONREAD
17420  * ioctl, which does not work properly on some flavors of Unix.
17421  */
17422 void
17423 Attention (ChessProgramState *cps)
17424 {
17425 #if ATTENTION
17426     if (!cps->useSigint) return;
17427     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17428     switch (gameMode) {
17429       case MachinePlaysWhite:
17430       case MachinePlaysBlack:
17431       case TwoMachinesPlay:
17432       case IcsPlayingWhite:
17433       case IcsPlayingBlack:
17434       case AnalyzeMode:
17435       case AnalyzeFile:
17436         /* Skip if we know it isn't thinking */
17437         if (!cps->maybeThinking) return;
17438         if (appData.debugMode)
17439           fprintf(debugFP, "Interrupting %s\n", cps->which);
17440         InterruptChildProcess(cps->pr);
17441         cps->maybeThinking = FALSE;
17442         break;
17443       default:
17444         break;
17445     }
17446 #endif /*ATTENTION*/
17447 }
17448
17449 int
17450 CheckFlags ()
17451 {
17452     if (whiteTimeRemaining <= 0) {
17453         if (!whiteFlag) {
17454             whiteFlag = TRUE;
17455             if (appData.icsActive) {
17456                 if (appData.autoCallFlag &&
17457                     gameMode == IcsPlayingBlack && !blackFlag) {
17458                   SendToICS(ics_prefix);
17459                   SendToICS("flag\n");
17460                 }
17461             } else {
17462                 if (blackFlag) {
17463                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17464                 } else {
17465                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17466                     if (appData.autoCallFlag) {
17467                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17468                         return TRUE;
17469                     }
17470                 }
17471             }
17472         }
17473     }
17474     if (blackTimeRemaining <= 0) {
17475         if (!blackFlag) {
17476             blackFlag = TRUE;
17477             if (appData.icsActive) {
17478                 if (appData.autoCallFlag &&
17479                     gameMode == IcsPlayingWhite && !whiteFlag) {
17480                   SendToICS(ics_prefix);
17481                   SendToICS("flag\n");
17482                 }
17483             } else {
17484                 if (whiteFlag) {
17485                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17486                 } else {
17487                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17488                     if (appData.autoCallFlag) {
17489                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17490                         return TRUE;
17491                     }
17492                 }
17493             }
17494         }
17495     }
17496     return FALSE;
17497 }
17498
17499 void
17500 CheckTimeControl ()
17501 {
17502     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17503         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17504
17505     /*
17506      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17507      */
17508     if ( !WhiteOnMove(forwardMostMove) ) {
17509         /* White made time control */
17510         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17511         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17512         /* [HGM] time odds: correct new time quota for time odds! */
17513                                             / WhitePlayer()->timeOdds;
17514         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17515     } else {
17516         lastBlack -= blackTimeRemaining;
17517         /* Black made time control */
17518         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17519                                             / WhitePlayer()->other->timeOdds;
17520         lastWhite = whiteTimeRemaining;
17521     }
17522 }
17523
17524 void
17525 DisplayBothClocks ()
17526 {
17527     int wom = gameMode == EditPosition ?
17528       !blackPlaysFirst : WhiteOnMove(currentMove);
17529     DisplayWhiteClock(whiteTimeRemaining, wom);
17530     DisplayBlackClock(blackTimeRemaining, !wom);
17531 }
17532
17533
17534 /* Timekeeping seems to be a portability nightmare.  I think everyone
17535    has ftime(), but I'm really not sure, so I'm including some ifdefs
17536    to use other calls if you don't.  Clocks will be less accurate if
17537    you have neither ftime nor gettimeofday.
17538 */
17539
17540 /* VS 2008 requires the #include outside of the function */
17541 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17542 #include <sys/timeb.h>
17543 #endif
17544
17545 /* Get the current time as a TimeMark */
17546 void
17547 GetTimeMark (TimeMark *tm)
17548 {
17549 #if HAVE_GETTIMEOFDAY
17550
17551     struct timeval timeVal;
17552     struct timezone timeZone;
17553
17554     gettimeofday(&timeVal, &timeZone);
17555     tm->sec = (long) timeVal.tv_sec;
17556     tm->ms = (int) (timeVal.tv_usec / 1000L);
17557
17558 #else /*!HAVE_GETTIMEOFDAY*/
17559 #if HAVE_FTIME
17560
17561 // include <sys/timeb.h> / moved to just above start of function
17562     struct timeb timeB;
17563
17564     ftime(&timeB);
17565     tm->sec = (long) timeB.time;
17566     tm->ms = (int) timeB.millitm;
17567
17568 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17569     tm->sec = (long) time(NULL);
17570     tm->ms = 0;
17571 #endif
17572 #endif
17573 }
17574
17575 /* Return the difference in milliseconds between two
17576    time marks.  We assume the difference will fit in a long!
17577 */
17578 long
17579 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17580 {
17581     return 1000L*(tm2->sec - tm1->sec) +
17582            (long) (tm2->ms - tm1->ms);
17583 }
17584
17585
17586 /*
17587  * Code to manage the game clocks.
17588  *
17589  * In tournament play, black starts the clock and then white makes a move.
17590  * We give the human user a slight advantage if he is playing white---the
17591  * clocks don't run until he makes his first move, so it takes zero time.
17592  * Also, we don't account for network lag, so we could get out of sync
17593  * with GNU Chess's clock -- but then, referees are always right.
17594  */
17595
17596 static TimeMark tickStartTM;
17597 static long intendedTickLength;
17598
17599 long
17600 NextTickLength (long timeRemaining)
17601 {
17602     long nominalTickLength, nextTickLength;
17603
17604     if (timeRemaining > 0L && timeRemaining <= 10000L)
17605       nominalTickLength = 100L;
17606     else
17607       nominalTickLength = 1000L;
17608     nextTickLength = timeRemaining % nominalTickLength;
17609     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17610
17611     return nextTickLength;
17612 }
17613
17614 /* Adjust clock one minute up or down */
17615 void
17616 AdjustClock (Boolean which, int dir)
17617 {
17618     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17619     if(which) blackTimeRemaining += 60000*dir;
17620     else      whiteTimeRemaining += 60000*dir;
17621     DisplayBothClocks();
17622     adjustedClock = TRUE;
17623 }
17624
17625 /* Stop clocks and reset to a fresh time control */
17626 void
17627 ResetClocks ()
17628 {
17629     (void) StopClockTimer();
17630     if (appData.icsActive) {
17631         whiteTimeRemaining = blackTimeRemaining = 0;
17632     } else if (searchTime) {
17633         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17634         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17635     } else { /* [HGM] correct new time quote for time odds */
17636         whiteTC = blackTC = fullTimeControlString;
17637         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17638         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17639     }
17640     if (whiteFlag || blackFlag) {
17641         DisplayTitle("");
17642         whiteFlag = blackFlag = FALSE;
17643     }
17644     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17645     DisplayBothClocks();
17646     adjustedClock = FALSE;
17647 }
17648
17649 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17650
17651 /* Decrement running clock by amount of time that has passed */
17652 void
17653 DecrementClocks ()
17654 {
17655     long timeRemaining;
17656     long lastTickLength, fudge;
17657     TimeMark now;
17658
17659     if (!appData.clockMode) return;
17660     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17661
17662     GetTimeMark(&now);
17663
17664     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17665
17666     /* Fudge if we woke up a little too soon */
17667     fudge = intendedTickLength - lastTickLength;
17668     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17669
17670     if (WhiteOnMove(forwardMostMove)) {
17671         if(whiteNPS >= 0) lastTickLength = 0;
17672         timeRemaining = whiteTimeRemaining -= lastTickLength;
17673         if(timeRemaining < 0 && !appData.icsActive) {
17674             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17675             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17676                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17677                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17678             }
17679         }
17680         DisplayWhiteClock(whiteTimeRemaining - fudge,
17681                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17682     } else {
17683         if(blackNPS >= 0) lastTickLength = 0;
17684         timeRemaining = blackTimeRemaining -= lastTickLength;
17685         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17686             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17687             if(suddenDeath) {
17688                 blackStartMove = forwardMostMove;
17689                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17690             }
17691         }
17692         DisplayBlackClock(blackTimeRemaining - fudge,
17693                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17694     }
17695     if (CheckFlags()) return;
17696
17697     if(twoBoards) { // count down secondary board's clocks as well
17698         activePartnerTime -= lastTickLength;
17699         partnerUp = 1;
17700         if(activePartner == 'W')
17701             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17702         else
17703             DisplayBlackClock(activePartnerTime, TRUE);
17704         partnerUp = 0;
17705     }
17706
17707     tickStartTM = now;
17708     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17709     StartClockTimer(intendedTickLength);
17710
17711     /* if the time remaining has fallen below the alarm threshold, sound the
17712      * alarm. if the alarm has sounded and (due to a takeback or time control
17713      * with increment) the time remaining has increased to a level above the
17714      * threshold, reset the alarm so it can sound again.
17715      */
17716
17717     if (appData.icsActive && appData.icsAlarm) {
17718
17719         /* make sure we are dealing with the user's clock */
17720         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17721                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17722            )) return;
17723
17724         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17725             alarmSounded = FALSE;
17726         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17727             PlayAlarmSound();
17728             alarmSounded = TRUE;
17729         }
17730     }
17731 }
17732
17733
17734 /* A player has just moved, so stop the previously running
17735    clock and (if in clock mode) start the other one.
17736    We redisplay both clocks in case we're in ICS mode, because
17737    ICS gives us an update to both clocks after every move.
17738    Note that this routine is called *after* forwardMostMove
17739    is updated, so the last fractional tick must be subtracted
17740    from the color that is *not* on move now.
17741 */
17742 void
17743 SwitchClocks (int newMoveNr)
17744 {
17745     long lastTickLength;
17746     TimeMark now;
17747     int flagged = FALSE;
17748
17749     GetTimeMark(&now);
17750
17751     if (StopClockTimer() && appData.clockMode) {
17752         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17753         if (!WhiteOnMove(forwardMostMove)) {
17754             if(blackNPS >= 0) lastTickLength = 0;
17755             blackTimeRemaining -= lastTickLength;
17756            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17757 //         if(pvInfoList[forwardMostMove].time == -1)
17758                  pvInfoList[forwardMostMove].time =               // use GUI time
17759                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17760         } else {
17761            if(whiteNPS >= 0) lastTickLength = 0;
17762            whiteTimeRemaining -= lastTickLength;
17763            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17764 //         if(pvInfoList[forwardMostMove].time == -1)
17765                  pvInfoList[forwardMostMove].time =
17766                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17767         }
17768         flagged = CheckFlags();
17769     }
17770     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17771     CheckTimeControl();
17772
17773     if (flagged || !appData.clockMode) return;
17774
17775     switch (gameMode) {
17776       case MachinePlaysBlack:
17777       case MachinePlaysWhite:
17778       case BeginningOfGame:
17779         if (pausing) return;
17780         break;
17781
17782       case EditGame:
17783       case PlayFromGameFile:
17784       case IcsExamining:
17785         return;
17786
17787       default:
17788         break;
17789     }
17790
17791     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17792         if(WhiteOnMove(forwardMostMove))
17793              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17794         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17795     }
17796
17797     tickStartTM = now;
17798     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17799       whiteTimeRemaining : blackTimeRemaining);
17800     StartClockTimer(intendedTickLength);
17801 }
17802
17803
17804 /* Stop both clocks */
17805 void
17806 StopClocks ()
17807 {
17808     long lastTickLength;
17809     TimeMark now;
17810
17811     if (!StopClockTimer()) return;
17812     if (!appData.clockMode) return;
17813
17814     GetTimeMark(&now);
17815
17816     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17817     if (WhiteOnMove(forwardMostMove)) {
17818         if(whiteNPS >= 0) lastTickLength = 0;
17819         whiteTimeRemaining -= lastTickLength;
17820         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17821     } else {
17822         if(blackNPS >= 0) lastTickLength = 0;
17823         blackTimeRemaining -= lastTickLength;
17824         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17825     }
17826     CheckFlags();
17827 }
17828
17829 /* Start clock of player on move.  Time may have been reset, so
17830    if clock is already running, stop and restart it. */
17831 void
17832 StartClocks ()
17833 {
17834     (void) StopClockTimer(); /* in case it was running already */
17835     DisplayBothClocks();
17836     if (CheckFlags()) return;
17837
17838     if (!appData.clockMode) return;
17839     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17840
17841     GetTimeMark(&tickStartTM);
17842     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17843       whiteTimeRemaining : blackTimeRemaining);
17844
17845    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17846     whiteNPS = blackNPS = -1;
17847     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17848        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17849         whiteNPS = first.nps;
17850     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17851        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17852         blackNPS = first.nps;
17853     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17854         whiteNPS = second.nps;
17855     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17856         blackNPS = second.nps;
17857     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17858
17859     StartClockTimer(intendedTickLength);
17860 }
17861
17862 char *
17863 TimeString (long ms)
17864 {
17865     long second, minute, hour, day;
17866     char *sign = "";
17867     static char buf[32];
17868
17869     if (ms > 0 && ms <= 9900) {
17870       /* convert milliseconds to tenths, rounding up */
17871       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17872
17873       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17874       return buf;
17875     }
17876
17877     /* convert milliseconds to seconds, rounding up */
17878     /* use floating point to avoid strangeness of integer division
17879        with negative dividends on many machines */
17880     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17881
17882     if (second < 0) {
17883         sign = "-";
17884         second = -second;
17885     }
17886
17887     day = second / (60 * 60 * 24);
17888     second = second % (60 * 60 * 24);
17889     hour = second / (60 * 60);
17890     second = second % (60 * 60);
17891     minute = second / 60;
17892     second = second % 60;
17893
17894     if (day > 0)
17895       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17896               sign, day, hour, minute, second);
17897     else if (hour > 0)
17898       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17899     else
17900       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17901
17902     return buf;
17903 }
17904
17905
17906 /*
17907  * This is necessary because some C libraries aren't ANSI C compliant yet.
17908  */
17909 char *
17910 StrStr (char *string, char *match)
17911 {
17912     int i, length;
17913
17914     length = strlen(match);
17915
17916     for (i = strlen(string) - length; i >= 0; i--, string++)
17917       if (!strncmp(match, string, length))
17918         return string;
17919
17920     return NULL;
17921 }
17922
17923 char *
17924 StrCaseStr (char *string, char *match)
17925 {
17926     int i, j, length;
17927
17928     length = strlen(match);
17929
17930     for (i = strlen(string) - length; i >= 0; i--, string++) {
17931         for (j = 0; j < length; j++) {
17932             if (ToLower(match[j]) != ToLower(string[j]))
17933               break;
17934         }
17935         if (j == length) return string;
17936     }
17937
17938     return NULL;
17939 }
17940
17941 #ifndef _amigados
17942 int
17943 StrCaseCmp (char *s1, char *s2)
17944 {
17945     char c1, c2;
17946
17947     for (;;) {
17948         c1 = ToLower(*s1++);
17949         c2 = ToLower(*s2++);
17950         if (c1 > c2) return 1;
17951         if (c1 < c2) return -1;
17952         if (c1 == NULLCHAR) return 0;
17953     }
17954 }
17955
17956
17957 int
17958 ToLower (int c)
17959 {
17960     return isupper(c) ? tolower(c) : c;
17961 }
17962
17963
17964 int
17965 ToUpper (int c)
17966 {
17967     return islower(c) ? toupper(c) : c;
17968 }
17969 #endif /* !_amigados    */
17970
17971 char *
17972 StrSave (char *s)
17973 {
17974   char *ret;
17975
17976   if ((ret = (char *) malloc(strlen(s) + 1)))
17977     {
17978       safeStrCpy(ret, s, strlen(s)+1);
17979     }
17980   return ret;
17981 }
17982
17983 char *
17984 StrSavePtr (char *s, char **savePtr)
17985 {
17986     if (*savePtr) {
17987         free(*savePtr);
17988     }
17989     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17990       safeStrCpy(*savePtr, s, strlen(s)+1);
17991     }
17992     return(*savePtr);
17993 }
17994
17995 char *
17996 PGNDate ()
17997 {
17998     time_t clock;
17999     struct tm *tm;
18000     char buf[MSG_SIZ];
18001
18002     clock = time((time_t *)NULL);
18003     tm = localtime(&clock);
18004     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18005             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18006     return StrSave(buf);
18007 }
18008
18009
18010 char *
18011 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18012 {
18013     int i, j, fromX, fromY, toX, toY;
18014     int whiteToPlay, haveRights = nrCastlingRights;
18015     char buf[MSG_SIZ];
18016     char *p, *q;
18017     int emptycount;
18018     ChessSquare piece;
18019
18020     whiteToPlay = (gameMode == EditPosition) ?
18021       !blackPlaysFirst : (move % 2 == 0);
18022     p = buf;
18023
18024     /* Piece placement data */
18025     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18026         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18027         emptycount = 0;
18028         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18029             if (boards[move][i][j] == EmptySquare) {
18030                 emptycount++;
18031             } else { ChessSquare piece = boards[move][i][j];
18032                 if (emptycount > 0) {
18033                     if(emptycount<10) /* [HGM] can be >= 10 */
18034                         *p++ = '0' + emptycount;
18035                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18036                     emptycount = 0;
18037                 }
18038                 if(PieceToChar(piece) == '+') {
18039                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18040                     *p++ = '+';
18041                     piece = (ChessSquare)(CHUDEMOTED(piece));
18042                 }
18043                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18044                 if(*p = PieceSuffix(piece)) p++;
18045                 if(p[-1] == '~') {
18046                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18047                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18048                     *p++ = '~';
18049                 }
18050             }
18051         }
18052         if (emptycount > 0) {
18053             if(emptycount<10) /* [HGM] can be >= 10 */
18054                 *p++ = '0' + emptycount;
18055             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18056             emptycount = 0;
18057         }
18058         *p++ = '/';
18059     }
18060     *(p - 1) = ' ';
18061
18062     /* [HGM] print Crazyhouse or Shogi holdings */
18063     if( gameInfo.holdingsWidth ) {
18064         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18065         q = p;
18066         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18067             piece = boards[move][i][BOARD_WIDTH-1];
18068             if( piece != EmptySquare )
18069               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18070                   *p++ = PieceToChar(piece);
18071         }
18072         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18073             piece = boards[move][BOARD_HEIGHT-i-1][0];
18074             if( piece != EmptySquare )
18075               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18076                   *p++ = PieceToChar(piece);
18077         }
18078
18079         if( q == p ) *p++ = '-';
18080         *p++ = ']';
18081         *p++ = ' ';
18082     }
18083
18084     /* Active color */
18085     *p++ = whiteToPlay ? 'w' : 'b';
18086     *p++ = ' ';
18087
18088   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18089     haveRights = 0; q = p;
18090     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18091       piece = boards[move][0][i];
18092       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18093         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18094       }
18095     }
18096     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18097       piece = boards[move][BOARD_HEIGHT-1][i];
18098       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18099         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18100       }
18101     }
18102     if(p == q) *p++ = '-';
18103     *p++ = ' ';
18104   }
18105
18106   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18107     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18108   } else {
18109   if(haveRights) {
18110      int handW=0, handB=0;
18111      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18112         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18113         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18114      }
18115      q = p;
18116      if(appData.fischerCastling) {
18117         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18118            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18119                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18120         } else {
18121        /* [HGM] write directly from rights */
18122            if(boards[move][CASTLING][2] != NoRights &&
18123               boards[move][CASTLING][0] != NoRights   )
18124                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18125            if(boards[move][CASTLING][2] != NoRights &&
18126               boards[move][CASTLING][1] != NoRights   )
18127                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18128         }
18129         if(handB) {
18130            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18131                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18132         } else {
18133            if(boards[move][CASTLING][5] != NoRights &&
18134               boards[move][CASTLING][3] != NoRights   )
18135                 *p++ = boards[move][CASTLING][3] + AAA;
18136            if(boards[move][CASTLING][5] != NoRights &&
18137               boards[move][CASTLING][4] != NoRights   )
18138                 *p++ = boards[move][CASTLING][4] + AAA;
18139         }
18140      } else {
18141
18142         /* [HGM] write true castling rights */
18143         if( nrCastlingRights == 6 ) {
18144             int q, k=0;
18145             if(boards[move][CASTLING][0] != NoRights &&
18146                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18147             q = (boards[move][CASTLING][1] != NoRights &&
18148                  boards[move][CASTLING][2] != NoRights  );
18149             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18150                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18151                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18152                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18153             }
18154             if(q) *p++ = 'Q';
18155             k = 0;
18156             if(boards[move][CASTLING][3] != NoRights &&
18157                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18158             q = (boards[move][CASTLING][4] != NoRights &&
18159                  boards[move][CASTLING][5] != NoRights  );
18160             if(handB) {
18161                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18162                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18163                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18164             }
18165             if(q) *p++ = 'q';
18166         }
18167      }
18168      if (q == p) *p++ = '-'; /* No castling rights */
18169      *p++ = ' ';
18170   }
18171
18172   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18173      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18174      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18175     /* En passant target square */
18176     if (move > backwardMostMove) {
18177         fromX = moveList[move - 1][0] - AAA;
18178         fromY = moveList[move - 1][1] - ONE;
18179         toX = moveList[move - 1][2] - AAA;
18180         toY = moveList[move - 1][3] - ONE;
18181         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18182             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18183             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18184             fromX == toX) {
18185             /* 2-square pawn move just happened */
18186             *p++ = toX + AAA;
18187             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18188         } else {
18189             *p++ = '-';
18190         }
18191     } else if(move == backwardMostMove) {
18192         // [HGM] perhaps we should always do it like this, and forget the above?
18193         if((signed char)boards[move][EP_STATUS] >= 0) {
18194             *p++ = boards[move][EP_STATUS] + AAA;
18195             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18196         } else {
18197             *p++ = '-';
18198         }
18199     } else {
18200         *p++ = '-';
18201     }
18202     *p++ = ' ';
18203   }
18204   }
18205
18206     if(moveCounts)
18207     {   int i = 0, j=move;
18208
18209         /* [HGM] find reversible plies */
18210         if (appData.debugMode) { int k;
18211             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18212             for(k=backwardMostMove; k<=forwardMostMove; k++)
18213                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18214
18215         }
18216
18217         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18218         if( j == backwardMostMove ) i += initialRulePlies;
18219         sprintf(p, "%d ", i);
18220         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18221
18222         /* Fullmove number */
18223         sprintf(p, "%d", (move / 2) + 1);
18224     } else *--p = NULLCHAR;
18225
18226     return StrSave(buf);
18227 }
18228
18229 Boolean
18230 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18231 {
18232     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18233     char *p, c;
18234     int emptycount, virgin[BOARD_FILES];
18235     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18236
18237     p = fen;
18238
18239     /* Piece placement data */
18240     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18241         j = 0;
18242         for (;;) {
18243             if (*p == '/' || *p == ' ' || *p == '[' ) {
18244                 if(j > w) w = j;
18245                 emptycount = gameInfo.boardWidth - j;
18246                 while (emptycount--)
18247                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18248                 if (*p == '/') p++;
18249                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18250                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18251                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18252                     }
18253                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18254                 }
18255                 break;
18256 #if(BOARD_FILES >= 10)*0
18257             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18258                 p++; emptycount=10;
18259                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18260                 while (emptycount--)
18261                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18262 #endif
18263             } else if (*p == '*') {
18264                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18265             } else if (isdigit(*p)) {
18266                 emptycount = *p++ - '0';
18267                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18268                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18269                 while (emptycount--)
18270                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18271             } else if (*p == '<') {
18272                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18273                 else if (i != 0 || !shuffle) return FALSE;
18274                 p++;
18275             } else if (shuffle && *p == '>') {
18276                 p++; // for now ignore closing shuffle range, and assume rank-end
18277             } else if (*p == '?') {
18278                 if (j >= gameInfo.boardWidth) return FALSE;
18279                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18280                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18281             } else if (*p == '+' || isalpha(*p)) {
18282                 char *q, *s = SUFFIXES;
18283                 if (j >= gameInfo.boardWidth) return FALSE;
18284                 if(*p=='+') {
18285                     char c = *++p;
18286                     if(q = strchr(s, p[1])) p++;
18287                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18288                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18289                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18290                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18291                 } else {
18292                     char c = *p++;
18293                     if(q = strchr(s, *p)) p++;
18294                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18295                 }
18296
18297                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18298                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18299                     piece = (ChessSquare) (PROMOTED(piece));
18300                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18301                     p++;
18302                 }
18303                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18304                 if(piece == king) wKingRank = i;
18305                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18306             } else {
18307                 return FALSE;
18308             }
18309         }
18310     }
18311     while (*p == '/' || *p == ' ') p++;
18312
18313     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18314
18315     /* [HGM] by default clear Crazyhouse holdings, if present */
18316     if(gameInfo.holdingsWidth) {
18317        for(i=0; i<BOARD_HEIGHT; i++) {
18318            board[i][0]             = EmptySquare; /* black holdings */
18319            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18320            board[i][1]             = (ChessSquare) 0; /* black counts */
18321            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18322        }
18323     }
18324
18325     /* [HGM] look for Crazyhouse holdings here */
18326     while(*p==' ') p++;
18327     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18328         int swap=0, wcnt=0, bcnt=0;
18329         if(*p == '[') p++;
18330         if(*p == '<') swap++, p++;
18331         if(*p == '-' ) p++; /* empty holdings */ else {
18332             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18333             /* if we would allow FEN reading to set board size, we would   */
18334             /* have to add holdings and shift the board read so far here   */
18335             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18336                 p++;
18337                 if((int) piece >= (int) BlackPawn ) {
18338                     i = (int)piece - (int)BlackPawn;
18339                     i = PieceToNumber((ChessSquare)i);
18340                     if( i >= gameInfo.holdingsSize ) return FALSE;
18341                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18342                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18343                     bcnt++;
18344                 } else {
18345                     i = (int)piece - (int)WhitePawn;
18346                     i = PieceToNumber((ChessSquare)i);
18347                     if( i >= gameInfo.holdingsSize ) return FALSE;
18348                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18349                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18350                     wcnt++;
18351                 }
18352             }
18353             if(subst) { // substitute back-rank question marks by holdings pieces
18354                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18355                     int k, m, n = bcnt + 1;
18356                     if(board[0][j] == ClearBoard) {
18357                         if(!wcnt) return FALSE;
18358                         n = rand() % wcnt;
18359                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18360                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18361                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18362                             break;
18363                         }
18364                     }
18365                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18366                         if(!bcnt) return FALSE;
18367                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18368                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18369                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18370                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18371                             break;
18372                         }
18373                     }
18374                 }
18375                 subst = 0;
18376             }
18377         }
18378         if(*p == ']') p++;
18379     }
18380
18381     if(subst) return FALSE; // substitution requested, but no holdings
18382
18383     while(*p == ' ') p++;
18384
18385     /* Active color */
18386     c = *p++;
18387     if(appData.colorNickNames) {
18388       if( c == appData.colorNickNames[0] ) c = 'w'; else
18389       if( c == appData.colorNickNames[1] ) c = 'b';
18390     }
18391     switch (c) {
18392       case 'w':
18393         *blackPlaysFirst = FALSE;
18394         break;
18395       case 'b':
18396         *blackPlaysFirst = TRUE;
18397         break;
18398       default:
18399         return FALSE;
18400     }
18401
18402     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18403     /* return the extra info in global variiables             */
18404
18405     while(*p==' ') p++;
18406
18407     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18408         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18409         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18410     }
18411
18412     /* set defaults in case FEN is incomplete */
18413     board[EP_STATUS] = EP_UNKNOWN;
18414     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18415     for(i=0; i<nrCastlingRights; i++ ) {
18416         board[CASTLING][i] =
18417             appData.fischerCastling ? NoRights : initialRights[i];
18418     }   /* assume possible unless obviously impossible */
18419     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18420     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18421     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18422                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18423     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18424     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18425     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18426                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18427     FENrulePlies = 0;
18428
18429     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18430       char *q = p;
18431       int w=0, b=0;
18432       while(isalpha(*p)) {
18433         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18434         if(islower(*p)) b |= 1 << (*p++ - 'a');
18435       }
18436       if(*p == '-') p++;
18437       if(p != q) {
18438         board[TOUCHED_W] = ~w;
18439         board[TOUCHED_B] = ~b;
18440         while(*p == ' ') p++;
18441       }
18442     } else
18443
18444     if(nrCastlingRights) {
18445       int fischer = 0;
18446       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18447       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18448           /* castling indicator present, so default becomes no castlings */
18449           for(i=0; i<nrCastlingRights; i++ ) {
18450                  board[CASTLING][i] = NoRights;
18451           }
18452       }
18453       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18454              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18455              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18456              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18457         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18458
18459         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18460             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18461             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18462         }
18463         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18464             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18465         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18466                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18467         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18468                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18469         switch(c) {
18470           case'K':
18471               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18472               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18473               board[CASTLING][2] = whiteKingFile;
18474               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18475               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18476               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18477               break;
18478           case'Q':
18479               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18480               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18481               board[CASTLING][2] = whiteKingFile;
18482               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18483               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18484               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18485               break;
18486           case'k':
18487               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18488               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18489               board[CASTLING][5] = blackKingFile;
18490               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18491               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18492               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18493               break;
18494           case'q':
18495               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18496               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18497               board[CASTLING][5] = blackKingFile;
18498               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18499               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18500               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18501           case '-':
18502               break;
18503           default: /* FRC castlings */
18504               if(c >= 'a') { /* black rights */
18505                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18506                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18507                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18508                   if(i == BOARD_RGHT) break;
18509                   board[CASTLING][5] = i;
18510                   c -= AAA;
18511                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18512                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18513                   if(c > i)
18514                       board[CASTLING][3] = c;
18515                   else
18516                       board[CASTLING][4] = c;
18517               } else { /* white rights */
18518                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18519                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18520                     if(board[0][i] == WhiteKing) break;
18521                   if(i == BOARD_RGHT) break;
18522                   board[CASTLING][2] = i;
18523                   c -= AAA - 'a' + 'A';
18524                   if(board[0][c] >= WhiteKing) break;
18525                   if(c > i)
18526                       board[CASTLING][0] = c;
18527                   else
18528                       board[CASTLING][1] = c;
18529               }
18530         }
18531       }
18532       for(i=0; i<nrCastlingRights; i++)
18533         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18534       if(gameInfo.variant == VariantSChess)
18535         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18536       if(fischer && shuffle) appData.fischerCastling = TRUE;
18537     if (appData.debugMode) {
18538         fprintf(debugFP, "FEN castling rights:");
18539         for(i=0; i<nrCastlingRights; i++)
18540         fprintf(debugFP, " %d", board[CASTLING][i]);
18541         fprintf(debugFP, "\n");
18542     }
18543
18544       while(*p==' ') p++;
18545     }
18546
18547     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18548
18549     /* read e.p. field in games that know e.p. capture */
18550     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18551        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18552        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18553       if(*p=='-') {
18554         p++; board[EP_STATUS] = EP_NONE;
18555       } else {
18556          char c = *p++ - AAA;
18557
18558          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18559          if(*p >= '0' && *p <='9') p++;
18560          board[EP_STATUS] = c;
18561       }
18562     }
18563
18564
18565     if(sscanf(p, "%d", &i) == 1) {
18566         FENrulePlies = i; /* 50-move ply counter */
18567         /* (The move number is still ignored)    */
18568     }
18569
18570     return TRUE;
18571 }
18572
18573 void
18574 EditPositionPasteFEN (char *fen)
18575 {
18576   if (fen != NULL) {
18577     Board initial_position;
18578
18579     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18580       DisplayError(_("Bad FEN position in clipboard"), 0);
18581       return ;
18582     } else {
18583       int savedBlackPlaysFirst = blackPlaysFirst;
18584       EditPositionEvent();
18585       blackPlaysFirst = savedBlackPlaysFirst;
18586       CopyBoard(boards[0], initial_position);
18587       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18588       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18589       DisplayBothClocks();
18590       DrawPosition(FALSE, boards[currentMove]);
18591     }
18592   }
18593 }
18594
18595 static char cseq[12] = "\\   ";
18596
18597 Boolean
18598 set_cont_sequence (char *new_seq)
18599 {
18600     int len;
18601     Boolean ret;
18602
18603     // handle bad attempts to set the sequence
18604         if (!new_seq)
18605                 return 0; // acceptable error - no debug
18606
18607     len = strlen(new_seq);
18608     ret = (len > 0) && (len < sizeof(cseq));
18609     if (ret)
18610       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18611     else if (appData.debugMode)
18612       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18613     return ret;
18614 }
18615
18616 /*
18617     reformat a source message so words don't cross the width boundary.  internal
18618     newlines are not removed.  returns the wrapped size (no null character unless
18619     included in source message).  If dest is NULL, only calculate the size required
18620     for the dest buffer.  lp argument indicats line position upon entry, and it's
18621     passed back upon exit.
18622 */
18623 int
18624 wrap (char *dest, char *src, int count, int width, int *lp)
18625 {
18626     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18627
18628     cseq_len = strlen(cseq);
18629     old_line = line = *lp;
18630     ansi = len = clen = 0;
18631
18632     for (i=0; i < count; i++)
18633     {
18634         if (src[i] == '\033')
18635             ansi = 1;
18636
18637         // if we hit the width, back up
18638         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18639         {
18640             // store i & len in case the word is too long
18641             old_i = i, old_len = len;
18642
18643             // find the end of the last word
18644             while (i && src[i] != ' ' && src[i] != '\n')
18645             {
18646                 i--;
18647                 len--;
18648             }
18649
18650             // word too long?  restore i & len before splitting it
18651             if ((old_i-i+clen) >= width)
18652             {
18653                 i = old_i;
18654                 len = old_len;
18655             }
18656
18657             // extra space?
18658             if (i && src[i-1] == ' ')
18659                 len--;
18660
18661             if (src[i] != ' ' && src[i] != '\n')
18662             {
18663                 i--;
18664                 if (len)
18665                     len--;
18666             }
18667
18668             // now append the newline and continuation sequence
18669             if (dest)
18670                 dest[len] = '\n';
18671             len++;
18672             if (dest)
18673                 strncpy(dest+len, cseq, cseq_len);
18674             len += cseq_len;
18675             line = cseq_len;
18676             clen = cseq_len;
18677             continue;
18678         }
18679
18680         if (dest)
18681             dest[len] = src[i];
18682         len++;
18683         if (!ansi)
18684             line++;
18685         if (src[i] == '\n')
18686             line = 0;
18687         if (src[i] == 'm')
18688             ansi = 0;
18689     }
18690     if (dest && appData.debugMode)
18691     {
18692         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18693             count, width, line, len, *lp);
18694         show_bytes(debugFP, src, count);
18695         fprintf(debugFP, "\ndest: ");
18696         show_bytes(debugFP, dest, len);
18697         fprintf(debugFP, "\n");
18698     }
18699     *lp = dest ? line : old_line;
18700
18701     return len;
18702 }
18703
18704 // [HGM] vari: routines for shelving variations
18705 Boolean modeRestore = FALSE;
18706
18707 void
18708 PushInner (int firstMove, int lastMove)
18709 {
18710         int i, j, nrMoves = lastMove - firstMove;
18711
18712         // push current tail of game on stack
18713         savedResult[storedGames] = gameInfo.result;
18714         savedDetails[storedGames] = gameInfo.resultDetails;
18715         gameInfo.resultDetails = NULL;
18716         savedFirst[storedGames] = firstMove;
18717         savedLast [storedGames] = lastMove;
18718         savedFramePtr[storedGames] = framePtr;
18719         framePtr -= nrMoves; // reserve space for the boards
18720         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18721             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18722             for(j=0; j<MOVE_LEN; j++)
18723                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18724             for(j=0; j<2*MOVE_LEN; j++)
18725                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18726             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18727             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18728             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18729             pvInfoList[firstMove+i-1].depth = 0;
18730             commentList[framePtr+i] = commentList[firstMove+i];
18731             commentList[firstMove+i] = NULL;
18732         }
18733
18734         storedGames++;
18735         forwardMostMove = firstMove; // truncate game so we can start variation
18736 }
18737
18738 void
18739 PushTail (int firstMove, int lastMove)
18740 {
18741         if(appData.icsActive) { // only in local mode
18742                 forwardMostMove = currentMove; // mimic old ICS behavior
18743                 return;
18744         }
18745         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18746
18747         PushInner(firstMove, lastMove);
18748         if(storedGames == 1) GreyRevert(FALSE);
18749         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18750 }
18751
18752 void
18753 PopInner (Boolean annotate)
18754 {
18755         int i, j, nrMoves;
18756         char buf[8000], moveBuf[20];
18757
18758         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18759         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18760         nrMoves = savedLast[storedGames] - currentMove;
18761         if(annotate) {
18762                 int cnt = 10;
18763                 if(!WhiteOnMove(currentMove))
18764                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18765                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18766                 for(i=currentMove; i<forwardMostMove; i++) {
18767                         if(WhiteOnMove(i))
18768                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18769                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18770                         strcat(buf, moveBuf);
18771                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18772                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18773                 }
18774                 strcat(buf, ")");
18775         }
18776         for(i=1; i<=nrMoves; i++) { // copy last variation back
18777             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18778             for(j=0; j<MOVE_LEN; j++)
18779                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18780             for(j=0; j<2*MOVE_LEN; j++)
18781                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18782             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18783             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18784             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18785             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18786             commentList[currentMove+i] = commentList[framePtr+i];
18787             commentList[framePtr+i] = NULL;
18788         }
18789         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18790         framePtr = savedFramePtr[storedGames];
18791         gameInfo.result = savedResult[storedGames];
18792         if(gameInfo.resultDetails != NULL) {
18793             free(gameInfo.resultDetails);
18794       }
18795         gameInfo.resultDetails = savedDetails[storedGames];
18796         forwardMostMove = currentMove + nrMoves;
18797 }
18798
18799 Boolean
18800 PopTail (Boolean annotate)
18801 {
18802         if(appData.icsActive) return FALSE; // only in local mode
18803         if(!storedGames) return FALSE; // sanity
18804         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18805
18806         PopInner(annotate);
18807         if(currentMove < forwardMostMove) ForwardEvent(); else
18808         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18809
18810         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18811         return TRUE;
18812 }
18813
18814 void
18815 CleanupTail ()
18816 {       // remove all shelved variations
18817         int i;
18818         for(i=0; i<storedGames; i++) {
18819             if(savedDetails[i])
18820                 free(savedDetails[i]);
18821             savedDetails[i] = NULL;
18822         }
18823         for(i=framePtr; i<MAX_MOVES; i++) {
18824                 if(commentList[i]) free(commentList[i]);
18825                 commentList[i] = NULL;
18826         }
18827         framePtr = MAX_MOVES-1;
18828         storedGames = 0;
18829 }
18830
18831 void
18832 LoadVariation (int index, char *text)
18833 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18834         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18835         int level = 0, move;
18836
18837         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18838         // first find outermost bracketing variation
18839         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18840             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18841                 if(*p == '{') wait = '}'; else
18842                 if(*p == '[') wait = ']'; else
18843                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18844                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18845             }
18846             if(*p == wait) wait = NULLCHAR; // closing ]} found
18847             p++;
18848         }
18849         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18850         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18851         end[1] = NULLCHAR; // clip off comment beyond variation
18852         ToNrEvent(currentMove-1);
18853         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18854         // kludge: use ParsePV() to append variation to game
18855         move = currentMove;
18856         ParsePV(start, TRUE, TRUE);
18857         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18858         ClearPremoveHighlights();
18859         CommentPopDown();
18860         ToNrEvent(currentMove+1);
18861 }
18862
18863 void
18864 LoadTheme ()
18865 {
18866     char *p, *q, buf[MSG_SIZ];
18867     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18868         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18869         ParseArgsFromString(buf);
18870         ActivateTheme(TRUE); // also redo colors
18871         return;
18872     }
18873     p = nickName;
18874     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18875     {
18876         int len;
18877         q = appData.themeNames;
18878         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18879       if(appData.useBitmaps) {
18880         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18881                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18882                 appData.liteBackTextureMode,
18883                 appData.darkBackTextureMode );
18884       } else {
18885         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18886                 Col2Text(2),   // lightSquareColor
18887                 Col2Text(3) ); // darkSquareColor
18888       }
18889       if(appData.useBorder) {
18890         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18891                 appData.border);
18892       } else {
18893         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18894       }
18895       if(appData.useFont) {
18896         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18897                 appData.renderPiecesWithFont,
18898                 appData.fontToPieceTable,
18899                 Col2Text(9),    // appData.fontBackColorWhite
18900                 Col2Text(10) ); // appData.fontForeColorBlack
18901       } else {
18902         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18903                 appData.pieceDirectory);
18904         if(!appData.pieceDirectory[0])
18905           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18906                 Col2Text(0),   // whitePieceColor
18907                 Col2Text(1) ); // blackPieceColor
18908       }
18909       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18910                 Col2Text(4),   // highlightSquareColor
18911                 Col2Text(5) ); // premoveHighlightColor
18912         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18913         if(insert != q) insert[-1] = NULLCHAR;
18914         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18915         if(q)   free(q);
18916     }
18917     ActivateTheme(FALSE);
18918 }