Improve SAN of Pawn moves and allow Betza e.p. definition
[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 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 static Boolean pieceDefs;
299
300 /* States for ics_getting_history */
301 #define H_FALSE 0
302 #define H_REQUESTED 1
303 #define H_GOT_REQ_HEADER 2
304 #define H_GOT_UNREQ_HEADER 3
305 #define H_GETTING_MOVES 4
306 #define H_GOT_UNWANTED_HEADER 5
307
308 /* whosays values for GameEnds */
309 #define GE_ICS 0
310 #define GE_ENGINE 1
311 #define GE_PLAYER 2
312 #define GE_FILE 3
313 #define GE_XBOARD 4
314 #define GE_ENGINE1 5
315 #define GE_ENGINE2 6
316
317 /* Maximum number of games in a cmail message */
318 #define CMAIL_MAX_GAMES 20
319
320 /* Different types of move when calling RegisterMove */
321 #define CMAIL_MOVE   0
322 #define CMAIL_RESIGN 1
323 #define CMAIL_DRAW   2
324 #define CMAIL_ACCEPT 3
325
326 /* Different types of result to remember for each game */
327 #define CMAIL_NOT_RESULT 0
328 #define CMAIL_OLD_RESULT 1
329 #define CMAIL_NEW_RESULT 2
330
331 /* Telnet protocol constants */
332 #define TN_WILL 0373
333 #define TN_WONT 0374
334 #define TN_DO   0375
335 #define TN_DONT 0376
336 #define TN_IAC  0377
337 #define TN_ECHO 0001
338 #define TN_SGA  0003
339 #define TN_PORT 23
340
341 char*
342 safeStrCpy (char *dst, const char *src, size_t count)
343 { // [HGM] made safe
344   int i;
345   assert( dst != NULL );
346   assert( src != NULL );
347   assert( count > 0 );
348
349   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
350   if(  i == count && dst[count-1] != NULLCHAR)
351     {
352       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
353       if(appData.debugMode)
354         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
355     }
356
357   return dst;
358 }
359
360 /* Some compiler can't cast u64 to double
361  * This function do the job for us:
362
363  * We use the highest bit for cast, this only
364  * works if the highest bit is not
365  * in use (This should not happen)
366  *
367  * We used this for all compiler
368  */
369 double
370 u64ToDouble (u64 value)
371 {
372   double r;
373   u64 tmp = value & u64Const(0x7fffffffffffffff);
374   r = (double)(s64)tmp;
375   if (value & u64Const(0x8000000000000000))
376        r +=  9.2233720368547758080e18; /* 2^63 */
377  return r;
378 }
379
380 /* Fake up flags for now, as we aren't keeping track of castling
381    availability yet. [HGM] Change of logic: the flag now only
382    indicates the type of castlings allowed by the rule of the game.
383    The actual rights themselves are maintained in the array
384    castlingRights, as part of the game history, and are not probed
385    by this function.
386  */
387 int
388 PosFlags (index)
389 {
390   int flags = F_ALL_CASTLE_OK;
391   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
392   switch (gameInfo.variant) {
393   case VariantSuicide:
394     flags &= ~F_ALL_CASTLE_OK;
395   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
396     flags |= F_IGNORE_CHECK;
397   case VariantLosers:
398     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399     break;
400   case VariantAtomic:
401     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
402     break;
403   case VariantKriegspiel:
404     flags |= F_KRIEGSPIEL_CAPTURE;
405     break;
406   case VariantCapaRandom:
407   case VariantFischeRandom:
408     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
409   case VariantNoCastle:
410   case VariantShatranj:
411   case VariantCourier:
412   case VariantMakruk:
413   case VariantASEAN:
414   case VariantGrand:
415     flags &= ~F_ALL_CASTLE_OK;
416     break;
417   case VariantChu:
418   case VariantChuChess:
419   case VariantLion:
420     flags |= F_NULL_MOVE;
421     break;
422   default:
423     break;
424   }
425   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
426   return flags;
427 }
428
429 FILE *gameFileFP, *debugFP, *serverFP;
430 char *currentDebugFile; // [HGM] debug split: to remember name
431
432 /*
433     [AS] Note: sometimes, the sscanf() function is used to parse the input
434     into a fixed-size buffer. Because of this, we must be prepared to
435     receive strings as long as the size of the input buffer, which is currently
436     set to 4K for Windows and 8K for the rest.
437     So, we must either allocate sufficiently large buffers here, or
438     reduce the size of the input buffer in the input reading part.
439 */
440
441 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
442 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
443 char thinkOutput1[MSG_SIZ*10];
444
445 ChessProgramState first, second, pairing;
446
447 /* premove variables */
448 int premoveToX = 0;
449 int premoveToY = 0;
450 int premoveFromX = 0;
451 int premoveFromY = 0;
452 int premovePromoChar = 0;
453 int gotPremove = 0;
454 Boolean alarmSounded;
455 /* end premove variables */
456
457 char *ics_prefix = "$";
458 enum ICS_TYPE ics_type = ICS_GENERIC;
459
460 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
461 int pauseExamForwardMostMove = 0;
462 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
463 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
464 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
465 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
466 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
467 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
468 int whiteFlag = FALSE, blackFlag = FALSE;
469 int userOfferedDraw = FALSE;
470 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
471 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
472 int cmailMoveType[CMAIL_MAX_GAMES];
473 long ics_clock_paused = 0;
474 ProcRef icsPR = NoProc, cmailPR = NoProc;
475 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
476 GameMode gameMode = BeginningOfGame;
477 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
478 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
479 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
480 int hiddenThinkOutputState = 0; /* [AS] */
481 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
482 int adjudicateLossPlies = 6;
483 char white_holding[64], black_holding[64];
484 TimeMark lastNodeCountTime;
485 long lastNodeCount=0;
486 int shiftKey, controlKey; // [HGM] set by mouse handler
487
488 int have_sent_ICS_logon = 0;
489 int movesPerSession;
490 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
491 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
492 Boolean adjustedClock;
493 long timeControl_2; /* [AS] Allow separate time controls */
494 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
495 long timeRemaining[2][MAX_MOVES];
496 int matchGame = 0, nextGame = 0, roundNr = 0;
497 Boolean waitingForGame = FALSE, startingEngine = FALSE;
498 TimeMark programStartTime, pauseStart;
499 char ics_handle[MSG_SIZ];
500 int have_set_title = 0;
501
502 /* animateTraining preserves the state of appData.animate
503  * when Training mode is activated. This allows the
504  * response to be animated when appData.animate == TRUE and
505  * appData.animateDragging == TRUE.
506  */
507 Boolean animateTraining;
508
509 GameInfo gameInfo;
510
511 AppData appData;
512
513 Board boards[MAX_MOVES];
514 /* [HGM] Following 7 needed for accurate legality tests: */
515 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
516 signed char  initialRights[BOARD_FILES];
517 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
518 int   initialRulePlies, FENrulePlies;
519 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
520 int loadFlag = 0;
521 Boolean shuffleOpenings;
522 int mute; // mute all sounds
523
524 // [HGM] vari: next 12 to save and restore variations
525 #define MAX_VARIATIONS 10
526 int framePtr = MAX_MOVES-1; // points to free stack entry
527 int storedGames = 0;
528 int savedFirst[MAX_VARIATIONS];
529 int savedLast[MAX_VARIATIONS];
530 int savedFramePtr[MAX_VARIATIONS];
531 char *savedDetails[MAX_VARIATIONS];
532 ChessMove savedResult[MAX_VARIATIONS];
533
534 void PushTail P((int firstMove, int lastMove));
535 Boolean PopTail P((Boolean annotate));
536 void PushInner P((int firstMove, int lastMove));
537 void PopInner P((Boolean annotate));
538 void CleanupTail P((void));
539
540 ChessSquare  FIDEArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544         BlackKing, BlackBishop, BlackKnight, BlackRook }
545 };
546
547 ChessSquare twoKingsArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
549         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
551         BlackKing, BlackKing, BlackKnight, BlackRook }
552 };
553
554 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
556         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
557     { BlackRook, BlackMan, BlackBishop, BlackQueen,
558         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
559 };
560
561 ChessSquare SpartanArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
565         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
566 };
567
568 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
569     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
570         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
571     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
572         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
573 };
574
575 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
577         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
579         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
584         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackMan, BlackFerz,
586         BlackKing, BlackMan, BlackKnight, BlackRook }
587 };
588
589 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
590     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
591         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackMan, BlackFerz,
593         BlackKing, BlackMan, BlackKnight, BlackRook }
594 };
595
596 ChessSquare  lionArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
598         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackLion, BlackBishop, BlackQueen,
600         BlackKing, BlackBishop, BlackKnight, BlackRook }
601 };
602
603
604 #if (BOARD_FILES>=10)
605 ChessSquare ShogiArray[2][BOARD_FILES] = {
606     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
607         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
608     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
609         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
610 };
611
612 ChessSquare XiangqiArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
614         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
616         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
617 };
618
619 ChessSquare CapablancaArray[2][BOARD_FILES] = {
620     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
621         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
622     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
623         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
624 };
625
626 ChessSquare GreatArray[2][BOARD_FILES] = {
627     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
628         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
629     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
630         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
631 };
632
633 ChessSquare JanusArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
635         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
636     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
637         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
638 };
639
640 ChessSquare GrandArray[2][BOARD_FILES] = {
641     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
642         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
643     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
644         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
645 };
646
647 ChessSquare ChuChessArray[2][BOARD_FILES] = {
648     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
649         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
650     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
651         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
652 };
653
654 #ifdef GOTHIC
655 ChessSquare GothicArray[2][BOARD_FILES] = {
656     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
657         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
658     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
659         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
660 };
661 #else // !GOTHIC
662 #define GothicArray CapablancaArray
663 #endif // !GOTHIC
664
665 #ifdef FALCON
666 ChessSquare FalconArray[2][BOARD_FILES] = {
667     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
668         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
669     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
670         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
671 };
672 #else // !FALCON
673 #define FalconArray CapablancaArray
674 #endif // !FALCON
675
676 #else // !(BOARD_FILES>=10)
677 #define XiangqiPosition FIDEArray
678 #define CapablancaArray FIDEArray
679 #define GothicArray FIDEArray
680 #define GreatArray FIDEArray
681 #endif // !(BOARD_FILES>=10)
682
683 #if (BOARD_FILES>=12)
684 ChessSquare CourierArray[2][BOARD_FILES] = {
685     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
686         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
687     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
688         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
689 };
690 ChessSquare ChuArray[6][BOARD_FILES] = {
691     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
692       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
693     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
694       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
695     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
696       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
697     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
698       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
699     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
700       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
701     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
702       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
703 };
704 #else // !(BOARD_FILES>=12)
705 #define CourierArray CapablancaArray
706 #define ChuArray CapablancaArray
707 #endif // !(BOARD_FILES>=12)
708
709
710 Board initialPosition;
711
712
713 /* Convert str to a rating. Checks for special cases of "----",
714
715    "++++", etc. Also strips ()'s */
716 int
717 string_to_rating (char *str)
718 {
719   while(*str && !isdigit(*str)) ++str;
720   if (!*str)
721     return 0;   /* One of the special "no rating" cases */
722   else
723     return atoi(str);
724 }
725
726 void
727 ClearProgramStats ()
728 {
729     /* Init programStats */
730     programStats.movelist[0] = 0;
731     programStats.depth = 0;
732     programStats.nr_moves = 0;
733     programStats.moves_left = 0;
734     programStats.nodes = 0;
735     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
736     programStats.score = 0;
737     programStats.got_only_move = 0;
738     programStats.got_fail = 0;
739     programStats.line_is_book = 0;
740 }
741
742 void
743 CommonEngineInit ()
744 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
745     if (appData.firstPlaysBlack) {
746         first.twoMachinesColor = "black\n";
747         second.twoMachinesColor = "white\n";
748     } else {
749         first.twoMachinesColor = "white\n";
750         second.twoMachinesColor = "black\n";
751     }
752
753     first.other = &second;
754     second.other = &first;
755
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = appData.timeOdds[0];
759             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
760         }
761         first.timeOdds  = appData.timeOdds[0]/norm;
762         second.timeOdds = appData.timeOdds[1]/norm;
763     }
764
765     if(programVersion) free(programVersion);
766     if (appData.noChessProgram) {
767         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
768         sprintf(programVersion, "%s", PACKAGE_STRING);
769     } else {
770       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
771       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
772       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
773     }
774 }
775
776 void
777 UnloadEngine (ChessProgramState *cps)
778 {
779         /* Kill off first chess program */
780         if (cps->isr != NULL)
781           RemoveInputSource(cps->isr);
782         cps->isr = NULL;
783
784         if (cps->pr != NoProc) {
785             ExitAnalyzeMode();
786             DoSleep( appData.delayBeforeQuit );
787             SendToProgram("quit\n", cps);
788             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
789         }
790         cps->pr = NoProc;
791         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
792 }
793
794 void
795 ClearOptions (ChessProgramState *cps)
796 {
797     int i;
798     cps->nrOptions = cps->comboCnt = 0;
799     for(i=0; i<MAX_OPTIONS; i++) {
800         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
801         cps->option[i].textValue = 0;
802     }
803 }
804
805 char *engineNames[] = {
806   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
807      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
808 N_("first"),
809   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("second")
812 };
813
814 void
815 InitEngine (ChessProgramState *cps, int n)
816 {   // [HGM] all engine initialiation put in a function that does one engine
817
818     ClearOptions(cps);
819
820     cps->which = engineNames[n];
821     cps->maybeThinking = FALSE;
822     cps->pr = NoProc;
823     cps->isr = NULL;
824     cps->sendTime = 2;
825     cps->sendDrawOffers = 1;
826
827     cps->program = appData.chessProgram[n];
828     cps->host = appData.host[n];
829     cps->dir = appData.directory[n];
830     cps->initString = appData.engInitString[n];
831     cps->computerString = appData.computerString[n];
832     cps->useSigint  = TRUE;
833     cps->useSigterm = TRUE;
834     cps->reuse = appData.reuse[n];
835     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
836     cps->useSetboard = FALSE;
837     cps->useSAN = FALSE;
838     cps->usePing = FALSE;
839     cps->lastPing = 0;
840     cps->lastPong = 0;
841     cps->usePlayother = FALSE;
842     cps->useColors = TRUE;
843     cps->useUsermove = FALSE;
844     cps->sendICS = FALSE;
845     cps->sendName = appData.icsActive;
846     cps->sdKludge = FALSE;
847     cps->stKludge = FALSE;
848     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
849     TidyProgramName(cps->program, cps->host, cps->tidy);
850     cps->matchWins = 0;
851     ASSIGN(cps->variants, appData.variant);
852     cps->analysisSupport = 2; /* detect */
853     cps->analyzing = FALSE;
854     cps->initDone = FALSE;
855     cps->reload = FALSE;
856     cps->pseudo = appData.pseudo[n];
857
858     /* New features added by Tord: */
859     cps->useFEN960 = FALSE;
860     cps->useOOCastle = TRUE;
861     /* End of new features added by Tord. */
862     cps->fenOverride  = appData.fenOverride[n];
863
864     /* [HGM] time odds: set factor for each machine */
865     cps->timeOdds  = appData.timeOdds[n];
866
867     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
868     cps->accumulateTC = appData.accumulateTC[n];
869     cps->maxNrOfSessions = 1;
870
871     /* [HGM] debug */
872     cps->debug = FALSE;
873
874     cps->drawDepth = appData.drawDepth[n];
875     cps->supportsNPS = UNKNOWN;
876     cps->memSize = FALSE;
877     cps->maxCores = FALSE;
878     ASSIGN(cps->egtFormats, "");
879
880     /* [HGM] options */
881     cps->optionSettings  = appData.engOptions[n];
882
883     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
884     cps->isUCI = appData.isUCI[n]; /* [AS] */
885     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
886     cps->highlight = 0;
887
888     if (appData.protocolVersion[n] > PROTOVER
889         || appData.protocolVersion[n] < 1)
890       {
891         char buf[MSG_SIZ];
892         int len;
893
894         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
895                        appData.protocolVersion[n]);
896         if( (len >= MSG_SIZ) && appData.debugMode )
897           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
898
899         DisplayFatalError(buf, 0, 2);
900       }
901     else
902       {
903         cps->protocolVersion = appData.protocolVersion[n];
904       }
905
906     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
907     ParseFeatures(appData.featureDefaults, cps);
908 }
909
910 ChessProgramState *savCps;
911
912 GameMode oldMode;
913
914 void
915 LoadEngine ()
916 {
917     int i;
918     if(WaitForEngine(savCps, LoadEngine)) return;
919     CommonEngineInit(); // recalculate time odds
920     if(gameInfo.variant != StringToVariant(appData.variant)) {
921         // we changed variant when loading the engine; this forces us to reset
922         Reset(TRUE, savCps != &first);
923         oldMode = BeginningOfGame; // to prevent restoring old mode
924     }
925     InitChessProgram(savCps, FALSE);
926     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
927     DisplayMessage("", "");
928     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
929     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
930     ThawUI();
931     SetGNUMode();
932     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
933 }
934
935 void
936 ReplaceEngine (ChessProgramState *cps, int n)
937 {
938     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
939     keepInfo = 1;
940     if(oldMode != BeginningOfGame) EditGameEvent();
941     keepInfo = 0;
942     UnloadEngine(cps);
943     appData.noChessProgram = FALSE;
944     appData.clockMode = TRUE;
945     InitEngine(cps, n);
946     UpdateLogos(TRUE);
947     if(n) return; // only startup first engine immediately; second can wait
948     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
949     LoadEngine();
950 }
951
952 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
953 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
954
955 static char resetOptions[] =
956         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
957         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
958         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
959         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
960
961 void
962 FloatToFront(char **list, char *engineLine)
963 {
964     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
965     int i=0;
966     if(appData.recentEngines <= 0) return;
967     TidyProgramName(engineLine, "localhost", tidy+1);
968     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
969     strncpy(buf+1, *list, MSG_SIZ-50);
970     if(p = strstr(buf, tidy)) { // tidy name appears in list
971         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
972         while(*p++ = *++q); // squeeze out
973     }
974     strcat(tidy, buf+1); // put list behind tidy name
975     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
976     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
977     ASSIGN(*list, tidy+1);
978 }
979
980 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
981
982 void
983 Load (ChessProgramState *cps, int i)
984 {
985     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
986     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
987         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
988         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
989         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
990         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
991         appData.firstProtocolVersion = PROTOVER;
992         ParseArgsFromString(buf);
993         SwapEngines(i);
994         ReplaceEngine(cps, i);
995         FloatToFront(&appData.recentEngineList, engineLine);
996         return;
997     }
998     p = engineName;
999     while(q = strchr(p, SLASH)) p = q+1;
1000     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1001     if(engineDir[0] != NULLCHAR) {
1002         ASSIGN(appData.directory[i], engineDir); p = engineName;
1003     } else if(p != engineName) { // derive directory from engine path, when not given
1004         p[-1] = 0;
1005         ASSIGN(appData.directory[i], engineName);
1006         p[-1] = SLASH;
1007         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1008     } else { ASSIGN(appData.directory[i], "."); }
1009     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1010     if(params[0]) {
1011         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1012         snprintf(command, MSG_SIZ, "%s %s", p, params);
1013         p = command;
1014     }
1015     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1016     ASSIGN(appData.chessProgram[i], p);
1017     appData.isUCI[i] = isUCI;
1018     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1019     appData.hasOwnBookUCI[i] = hasBook;
1020     if(!nickName[0]) useNick = FALSE;
1021     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1022     if(addToList) {
1023         int len;
1024         char quote;
1025         q = firstChessProgramNames;
1026         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1027         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1028         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1029                         quote, p, quote, appData.directory[i],
1030                         useNick ? " -fn \"" : "",
1031                         useNick ? nickName : "",
1032                         useNick ? "\"" : "",
1033                         v1 ? " -firstProtocolVersion 1" : "",
1034                         hasBook ? "" : " -fNoOwnBookUCI",
1035                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1036                         storeVariant ? " -variant " : "",
1037                         storeVariant ? VariantName(gameInfo.variant) : "");
1038         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1039         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1040         if(insert != q) insert[-1] = NULLCHAR;
1041         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1042         if(q)   free(q);
1043         FloatToFront(&appData.recentEngineList, buf);
1044     }
1045     ReplaceEngine(cps, i);
1046 }
1047
1048 void
1049 InitTimeControls ()
1050 {
1051     int matched, min, sec;
1052     /*
1053      * Parse timeControl resource
1054      */
1055     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1056                           appData.movesPerSession)) {
1057         char buf[MSG_SIZ];
1058         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1059         DisplayFatalError(buf, 0, 2);
1060     }
1061
1062     /*
1063      * Parse searchTime resource
1064      */
1065     if (*appData.searchTime != NULLCHAR) {
1066         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1067         if (matched == 1) {
1068             searchTime = min * 60;
1069         } else if (matched == 2) {
1070             searchTime = min * 60 + sec;
1071         } else {
1072             char buf[MSG_SIZ];
1073             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1074             DisplayFatalError(buf, 0, 2);
1075         }
1076     }
1077 }
1078
1079 void
1080 InitBackEnd1 ()
1081 {
1082
1083     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1084     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1085
1086     GetTimeMark(&programStartTime);
1087     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1088     appData.seedBase = random() + (random()<<15);
1089     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1090
1091     ClearProgramStats();
1092     programStats.ok_to_send = 1;
1093     programStats.seen_stat = 0;
1094
1095     /*
1096      * Initialize game list
1097      */
1098     ListNew(&gameList);
1099
1100
1101     /*
1102      * Internet chess server status
1103      */
1104     if (appData.icsActive) {
1105         appData.matchMode = FALSE;
1106         appData.matchGames = 0;
1107 #if ZIPPY
1108         appData.noChessProgram = !appData.zippyPlay;
1109 #else
1110         appData.zippyPlay = FALSE;
1111         appData.zippyTalk = FALSE;
1112         appData.noChessProgram = TRUE;
1113 #endif
1114         if (*appData.icsHelper != NULLCHAR) {
1115             appData.useTelnet = TRUE;
1116             appData.telnetProgram = appData.icsHelper;
1117         }
1118     } else {
1119         appData.zippyTalk = appData.zippyPlay = FALSE;
1120     }
1121
1122     /* [AS] Initialize pv info list [HGM] and game state */
1123     {
1124         int i, j;
1125
1126         for( i=0; i<=framePtr; i++ ) {
1127             pvInfoList[i].depth = -1;
1128             boards[i][EP_STATUS] = EP_NONE;
1129             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1130         }
1131     }
1132
1133     InitTimeControls();
1134
1135     /* [AS] Adjudication threshold */
1136     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1137
1138     InitEngine(&first, 0);
1139     InitEngine(&second, 1);
1140     CommonEngineInit();
1141
1142     pairing.which = "pairing"; // pairing engine
1143     pairing.pr = NoProc;
1144     pairing.isr = NULL;
1145     pairing.program = appData.pairingEngine;
1146     pairing.host = "localhost";
1147     pairing.dir = ".";
1148
1149     if (appData.icsActive) {
1150         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1151     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1152         appData.clockMode = FALSE;
1153         first.sendTime = second.sendTime = 0;
1154     }
1155
1156 #if ZIPPY
1157     /* Override some settings from environment variables, for backward
1158        compatibility.  Unfortunately it's not feasible to have the env
1159        vars just set defaults, at least in xboard.  Ugh.
1160     */
1161     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1162       ZippyInit();
1163     }
1164 #endif
1165
1166     if (!appData.icsActive) {
1167       char buf[MSG_SIZ];
1168       int len;
1169
1170       /* Check for variants that are supported only in ICS mode,
1171          or not at all.  Some that are accepted here nevertheless
1172          have bugs; see comments below.
1173       */
1174       VariantClass variant = StringToVariant(appData.variant);
1175       switch (variant) {
1176       case VariantBughouse:     /* need four players and two boards */
1177       case VariantKriegspiel:   /* need to hide pieces and move details */
1178         /* case VariantFischeRandom: (Fabien: moved below) */
1179         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1180         if( (len >= MSG_SIZ) && appData.debugMode )
1181           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1182
1183         DisplayFatalError(buf, 0, 2);
1184         return;
1185
1186       case VariantUnknown:
1187       case VariantLoadable:
1188       case Variant29:
1189       case Variant30:
1190       case Variant31:
1191       case Variant32:
1192       case Variant33:
1193       case Variant34:
1194       case Variant35:
1195       case Variant36:
1196       default:
1197         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1198         if( (len >= MSG_SIZ) && appData.debugMode )
1199           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1200
1201         DisplayFatalError(buf, 0, 2);
1202         return;
1203
1204       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1205       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1206       case VariantGothic:     /* [HGM] should work */
1207       case VariantCapablanca: /* [HGM] should work */
1208       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1209       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1210       case VariantChu:        /* [HGM] experimental */
1211       case VariantKnightmate: /* [HGM] should work */
1212       case VariantCylinder:   /* [HGM] untested */
1213       case VariantFalcon:     /* [HGM] untested */
1214       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1215                                  offboard interposition not understood */
1216       case VariantNormal:     /* definitely works! */
1217       case VariantWildCastle: /* pieces not automatically shuffled */
1218       case VariantNoCastle:   /* pieces not automatically shuffled */
1219       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1220       case VariantLosers:     /* should work except for win condition,
1221                                  and doesn't know captures are mandatory */
1222       case VariantSuicide:    /* should work except for win condition,
1223                                  and doesn't know captures are mandatory */
1224       case VariantGiveaway:   /* should work except for win condition,
1225                                  and doesn't know captures are mandatory */
1226       case VariantTwoKings:   /* should work */
1227       case VariantAtomic:     /* should work except for win condition */
1228       case Variant3Check:     /* should work except for win condition */
1229       case VariantShatranj:   /* should work except for all win conditions */
1230       case VariantMakruk:     /* should work except for draw countdown */
1231       case VariantASEAN :     /* should work except for draw countdown */
1232       case VariantBerolina:   /* might work if TestLegality is off */
1233       case VariantCapaRandom: /* should work */
1234       case VariantJanus:      /* should work */
1235       case VariantSuper:      /* experimental */
1236       case VariantGreat:      /* experimental, requires legality testing to be off */
1237       case VariantSChess:     /* S-Chess, should work */
1238       case VariantGrand:      /* should work */
1239       case VariantSpartan:    /* should work */
1240       case VariantLion:       /* should work */
1241       case VariantChuChess:   /* should work */
1242         break;
1243       }
1244     }
1245
1246 }
1247
1248 int
1249 NextIntegerFromString (char ** str, long * value)
1250 {
1251     int result = -1;
1252     char * s = *str;
1253
1254     while( *s == ' ' || *s == '\t' ) {
1255         s++;
1256     }
1257
1258     *value = 0;
1259
1260     if( *s >= '0' && *s <= '9' ) {
1261         while( *s >= '0' && *s <= '9' ) {
1262             *value = *value * 10 + (*s - '0');
1263             s++;
1264         }
1265
1266         result = 0;
1267     }
1268
1269     *str = s;
1270
1271     return result;
1272 }
1273
1274 int
1275 NextTimeControlFromString (char ** str, long * value)
1276 {
1277     long temp;
1278     int result = NextIntegerFromString( str, &temp );
1279
1280     if( result == 0 ) {
1281         *value = temp * 60; /* Minutes */
1282         if( **str == ':' ) {
1283             (*str)++;
1284             result = NextIntegerFromString( str, &temp );
1285             *value += temp; /* Seconds */
1286         }
1287     }
1288
1289     return result;
1290 }
1291
1292 int
1293 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1294 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1295     int result = -1, type = 0; long temp, temp2;
1296
1297     if(**str != ':') return -1; // old params remain in force!
1298     (*str)++;
1299     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1300     if( NextIntegerFromString( str, &temp ) ) return -1;
1301     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1302
1303     if(**str != '/') {
1304         /* time only: incremental or sudden-death time control */
1305         if(**str == '+') { /* increment follows; read it */
1306             (*str)++;
1307             if(**str == '!') type = *(*str)++; // Bronstein TC
1308             if(result = NextIntegerFromString( str, &temp2)) return -1;
1309             *inc = temp2 * 1000;
1310             if(**str == '.') { // read fraction of increment
1311                 char *start = ++(*str);
1312                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1313                 temp2 *= 1000;
1314                 while(start++ < *str) temp2 /= 10;
1315                 *inc += temp2;
1316             }
1317         } else *inc = 0;
1318         *moves = 0; *tc = temp * 1000; *incType = type;
1319         return 0;
1320     }
1321
1322     (*str)++; /* classical time control */
1323     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1324
1325     if(result == 0) {
1326         *moves = temp;
1327         *tc    = temp2 * 1000;
1328         *inc   = 0;
1329         *incType = type;
1330     }
1331     return result;
1332 }
1333
1334 int
1335 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1336 {   /* [HGM] get time to add from the multi-session time-control string */
1337     int incType, moves=1; /* kludge to force reading of first session */
1338     long time, increment;
1339     char *s = tcString;
1340
1341     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1342     do {
1343         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1344         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1345         if(movenr == -1) return time;    /* last move before new session     */
1346         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1347         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1348         if(!moves) return increment;     /* current session is incremental   */
1349         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1350     } while(movenr >= -1);               /* try again for next session       */
1351
1352     return 0; // no new time quota on this move
1353 }
1354
1355 int
1356 ParseTimeControl (char *tc, float ti, int mps)
1357 {
1358   long tc1;
1359   long tc2;
1360   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1361   int min, sec=0;
1362
1363   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1364   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1365       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1366   if(ti > 0) {
1367
1368     if(mps)
1369       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1370     else
1371       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1372   } else {
1373     if(mps)
1374       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1375     else
1376       snprintf(buf, MSG_SIZ, ":%s", mytc);
1377   }
1378   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1379
1380   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1381     return FALSE;
1382   }
1383
1384   if( *tc == '/' ) {
1385     /* Parse second time control */
1386     tc++;
1387
1388     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1389       return FALSE;
1390     }
1391
1392     if( tc2 == 0 ) {
1393       return FALSE;
1394     }
1395
1396     timeControl_2 = tc2 * 1000;
1397   }
1398   else {
1399     timeControl_2 = 0;
1400   }
1401
1402   if( tc1 == 0 ) {
1403     return FALSE;
1404   }
1405
1406   timeControl = tc1 * 1000;
1407
1408   if (ti >= 0) {
1409     timeIncrement = ti * 1000;  /* convert to ms */
1410     movesPerSession = 0;
1411   } else {
1412     timeIncrement = 0;
1413     movesPerSession = mps;
1414   }
1415   return TRUE;
1416 }
1417
1418 void
1419 InitBackEnd2 ()
1420 {
1421     if (appData.debugMode) {
1422 #    ifdef __GIT_VERSION
1423       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1424 #    else
1425       fprintf(debugFP, "Version: %s\n", programVersion);
1426 #    endif
1427     }
1428     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1429
1430     set_cont_sequence(appData.wrapContSeq);
1431     if (appData.matchGames > 0) {
1432         appData.matchMode = TRUE;
1433     } else if (appData.matchMode) {
1434         appData.matchGames = 1;
1435     }
1436     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1437         appData.matchGames = appData.sameColorGames;
1438     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1439         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1440         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1441     }
1442     Reset(TRUE, FALSE);
1443     if (appData.noChessProgram || first.protocolVersion == 1) {
1444       InitBackEnd3();
1445     } else {
1446       /* kludge: allow timeout for initial "feature" commands */
1447       FreezeUI();
1448       DisplayMessage("", _("Starting chess program"));
1449       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1450     }
1451 }
1452
1453 int
1454 CalculateIndex (int index, int gameNr)
1455 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1456     int res;
1457     if(index > 0) return index; // fixed nmber
1458     if(index == 0) return 1;
1459     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1460     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1461     return res;
1462 }
1463
1464 int
1465 LoadGameOrPosition (int gameNr)
1466 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1467     if (*appData.loadGameFile != NULLCHAR) {
1468         if (!LoadGameFromFile(appData.loadGameFile,
1469                 CalculateIndex(appData.loadGameIndex, gameNr),
1470                               appData.loadGameFile, FALSE)) {
1471             DisplayFatalError(_("Bad game file"), 0, 1);
1472             return 0;
1473         }
1474     } else if (*appData.loadPositionFile != NULLCHAR) {
1475         if (!LoadPositionFromFile(appData.loadPositionFile,
1476                 CalculateIndex(appData.loadPositionIndex, gameNr),
1477                                   appData.loadPositionFile)) {
1478             DisplayFatalError(_("Bad position file"), 0, 1);
1479             return 0;
1480         }
1481     }
1482     return 1;
1483 }
1484
1485 void
1486 ReserveGame (int gameNr, char resChar)
1487 {
1488     FILE *tf = fopen(appData.tourneyFile, "r+");
1489     char *p, *q, c, buf[MSG_SIZ];
1490     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1491     safeStrCpy(buf, lastMsg, MSG_SIZ);
1492     DisplayMessage(_("Pick new game"), "");
1493     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1494     ParseArgsFromFile(tf);
1495     p = q = appData.results;
1496     if(appData.debugMode) {
1497       char *r = appData.participants;
1498       fprintf(debugFP, "results = '%s'\n", p);
1499       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1500       fprintf(debugFP, "\n");
1501     }
1502     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1503     nextGame = q - p;
1504     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1505     safeStrCpy(q, p, strlen(p) + 2);
1506     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1507     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1508     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1509         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1510         q[nextGame] = '*';
1511     }
1512     fseek(tf, -(strlen(p)+4), SEEK_END);
1513     c = fgetc(tf);
1514     if(c != '"') // depending on DOS or Unix line endings we can be one off
1515          fseek(tf, -(strlen(p)+2), SEEK_END);
1516     else fseek(tf, -(strlen(p)+3), SEEK_END);
1517     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1518     DisplayMessage(buf, "");
1519     free(p); appData.results = q;
1520     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1521        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1522       int round = appData.defaultMatchGames * appData.tourneyType;
1523       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1524          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1525         UnloadEngine(&first);  // next game belongs to other pairing;
1526         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1527     }
1528     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1529 }
1530
1531 void
1532 MatchEvent (int mode)
1533 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1534         int dummy;
1535         if(matchMode) { // already in match mode: switch it off
1536             abortMatch = TRUE;
1537             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1538             return;
1539         }
1540 //      if(gameMode != BeginningOfGame) {
1541 //          DisplayError(_("You can only start a match from the initial position."), 0);
1542 //          return;
1543 //      }
1544         abortMatch = FALSE;
1545         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1546         /* Set up machine vs. machine match */
1547         nextGame = 0;
1548         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1549         if(appData.tourneyFile[0]) {
1550             ReserveGame(-1, 0);
1551             if(nextGame > appData.matchGames) {
1552                 char buf[MSG_SIZ];
1553                 if(strchr(appData.results, '*') == NULL) {
1554                     FILE *f;
1555                     appData.tourneyCycles++;
1556                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1557                         fclose(f);
1558                         NextTourneyGame(-1, &dummy);
1559                         ReserveGame(-1, 0);
1560                         if(nextGame <= appData.matchGames) {
1561                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1562                             matchMode = mode;
1563                             ScheduleDelayedEvent(NextMatchGame, 10000);
1564                             return;
1565                         }
1566                     }
1567                 }
1568                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1569                 DisplayError(buf, 0);
1570                 appData.tourneyFile[0] = 0;
1571                 return;
1572             }
1573         } else
1574         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1575             DisplayFatalError(_("Can't have a match with no chess programs"),
1576                               0, 2);
1577             return;
1578         }
1579         matchMode = mode;
1580         matchGame = roundNr = 1;
1581         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1582         NextMatchGame();
1583 }
1584
1585 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1586
1587 void
1588 InitBackEnd3 P((void))
1589 {
1590     GameMode initialMode;
1591     char buf[MSG_SIZ];
1592     int err, len;
1593
1594     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1595        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1596         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1597        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1598        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1599         char c, *q = first.variants, *p = strchr(q, ',');
1600         if(p) *p = NULLCHAR;
1601         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1602             int w, h, s;
1603             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1604                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1605             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1606             Reset(TRUE, FALSE);         // and re-initialize
1607         }
1608         if(p) *p = ',';
1609     }
1610
1611     InitChessProgram(&first, startedFromSetupPosition);
1612
1613     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1614         free(programVersion);
1615         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1616         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1617         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1618     }
1619
1620     if (appData.icsActive) {
1621 #ifdef WIN32
1622         /* [DM] Make a console window if needed [HGM] merged ifs */
1623         ConsoleCreate();
1624 #endif
1625         err = establish();
1626         if (err != 0)
1627           {
1628             if (*appData.icsCommPort != NULLCHAR)
1629               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1630                              appData.icsCommPort);
1631             else
1632               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1633                         appData.icsHost, appData.icsPort);
1634
1635             if( (len >= MSG_SIZ) && appData.debugMode )
1636               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1637
1638             DisplayFatalError(buf, err, 1);
1639             return;
1640         }
1641         SetICSMode();
1642         telnetISR =
1643           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1644         fromUserISR =
1645           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1646         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1647             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1648     } else if (appData.noChessProgram) {
1649         SetNCPMode();
1650     } else {
1651         SetGNUMode();
1652     }
1653
1654     if (*appData.cmailGameName != NULLCHAR) {
1655         SetCmailMode();
1656         OpenLoopback(&cmailPR);
1657         cmailISR =
1658           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1659     }
1660
1661     ThawUI();
1662     DisplayMessage("", "");
1663     if (StrCaseCmp(appData.initialMode, "") == 0) {
1664       initialMode = BeginningOfGame;
1665       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1666         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1667         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1668         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1669         ModeHighlight();
1670       }
1671     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1672       initialMode = TwoMachinesPlay;
1673     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1674       initialMode = AnalyzeFile;
1675     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1676       initialMode = AnalyzeMode;
1677     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1678       initialMode = MachinePlaysWhite;
1679     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1680       initialMode = MachinePlaysBlack;
1681     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1682       initialMode = EditGame;
1683     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1684       initialMode = EditPosition;
1685     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1686       initialMode = Training;
1687     } else {
1688       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1689       if( (len >= MSG_SIZ) && appData.debugMode )
1690         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1691
1692       DisplayFatalError(buf, 0, 2);
1693       return;
1694     }
1695
1696     if (appData.matchMode) {
1697         if(appData.tourneyFile[0]) { // start tourney from command line
1698             FILE *f;
1699             if(f = fopen(appData.tourneyFile, "r")) {
1700                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1701                 fclose(f);
1702                 appData.clockMode = TRUE;
1703                 SetGNUMode();
1704             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1705         }
1706         MatchEvent(TRUE);
1707     } else if (*appData.cmailGameName != NULLCHAR) {
1708         /* Set up cmail mode */
1709         ReloadCmailMsgEvent(TRUE);
1710     } else {
1711         /* Set up other modes */
1712         if (initialMode == AnalyzeFile) {
1713           if (*appData.loadGameFile == NULLCHAR) {
1714             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1715             return;
1716           }
1717         }
1718         if (*appData.loadGameFile != NULLCHAR) {
1719             (void) LoadGameFromFile(appData.loadGameFile,
1720                                     appData.loadGameIndex,
1721                                     appData.loadGameFile, TRUE);
1722         } else if (*appData.loadPositionFile != NULLCHAR) {
1723             (void) LoadPositionFromFile(appData.loadPositionFile,
1724                                         appData.loadPositionIndex,
1725                                         appData.loadPositionFile);
1726             /* [HGM] try to make self-starting even after FEN load */
1727             /* to allow automatic setup of fairy variants with wtm */
1728             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1729                 gameMode = BeginningOfGame;
1730                 setboardSpoiledMachineBlack = 1;
1731             }
1732             /* [HGM] loadPos: make that every new game uses the setup */
1733             /* from file as long as we do not switch variant          */
1734             if(!blackPlaysFirst) {
1735                 startedFromPositionFile = TRUE;
1736                 CopyBoard(filePosition, boards[0]);
1737             }
1738         }
1739         if (initialMode == AnalyzeMode) {
1740           if (appData.noChessProgram) {
1741             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1742             return;
1743           }
1744           if (appData.icsActive) {
1745             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1746             return;
1747           }
1748           AnalyzeModeEvent();
1749         } else if (initialMode == AnalyzeFile) {
1750           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1751           ShowThinkingEvent();
1752           AnalyzeFileEvent();
1753           AnalysisPeriodicEvent(1);
1754         } else if (initialMode == MachinePlaysWhite) {
1755           if (appData.noChessProgram) {
1756             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1757                               0, 2);
1758             return;
1759           }
1760           if (appData.icsActive) {
1761             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1762                               0, 2);
1763             return;
1764           }
1765           MachineWhiteEvent();
1766         } else if (initialMode == MachinePlaysBlack) {
1767           if (appData.noChessProgram) {
1768             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1769                               0, 2);
1770             return;
1771           }
1772           if (appData.icsActive) {
1773             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1774                               0, 2);
1775             return;
1776           }
1777           MachineBlackEvent();
1778         } else if (initialMode == TwoMachinesPlay) {
1779           if (appData.noChessProgram) {
1780             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1781                               0, 2);
1782             return;
1783           }
1784           if (appData.icsActive) {
1785             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1786                               0, 2);
1787             return;
1788           }
1789           TwoMachinesEvent();
1790         } else if (initialMode == EditGame) {
1791           EditGameEvent();
1792         } else if (initialMode == EditPosition) {
1793           EditPositionEvent();
1794         } else if (initialMode == Training) {
1795           if (*appData.loadGameFile == NULLCHAR) {
1796             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1797             return;
1798           }
1799           TrainingEvent();
1800         }
1801     }
1802 }
1803
1804 void
1805 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1806 {
1807     DisplayBook(current+1);
1808
1809     MoveHistorySet( movelist, first, last, current, pvInfoList );
1810
1811     EvalGraphSet( first, last, current, pvInfoList );
1812
1813     MakeEngineOutputTitle();
1814 }
1815
1816 /*
1817  * Establish will establish a contact to a remote host.port.
1818  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1819  *  used to talk to the host.
1820  * Returns 0 if okay, error code if not.
1821  */
1822 int
1823 establish ()
1824 {
1825     char buf[MSG_SIZ];
1826
1827     if (*appData.icsCommPort != NULLCHAR) {
1828         /* Talk to the host through a serial comm port */
1829         return OpenCommPort(appData.icsCommPort, &icsPR);
1830
1831     } else if (*appData.gateway != NULLCHAR) {
1832         if (*appData.remoteShell == NULLCHAR) {
1833             /* Use the rcmd protocol to run telnet program on a gateway host */
1834             snprintf(buf, sizeof(buf), "%s %s %s",
1835                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1836             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1837
1838         } else {
1839             /* Use the rsh program to run telnet program on a gateway host */
1840             if (*appData.remoteUser == NULLCHAR) {
1841                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1842                         appData.gateway, appData.telnetProgram,
1843                         appData.icsHost, appData.icsPort);
1844             } else {
1845                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1846                         appData.remoteShell, appData.gateway,
1847                         appData.remoteUser, appData.telnetProgram,
1848                         appData.icsHost, appData.icsPort);
1849             }
1850             return StartChildProcess(buf, "", &icsPR);
1851
1852         }
1853     } else if (appData.useTelnet) {
1854         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1855
1856     } else {
1857         /* TCP socket interface differs somewhat between
1858            Unix and NT; handle details in the front end.
1859            */
1860         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1861     }
1862 }
1863
1864 void
1865 EscapeExpand (char *p, char *q)
1866 {       // [HGM] initstring: routine to shape up string arguments
1867         while(*p++ = *q++) if(p[-1] == '\\')
1868             switch(*q++) {
1869                 case 'n': p[-1] = '\n'; break;
1870                 case 'r': p[-1] = '\r'; break;
1871                 case 't': p[-1] = '\t'; break;
1872                 case '\\': p[-1] = '\\'; break;
1873                 case 0: *p = 0; return;
1874                 default: p[-1] = q[-1]; break;
1875             }
1876 }
1877
1878 void
1879 show_bytes (FILE *fp, char *buf, int count)
1880 {
1881     while (count--) {
1882         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1883             fprintf(fp, "\\%03o", *buf & 0xff);
1884         } else {
1885             putc(*buf, fp);
1886         }
1887         buf++;
1888     }
1889     fflush(fp);
1890 }
1891
1892 /* Returns an errno value */
1893 int
1894 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1895 {
1896     char buf[8192], *p, *q, *buflim;
1897     int left, newcount, outcount;
1898
1899     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1900         *appData.gateway != NULLCHAR) {
1901         if (appData.debugMode) {
1902             fprintf(debugFP, ">ICS: ");
1903             show_bytes(debugFP, message, count);
1904             fprintf(debugFP, "\n");
1905         }
1906         return OutputToProcess(pr, message, count, outError);
1907     }
1908
1909     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1910     p = message;
1911     q = buf;
1912     left = count;
1913     newcount = 0;
1914     while (left) {
1915         if (q >= buflim) {
1916             if (appData.debugMode) {
1917                 fprintf(debugFP, ">ICS: ");
1918                 show_bytes(debugFP, buf, newcount);
1919                 fprintf(debugFP, "\n");
1920             }
1921             outcount = OutputToProcess(pr, buf, newcount, outError);
1922             if (outcount < newcount) return -1; /* to be sure */
1923             q = buf;
1924             newcount = 0;
1925         }
1926         if (*p == '\n') {
1927             *q++ = '\r';
1928             newcount++;
1929         } else if (((unsigned char) *p) == TN_IAC) {
1930             *q++ = (char) TN_IAC;
1931             newcount ++;
1932         }
1933         *q++ = *p++;
1934         newcount++;
1935         left--;
1936     }
1937     if (appData.debugMode) {
1938         fprintf(debugFP, ">ICS: ");
1939         show_bytes(debugFP, buf, newcount);
1940         fprintf(debugFP, "\n");
1941     }
1942     outcount = OutputToProcess(pr, buf, newcount, outError);
1943     if (outcount < newcount) return -1; /* to be sure */
1944     return count;
1945 }
1946
1947 void
1948 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1949 {
1950     int outError, outCount;
1951     static int gotEof = 0;
1952     static FILE *ini;
1953
1954     /* Pass data read from player on to ICS */
1955     if (count > 0) {
1956         gotEof = 0;
1957         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1958         if (outCount < count) {
1959             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1960         }
1961         if(have_sent_ICS_logon == 2) {
1962           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1963             fprintf(ini, "%s", message);
1964             have_sent_ICS_logon = 3;
1965           } else
1966             have_sent_ICS_logon = 1;
1967         } else if(have_sent_ICS_logon == 3) {
1968             fprintf(ini, "%s", message);
1969             fclose(ini);
1970           have_sent_ICS_logon = 1;
1971         }
1972     } else if (count < 0) {
1973         RemoveInputSource(isr);
1974         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1975     } else if (gotEof++ > 0) {
1976         RemoveInputSource(isr);
1977         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1978     }
1979 }
1980
1981 void
1982 KeepAlive ()
1983 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1984     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1985     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1986     SendToICS("date\n");
1987     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1988 }
1989
1990 /* added routine for printf style output to ics */
1991 void
1992 ics_printf (char *format, ...)
1993 {
1994     char buffer[MSG_SIZ];
1995     va_list args;
1996
1997     va_start(args, format);
1998     vsnprintf(buffer, sizeof(buffer), format, args);
1999     buffer[sizeof(buffer)-1] = '\0';
2000     SendToICS(buffer);
2001     va_end(args);
2002 }
2003
2004 void
2005 SendToICS (char *s)
2006 {
2007     int count, outCount, outError;
2008
2009     if (icsPR == NoProc) return;
2010
2011     count = strlen(s);
2012     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2013     if (outCount < count) {
2014         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2015     }
2016 }
2017
2018 /* This is used for sending logon scripts to the ICS. Sending
2019    without a delay causes problems when using timestamp on ICC
2020    (at least on my machine). */
2021 void
2022 SendToICSDelayed (char *s, long msdelay)
2023 {
2024     int count, outCount, outError;
2025
2026     if (icsPR == NoProc) return;
2027
2028     count = strlen(s);
2029     if (appData.debugMode) {
2030         fprintf(debugFP, ">ICS: ");
2031         show_bytes(debugFP, s, count);
2032         fprintf(debugFP, "\n");
2033     }
2034     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2035                                       msdelay);
2036     if (outCount < count) {
2037         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2038     }
2039 }
2040
2041
2042 /* Remove all highlighting escape sequences in s
2043    Also deletes any suffix starting with '('
2044    */
2045 char *
2046 StripHighlightAndTitle (char *s)
2047 {
2048     static char retbuf[MSG_SIZ];
2049     char *p = retbuf;
2050
2051     while (*s != NULLCHAR) {
2052         while (*s == '\033') {
2053             while (*s != NULLCHAR && !isalpha(*s)) s++;
2054             if (*s != NULLCHAR) s++;
2055         }
2056         while (*s != NULLCHAR && *s != '\033') {
2057             if (*s == '(' || *s == '[') {
2058                 *p = NULLCHAR;
2059                 return retbuf;
2060             }
2061             *p++ = *s++;
2062         }
2063     }
2064     *p = NULLCHAR;
2065     return retbuf;
2066 }
2067
2068 /* Remove all highlighting escape sequences in s */
2069 char *
2070 StripHighlight (char *s)
2071 {
2072     static char retbuf[MSG_SIZ];
2073     char *p = retbuf;
2074
2075     while (*s != NULLCHAR) {
2076         while (*s == '\033') {
2077             while (*s != NULLCHAR && !isalpha(*s)) s++;
2078             if (*s != NULLCHAR) s++;
2079         }
2080         while (*s != NULLCHAR && *s != '\033') {
2081             *p++ = *s++;
2082         }
2083     }
2084     *p = NULLCHAR;
2085     return retbuf;
2086 }
2087
2088 char engineVariant[MSG_SIZ];
2089 char *variantNames[] = VARIANT_NAMES;
2090 char *
2091 VariantName (VariantClass v)
2092 {
2093     if(v == VariantUnknown || *engineVariant) return engineVariant;
2094     return variantNames[v];
2095 }
2096
2097
2098 /* Identify a variant from the strings the chess servers use or the
2099    PGN Variant tag names we use. */
2100 VariantClass
2101 StringToVariant (char *e)
2102 {
2103     char *p;
2104     int wnum = -1;
2105     VariantClass v = VariantNormal;
2106     int i, found = FALSE;
2107     char buf[MSG_SIZ];
2108     int len;
2109
2110     if (!e) return v;
2111
2112     /* [HGM] skip over optional board-size prefixes */
2113     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2114         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2115         while( *e++ != '_');
2116     }
2117
2118     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2119         v = VariantNormal;
2120         found = TRUE;
2121     } else
2122     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2123       if (p = StrCaseStr(e, variantNames[i])) {
2124         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2125         v = (VariantClass) i;
2126         found = TRUE;
2127         break;
2128       }
2129     }
2130
2131     if (!found) {
2132       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2133           || StrCaseStr(e, "wild/fr")
2134           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2135         v = VariantFischeRandom;
2136       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2137                  (i = 1, p = StrCaseStr(e, "w"))) {
2138         p += i;
2139         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2140         if (isdigit(*p)) {
2141           wnum = atoi(p);
2142         } else {
2143           wnum = -1;
2144         }
2145         switch (wnum) {
2146         case 0: /* FICS only, actually */
2147         case 1:
2148           /* Castling legal even if K starts on d-file */
2149           v = VariantWildCastle;
2150           break;
2151         case 2:
2152         case 3:
2153         case 4:
2154           /* Castling illegal even if K & R happen to start in
2155              normal positions. */
2156           v = VariantNoCastle;
2157           break;
2158         case 5:
2159         case 7:
2160         case 8:
2161         case 10:
2162         case 11:
2163         case 12:
2164         case 13:
2165         case 14:
2166         case 15:
2167         case 18:
2168         case 19:
2169           /* Castling legal iff K & R start in normal positions */
2170           v = VariantNormal;
2171           break;
2172         case 6:
2173         case 20:
2174         case 21:
2175           /* Special wilds for position setup; unclear what to do here */
2176           v = VariantLoadable;
2177           break;
2178         case 9:
2179           /* Bizarre ICC game */
2180           v = VariantTwoKings;
2181           break;
2182         case 16:
2183           v = VariantKriegspiel;
2184           break;
2185         case 17:
2186           v = VariantLosers;
2187           break;
2188         case 22:
2189           v = VariantFischeRandom;
2190           break;
2191         case 23:
2192           v = VariantCrazyhouse;
2193           break;
2194         case 24:
2195           v = VariantBughouse;
2196           break;
2197         case 25:
2198           v = Variant3Check;
2199           break;
2200         case 26:
2201           /* Not quite the same as FICS suicide! */
2202           v = VariantGiveaway;
2203           break;
2204         case 27:
2205           v = VariantAtomic;
2206           break;
2207         case 28:
2208           v = VariantShatranj;
2209           break;
2210
2211         /* Temporary names for future ICC types.  The name *will* change in
2212            the next xboard/WinBoard release after ICC defines it. */
2213         case 29:
2214           v = Variant29;
2215           break;
2216         case 30:
2217           v = Variant30;
2218           break;
2219         case 31:
2220           v = Variant31;
2221           break;
2222         case 32:
2223           v = Variant32;
2224           break;
2225         case 33:
2226           v = Variant33;
2227           break;
2228         case 34:
2229           v = Variant34;
2230           break;
2231         case 35:
2232           v = Variant35;
2233           break;
2234         case 36:
2235           v = Variant36;
2236           break;
2237         case 37:
2238           v = VariantShogi;
2239           break;
2240         case 38:
2241           v = VariantXiangqi;
2242           break;
2243         case 39:
2244           v = VariantCourier;
2245           break;
2246         case 40:
2247           v = VariantGothic;
2248           break;
2249         case 41:
2250           v = VariantCapablanca;
2251           break;
2252         case 42:
2253           v = VariantKnightmate;
2254           break;
2255         case 43:
2256           v = VariantFairy;
2257           break;
2258         case 44:
2259           v = VariantCylinder;
2260           break;
2261         case 45:
2262           v = VariantFalcon;
2263           break;
2264         case 46:
2265           v = VariantCapaRandom;
2266           break;
2267         case 47:
2268           v = VariantBerolina;
2269           break;
2270         case 48:
2271           v = VariantJanus;
2272           break;
2273         case 49:
2274           v = VariantSuper;
2275           break;
2276         case 50:
2277           v = VariantGreat;
2278           break;
2279         case -1:
2280           /* Found "wild" or "w" in the string but no number;
2281              must assume it's normal chess. */
2282           v = VariantNormal;
2283           break;
2284         default:
2285           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2286           if( (len >= MSG_SIZ) && appData.debugMode )
2287             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2288
2289           DisplayError(buf, 0);
2290           v = VariantUnknown;
2291           break;
2292         }
2293       }
2294     }
2295     if (appData.debugMode) {
2296       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2297               e, wnum, VariantName(v));
2298     }
2299     return v;
2300 }
2301
2302 static int leftover_start = 0, leftover_len = 0;
2303 char star_match[STAR_MATCH_N][MSG_SIZ];
2304
2305 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2306    advance *index beyond it, and set leftover_start to the new value of
2307    *index; else return FALSE.  If pattern contains the character '*', it
2308    matches any sequence of characters not containing '\r', '\n', or the
2309    character following the '*' (if any), and the matched sequence(s) are
2310    copied into star_match.
2311    */
2312 int
2313 looking_at ( char *buf, int *index, char *pattern)
2314 {
2315     char *bufp = &buf[*index], *patternp = pattern;
2316     int star_count = 0;
2317     char *matchp = star_match[0];
2318
2319     for (;;) {
2320         if (*patternp == NULLCHAR) {
2321             *index = leftover_start = bufp - buf;
2322             *matchp = NULLCHAR;
2323             return TRUE;
2324         }
2325         if (*bufp == NULLCHAR) return FALSE;
2326         if (*patternp == '*') {
2327             if (*bufp == *(patternp + 1)) {
2328                 *matchp = NULLCHAR;
2329                 matchp = star_match[++star_count];
2330                 patternp += 2;
2331                 bufp++;
2332                 continue;
2333             } else if (*bufp == '\n' || *bufp == '\r') {
2334                 patternp++;
2335                 if (*patternp == NULLCHAR)
2336                   continue;
2337                 else
2338                   return FALSE;
2339             } else {
2340                 *matchp++ = *bufp++;
2341                 continue;
2342             }
2343         }
2344         if (*patternp != *bufp) return FALSE;
2345         patternp++;
2346         bufp++;
2347     }
2348 }
2349
2350 void
2351 SendToPlayer (char *data, int length)
2352 {
2353     int error, outCount;
2354     outCount = OutputToProcess(NoProc, data, length, &error);
2355     if (outCount < length) {
2356         DisplayFatalError(_("Error writing to display"), error, 1);
2357     }
2358 }
2359
2360 void
2361 PackHolding (char packed[], char *holding)
2362 {
2363     char *p = holding;
2364     char *q = packed;
2365     int runlength = 0;
2366     int curr = 9999;
2367     do {
2368         if (*p == curr) {
2369             runlength++;
2370         } else {
2371             switch (runlength) {
2372               case 0:
2373                 break;
2374               case 1:
2375                 *q++ = curr;
2376                 break;
2377               case 2:
2378                 *q++ = curr;
2379                 *q++ = curr;
2380                 break;
2381               default:
2382                 sprintf(q, "%d", runlength);
2383                 while (*q) q++;
2384                 *q++ = curr;
2385                 break;
2386             }
2387             runlength = 1;
2388             curr = *p;
2389         }
2390     } while (*p++);
2391     *q = NULLCHAR;
2392 }
2393
2394 /* Telnet protocol requests from the front end */
2395 void
2396 TelnetRequest (unsigned char ddww, unsigned char option)
2397 {
2398     unsigned char msg[3];
2399     int outCount, outError;
2400
2401     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2402
2403     if (appData.debugMode) {
2404         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2405         switch (ddww) {
2406           case TN_DO:
2407             ddwwStr = "DO";
2408             break;
2409           case TN_DONT:
2410             ddwwStr = "DONT";
2411             break;
2412           case TN_WILL:
2413             ddwwStr = "WILL";
2414             break;
2415           case TN_WONT:
2416             ddwwStr = "WONT";
2417             break;
2418           default:
2419             ddwwStr = buf1;
2420             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2421             break;
2422         }
2423         switch (option) {
2424           case TN_ECHO:
2425             optionStr = "ECHO";
2426             break;
2427           default:
2428             optionStr = buf2;
2429             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2430             break;
2431         }
2432         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2433     }
2434     msg[0] = TN_IAC;
2435     msg[1] = ddww;
2436     msg[2] = option;
2437     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2438     if (outCount < 3) {
2439         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2440     }
2441 }
2442
2443 void
2444 DoEcho ()
2445 {
2446     if (!appData.icsActive) return;
2447     TelnetRequest(TN_DO, TN_ECHO);
2448 }
2449
2450 void
2451 DontEcho ()
2452 {
2453     if (!appData.icsActive) return;
2454     TelnetRequest(TN_DONT, TN_ECHO);
2455 }
2456
2457 void
2458 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2459 {
2460     /* put the holdings sent to us by the server on the board holdings area */
2461     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2462     char p;
2463     ChessSquare piece;
2464
2465     if(gameInfo.holdingsWidth < 2)  return;
2466     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2467         return; // prevent overwriting by pre-board holdings
2468
2469     if( (int)lowestPiece >= BlackPawn ) {
2470         holdingsColumn = 0;
2471         countsColumn = 1;
2472         holdingsStartRow = BOARD_HEIGHT-1;
2473         direction = -1;
2474     } else {
2475         holdingsColumn = BOARD_WIDTH-1;
2476         countsColumn = BOARD_WIDTH-2;
2477         holdingsStartRow = 0;
2478         direction = 1;
2479     }
2480
2481     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2482         board[i][holdingsColumn] = EmptySquare;
2483         board[i][countsColumn]   = (ChessSquare) 0;
2484     }
2485     while( (p=*holdings++) != NULLCHAR ) {
2486         piece = CharToPiece( ToUpper(p) );
2487         if(piece == EmptySquare) continue;
2488         /*j = (int) piece - (int) WhitePawn;*/
2489         j = PieceToNumber(piece);
2490         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2491         if(j < 0) continue;               /* should not happen */
2492         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2493         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2494         board[holdingsStartRow+j*direction][countsColumn]++;
2495     }
2496 }
2497
2498
2499 void
2500 VariantSwitch (Board board, VariantClass newVariant)
2501 {
2502    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2503    static Board oldBoard;
2504
2505    startedFromPositionFile = FALSE;
2506    if(gameInfo.variant == newVariant) return;
2507
2508    /* [HGM] This routine is called each time an assignment is made to
2509     * gameInfo.variant during a game, to make sure the board sizes
2510     * are set to match the new variant. If that means adding or deleting
2511     * holdings, we shift the playing board accordingly
2512     * This kludge is needed because in ICS observe mode, we get boards
2513     * of an ongoing game without knowing the variant, and learn about the
2514     * latter only later. This can be because of the move list we requested,
2515     * in which case the game history is refilled from the beginning anyway,
2516     * but also when receiving holdings of a crazyhouse game. In the latter
2517     * case we want to add those holdings to the already received position.
2518     */
2519
2520
2521    if (appData.debugMode) {
2522      fprintf(debugFP, "Switch board from %s to %s\n",
2523              VariantName(gameInfo.variant), VariantName(newVariant));
2524      setbuf(debugFP, NULL);
2525    }
2526    shuffleOpenings = 0;       /* [HGM] shuffle */
2527    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2528    switch(newVariant)
2529      {
2530      case VariantShogi:
2531        newWidth = 9;  newHeight = 9;
2532        gameInfo.holdingsSize = 7;
2533      case VariantBughouse:
2534      case VariantCrazyhouse:
2535        newHoldingsWidth = 2; break;
2536      case VariantGreat:
2537        newWidth = 10;
2538      case VariantSuper:
2539        newHoldingsWidth = 2;
2540        gameInfo.holdingsSize = 8;
2541        break;
2542      case VariantGothic:
2543      case VariantCapablanca:
2544      case VariantCapaRandom:
2545        newWidth = 10;
2546      default:
2547        newHoldingsWidth = gameInfo.holdingsSize = 0;
2548      };
2549
2550    if(newWidth  != gameInfo.boardWidth  ||
2551       newHeight != gameInfo.boardHeight ||
2552       newHoldingsWidth != gameInfo.holdingsWidth ) {
2553
2554      /* shift position to new playing area, if needed */
2555      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2556        for(i=0; i<BOARD_HEIGHT; i++)
2557          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2558            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2559              board[i][j];
2560        for(i=0; i<newHeight; i++) {
2561          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2562          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2563        }
2564      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2565        for(i=0; i<BOARD_HEIGHT; i++)
2566          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2567            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2568              board[i][j];
2569      }
2570      board[HOLDINGS_SET] = 0;
2571      gameInfo.boardWidth  = newWidth;
2572      gameInfo.boardHeight = newHeight;
2573      gameInfo.holdingsWidth = newHoldingsWidth;
2574      gameInfo.variant = newVariant;
2575      InitDrawingSizes(-2, 0);
2576    } else gameInfo.variant = newVariant;
2577    CopyBoard(oldBoard, board);   // remember correctly formatted board
2578      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2579    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2580 }
2581
2582 static int loggedOn = FALSE;
2583
2584 /*-- Game start info cache: --*/
2585 int gs_gamenum;
2586 char gs_kind[MSG_SIZ];
2587 static char player1Name[128] = "";
2588 static char player2Name[128] = "";
2589 static char cont_seq[] = "\n\\   ";
2590 static int player1Rating = -1;
2591 static int player2Rating = -1;
2592 /*----------------------------*/
2593
2594 ColorClass curColor = ColorNormal;
2595 int suppressKibitz = 0;
2596
2597 // [HGM] seekgraph
2598 Boolean soughtPending = FALSE;
2599 Boolean seekGraphUp;
2600 #define MAX_SEEK_ADS 200
2601 #define SQUARE 0x80
2602 char *seekAdList[MAX_SEEK_ADS];
2603 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2604 float tcList[MAX_SEEK_ADS];
2605 char colorList[MAX_SEEK_ADS];
2606 int nrOfSeekAds = 0;
2607 int minRating = 1010, maxRating = 2800;
2608 int hMargin = 10, vMargin = 20, h, w;
2609 extern int squareSize, lineGap;
2610
2611 void
2612 PlotSeekAd (int i)
2613 {
2614         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2615         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2616         if(r < minRating+100 && r >=0 ) r = minRating+100;
2617         if(r > maxRating) r = maxRating;
2618         if(tc < 1.f) tc = 1.f;
2619         if(tc > 95.f) tc = 95.f;
2620         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2621         y = ((double)r - minRating)/(maxRating - minRating)
2622             * (h-vMargin-squareSize/8-1) + vMargin;
2623         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2624         if(strstr(seekAdList[i], " u ")) color = 1;
2625         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2626            !strstr(seekAdList[i], "bullet") &&
2627            !strstr(seekAdList[i], "blitz") &&
2628            !strstr(seekAdList[i], "standard") ) color = 2;
2629         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2630         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2631 }
2632
2633 void
2634 PlotSingleSeekAd (int i)
2635 {
2636         PlotSeekAd(i);
2637 }
2638
2639 void
2640 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2641 {
2642         char buf[MSG_SIZ], *ext = "";
2643         VariantClass v = StringToVariant(type);
2644         if(strstr(type, "wild")) {
2645             ext = type + 4; // append wild number
2646             if(v == VariantFischeRandom) type = "chess960"; else
2647             if(v == VariantLoadable) type = "setup"; else
2648             type = VariantName(v);
2649         }
2650         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2651         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2652             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2653             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2654             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2655             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2656             seekNrList[nrOfSeekAds] = nr;
2657             zList[nrOfSeekAds] = 0;
2658             seekAdList[nrOfSeekAds++] = StrSave(buf);
2659             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2660         }
2661 }
2662
2663 void
2664 EraseSeekDot (int i)
2665 {
2666     int x = xList[i], y = yList[i], d=squareSize/4, k;
2667     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2668     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2669     // now replot every dot that overlapped
2670     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2671         int xx = xList[k], yy = yList[k];
2672         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2673             DrawSeekDot(xx, yy, colorList[k]);
2674     }
2675 }
2676
2677 void
2678 RemoveSeekAd (int nr)
2679 {
2680         int i;
2681         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2682             EraseSeekDot(i);
2683             if(seekAdList[i]) free(seekAdList[i]);
2684             seekAdList[i] = seekAdList[--nrOfSeekAds];
2685             seekNrList[i] = seekNrList[nrOfSeekAds];
2686             ratingList[i] = ratingList[nrOfSeekAds];
2687             colorList[i]  = colorList[nrOfSeekAds];
2688             tcList[i] = tcList[nrOfSeekAds];
2689             xList[i]  = xList[nrOfSeekAds];
2690             yList[i]  = yList[nrOfSeekAds];
2691             zList[i]  = zList[nrOfSeekAds];
2692             seekAdList[nrOfSeekAds] = NULL;
2693             break;
2694         }
2695 }
2696
2697 Boolean
2698 MatchSoughtLine (char *line)
2699 {
2700     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2701     int nr, base, inc, u=0; char dummy;
2702
2703     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2704        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2705        (u=1) &&
2706        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2707         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2708         // match: compact and save the line
2709         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2710         return TRUE;
2711     }
2712     return FALSE;
2713 }
2714
2715 int
2716 DrawSeekGraph ()
2717 {
2718     int i;
2719     if(!seekGraphUp) return FALSE;
2720     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2721     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2722
2723     DrawSeekBackground(0, 0, w, h);
2724     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2725     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2726     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2727         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2728         yy = h-1-yy;
2729         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2730         if(i%500 == 0) {
2731             char buf[MSG_SIZ];
2732             snprintf(buf, MSG_SIZ, "%d", i);
2733             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2734         }
2735     }
2736     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2737     for(i=1; i<100; i+=(i<10?1:5)) {
2738         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2739         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2740         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2741             char buf[MSG_SIZ];
2742             snprintf(buf, MSG_SIZ, "%d", i);
2743             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2744         }
2745     }
2746     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2747     return TRUE;
2748 }
2749
2750 int
2751 SeekGraphClick (ClickType click, int x, int y, int moving)
2752 {
2753     static int lastDown = 0, displayed = 0, lastSecond;
2754     if(y < 0) return FALSE;
2755     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2756         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2757         if(!seekGraphUp) return FALSE;
2758         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2759         DrawPosition(TRUE, NULL);
2760         return TRUE;
2761     }
2762     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2763         if(click == Release || moving) return FALSE;
2764         nrOfSeekAds = 0;
2765         soughtPending = TRUE;
2766         SendToICS(ics_prefix);
2767         SendToICS("sought\n"); // should this be "sought all"?
2768     } else { // issue challenge based on clicked ad
2769         int dist = 10000; int i, closest = 0, second = 0;
2770         for(i=0; i<nrOfSeekAds; i++) {
2771             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2772             if(d < dist) { dist = d; closest = i; }
2773             second += (d - zList[i] < 120); // count in-range ads
2774             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2775         }
2776         if(dist < 120) {
2777             char buf[MSG_SIZ];
2778             second = (second > 1);
2779             if(displayed != closest || second != lastSecond) {
2780                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2781                 lastSecond = second; displayed = closest;
2782             }
2783             if(click == Press) {
2784                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2785                 lastDown = closest;
2786                 return TRUE;
2787             } // on press 'hit', only show info
2788             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2789             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2790             SendToICS(ics_prefix);
2791             SendToICS(buf);
2792             return TRUE; // let incoming board of started game pop down the graph
2793         } else if(click == Release) { // release 'miss' is ignored
2794             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2795             if(moving == 2) { // right up-click
2796                 nrOfSeekAds = 0; // refresh graph
2797                 soughtPending = TRUE;
2798                 SendToICS(ics_prefix);
2799                 SendToICS("sought\n"); // should this be "sought all"?
2800             }
2801             return TRUE;
2802         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2803         // press miss or release hit 'pop down' seek graph
2804         seekGraphUp = FALSE;
2805         DrawPosition(TRUE, NULL);
2806     }
2807     return TRUE;
2808 }
2809
2810 void
2811 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2812 {
2813 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2814 #define STARTED_NONE 0
2815 #define STARTED_MOVES 1
2816 #define STARTED_BOARD 2
2817 #define STARTED_OBSERVE 3
2818 #define STARTED_HOLDINGS 4
2819 #define STARTED_CHATTER 5
2820 #define STARTED_COMMENT 6
2821 #define STARTED_MOVES_NOHIDE 7
2822
2823     static int started = STARTED_NONE;
2824     static char parse[20000];
2825     static int parse_pos = 0;
2826     static char buf[BUF_SIZE + 1];
2827     static int firstTime = TRUE, intfSet = FALSE;
2828     static ColorClass prevColor = ColorNormal;
2829     static int savingComment = FALSE;
2830     static int cmatch = 0; // continuation sequence match
2831     char *bp;
2832     char str[MSG_SIZ];
2833     int i, oldi;
2834     int buf_len;
2835     int next_out;
2836     int tkind;
2837     int backup;    /* [DM] For zippy color lines */
2838     char *p;
2839     char talker[MSG_SIZ]; // [HGM] chat
2840     int channel, collective=0;
2841
2842     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2843
2844     if (appData.debugMode) {
2845       if (!error) {
2846         fprintf(debugFP, "<ICS: ");
2847         show_bytes(debugFP, data, count);
2848         fprintf(debugFP, "\n");
2849       }
2850     }
2851
2852     if (appData.debugMode) { int f = forwardMostMove;
2853         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2854                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2855                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2856     }
2857     if (count > 0) {
2858         /* If last read ended with a partial line that we couldn't parse,
2859            prepend it to the new read and try again. */
2860         if (leftover_len > 0) {
2861             for (i=0; i<leftover_len; i++)
2862               buf[i] = buf[leftover_start + i];
2863         }
2864
2865     /* copy new characters into the buffer */
2866     bp = buf + leftover_len;
2867     buf_len=leftover_len;
2868     for (i=0; i<count; i++)
2869     {
2870         // ignore these
2871         if (data[i] == '\r')
2872             continue;
2873
2874         // join lines split by ICS?
2875         if (!appData.noJoin)
2876         {
2877             /*
2878                 Joining just consists of finding matches against the
2879                 continuation sequence, and discarding that sequence
2880                 if found instead of copying it.  So, until a match
2881                 fails, there's nothing to do since it might be the
2882                 complete sequence, and thus, something we don't want
2883                 copied.
2884             */
2885             if (data[i] == cont_seq[cmatch])
2886             {
2887                 cmatch++;
2888                 if (cmatch == strlen(cont_seq))
2889                 {
2890                     cmatch = 0; // complete match.  just reset the counter
2891
2892                     /*
2893                         it's possible for the ICS to not include the space
2894                         at the end of the last word, making our [correct]
2895                         join operation fuse two separate words.  the server
2896                         does this when the space occurs at the width setting.
2897                     */
2898                     if (!buf_len || buf[buf_len-1] != ' ')
2899                     {
2900                         *bp++ = ' ';
2901                         buf_len++;
2902                     }
2903                 }
2904                 continue;
2905             }
2906             else if (cmatch)
2907             {
2908                 /*
2909                     match failed, so we have to copy what matched before
2910                     falling through and copying this character.  In reality,
2911                     this will only ever be just the newline character, but
2912                     it doesn't hurt to be precise.
2913                 */
2914                 strncpy(bp, cont_seq, cmatch);
2915                 bp += cmatch;
2916                 buf_len += cmatch;
2917                 cmatch = 0;
2918             }
2919         }
2920
2921         // copy this char
2922         *bp++ = data[i];
2923         buf_len++;
2924     }
2925
2926         buf[buf_len] = NULLCHAR;
2927 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2928         next_out = 0;
2929         leftover_start = 0;
2930
2931         i = 0;
2932         while (i < buf_len) {
2933             /* Deal with part of the TELNET option negotiation
2934                protocol.  We refuse to do anything beyond the
2935                defaults, except that we allow the WILL ECHO option,
2936                which ICS uses to turn off password echoing when we are
2937                directly connected to it.  We reject this option
2938                if localLineEditing mode is on (always on in xboard)
2939                and we are talking to port 23, which might be a real
2940                telnet server that will try to keep WILL ECHO on permanently.
2941              */
2942             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2943                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2944                 unsigned char option;
2945                 oldi = i;
2946                 switch ((unsigned char) buf[++i]) {
2947                   case TN_WILL:
2948                     if (appData.debugMode)
2949                       fprintf(debugFP, "\n<WILL ");
2950                     switch (option = (unsigned char) buf[++i]) {
2951                       case TN_ECHO:
2952                         if (appData.debugMode)
2953                           fprintf(debugFP, "ECHO ");
2954                         /* Reply only if this is a change, according
2955                            to the protocol rules. */
2956                         if (remoteEchoOption) break;
2957                         if (appData.localLineEditing &&
2958                             atoi(appData.icsPort) == TN_PORT) {
2959                             TelnetRequest(TN_DONT, TN_ECHO);
2960                         } else {
2961                             EchoOff();
2962                             TelnetRequest(TN_DO, TN_ECHO);
2963                             remoteEchoOption = TRUE;
2964                         }
2965                         break;
2966                       default:
2967                         if (appData.debugMode)
2968                           fprintf(debugFP, "%d ", option);
2969                         /* Whatever this is, we don't want it. */
2970                         TelnetRequest(TN_DONT, option);
2971                         break;
2972                     }
2973                     break;
2974                   case TN_WONT:
2975                     if (appData.debugMode)
2976                       fprintf(debugFP, "\n<WONT ");
2977                     switch (option = (unsigned char) buf[++i]) {
2978                       case TN_ECHO:
2979                         if (appData.debugMode)
2980                           fprintf(debugFP, "ECHO ");
2981                         /* Reply only if this is a change, according
2982                            to the protocol rules. */
2983                         if (!remoteEchoOption) break;
2984                         EchoOn();
2985                         TelnetRequest(TN_DONT, TN_ECHO);
2986                         remoteEchoOption = FALSE;
2987                         break;
2988                       default:
2989                         if (appData.debugMode)
2990                           fprintf(debugFP, "%d ", (unsigned char) option);
2991                         /* Whatever this is, it must already be turned
2992                            off, because we never agree to turn on
2993                            anything non-default, so according to the
2994                            protocol rules, we don't reply. */
2995                         break;
2996                     }
2997                     break;
2998                   case TN_DO:
2999                     if (appData.debugMode)
3000                       fprintf(debugFP, "\n<DO ");
3001                     switch (option = (unsigned char) buf[++i]) {
3002                       default:
3003                         /* Whatever this is, we refuse to do it. */
3004                         if (appData.debugMode)
3005                           fprintf(debugFP, "%d ", option);
3006                         TelnetRequest(TN_WONT, option);
3007                         break;
3008                     }
3009                     break;
3010                   case TN_DONT:
3011                     if (appData.debugMode)
3012                       fprintf(debugFP, "\n<DONT ");
3013                     switch (option = (unsigned char) buf[++i]) {
3014                       default:
3015                         if (appData.debugMode)
3016                           fprintf(debugFP, "%d ", option);
3017                         /* Whatever this is, we are already not doing
3018                            it, because we never agree to do anything
3019                            non-default, so according to the protocol
3020                            rules, we don't reply. */
3021                         break;
3022                     }
3023                     break;
3024                   case TN_IAC:
3025                     if (appData.debugMode)
3026                       fprintf(debugFP, "\n<IAC ");
3027                     /* Doubled IAC; pass it through */
3028                     i--;
3029                     break;
3030                   default:
3031                     if (appData.debugMode)
3032                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3033                     /* Drop all other telnet commands on the floor */
3034                     break;
3035                 }
3036                 if (oldi > next_out)
3037                   SendToPlayer(&buf[next_out], oldi - next_out);
3038                 if (++i > next_out)
3039                   next_out = i;
3040                 continue;
3041             }
3042
3043             /* OK, this at least will *usually* work */
3044             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3045                 loggedOn = TRUE;
3046             }
3047
3048             if (loggedOn && !intfSet) {
3049                 if (ics_type == ICS_ICC) {
3050                   snprintf(str, MSG_SIZ,
3051                           "/set-quietly interface %s\n/set-quietly style 12\n",
3052                           programVersion);
3053                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3054                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3055                 } else if (ics_type == ICS_CHESSNET) {
3056                   snprintf(str, MSG_SIZ, "/style 12\n");
3057                 } else {
3058                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3059                   strcat(str, programVersion);
3060                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3061                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3062                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3063 #ifdef WIN32
3064                   strcat(str, "$iset nohighlight 1\n");
3065 #endif
3066                   strcat(str, "$iset lock 1\n$style 12\n");
3067                 }
3068                 SendToICS(str);
3069                 NotifyFrontendLogin();
3070                 intfSet = TRUE;
3071             }
3072
3073             if (started == STARTED_COMMENT) {
3074                 /* Accumulate characters in comment */
3075                 parse[parse_pos++] = buf[i];
3076                 if (buf[i] == '\n') {
3077                     parse[parse_pos] = NULLCHAR;
3078                     if(chattingPartner>=0) {
3079                         char mess[MSG_SIZ];
3080                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3081                         OutputChatMessage(chattingPartner, mess);
3082                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3083                             int p;
3084                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3085                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3086                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3087                                 OutputChatMessage(p, mess);
3088                                 break;
3089                             }
3090                         }
3091                         chattingPartner = -1;
3092                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3093                         collective = 0;
3094                     } else
3095                     if(!suppressKibitz) // [HGM] kibitz
3096                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3097                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3098                         int nrDigit = 0, nrAlph = 0, j;
3099                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3100                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3101                         parse[parse_pos] = NULLCHAR;
3102                         // try to be smart: if it does not look like search info, it should go to
3103                         // ICS interaction window after all, not to engine-output window.
3104                         for(j=0; j<parse_pos; j++) { // count letters and digits
3105                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3106                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3107                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3108                         }
3109                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3110                             int depth=0; float score;
3111                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3112                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3113                                 pvInfoList[forwardMostMove-1].depth = depth;
3114                                 pvInfoList[forwardMostMove-1].score = 100*score;
3115                             }
3116                             OutputKibitz(suppressKibitz, parse);
3117                         } else {
3118                             char tmp[MSG_SIZ];
3119                             if(gameMode == IcsObserving) // restore original ICS messages
3120                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3121                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3122                             else
3123                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3124                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3125                             SendToPlayer(tmp, strlen(tmp));
3126                         }
3127                         next_out = i+1; // [HGM] suppress printing in ICS window
3128                     }
3129                     started = STARTED_NONE;
3130                 } else {
3131                     /* Don't match patterns against characters in comment */
3132                     i++;
3133                     continue;
3134                 }
3135             }
3136             if (started == STARTED_CHATTER) {
3137                 if (buf[i] != '\n') {
3138                     /* Don't match patterns against characters in chatter */
3139                     i++;
3140                     continue;
3141                 }
3142                 started = STARTED_NONE;
3143                 if(suppressKibitz) next_out = i+1;
3144             }
3145
3146             /* Kludge to deal with rcmd protocol */
3147             if (firstTime && looking_at(buf, &i, "\001*")) {
3148                 DisplayFatalError(&buf[1], 0, 1);
3149                 continue;
3150             } else {
3151                 firstTime = FALSE;
3152             }
3153
3154             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3155                 ics_type = ICS_ICC;
3156                 ics_prefix = "/";
3157                 if (appData.debugMode)
3158                   fprintf(debugFP, "ics_type %d\n", ics_type);
3159                 continue;
3160             }
3161             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3162                 ics_type = ICS_FICS;
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, "chess.net")) {
3169                 ics_type = ICS_CHESSNET;
3170                 ics_prefix = "/";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175
3176             if (!loggedOn &&
3177                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3178                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3179                  looking_at(buf, &i, "will be \"*\""))) {
3180               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3181               continue;
3182             }
3183
3184             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3185               char buf[MSG_SIZ];
3186               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3187               DisplayIcsInteractionTitle(buf);
3188               have_set_title = TRUE;
3189             }
3190
3191             /* skip finger notes */
3192             if (started == STARTED_NONE &&
3193                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3194                  (buf[i] == '1' && buf[i+1] == '0')) &&
3195                 buf[i+2] == ':' && buf[i+3] == ' ') {
3196               started = STARTED_CHATTER;
3197               i += 3;
3198               continue;
3199             }
3200
3201             oldi = i;
3202             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3203             if(appData.seekGraph) {
3204                 if(soughtPending && MatchSoughtLine(buf+i)) {
3205                     i = strstr(buf+i, "rated") - buf;
3206                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3207                     next_out = leftover_start = i;
3208                     started = STARTED_CHATTER;
3209                     suppressKibitz = TRUE;
3210                     continue;
3211                 }
3212                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3213                         && looking_at(buf, &i, "* ads displayed")) {
3214                     soughtPending = FALSE;
3215                     seekGraphUp = TRUE;
3216                     DrawSeekGraph();
3217                     continue;
3218                 }
3219                 if(appData.autoRefresh) {
3220                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3221                         int s = (ics_type == ICS_ICC); // ICC format differs
3222                         if(seekGraphUp)
3223                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3224                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3225                         looking_at(buf, &i, "*% "); // eat prompt
3226                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3227                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3228                         next_out = i; // suppress
3229                         continue;
3230                     }
3231                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3232                         char *p = star_match[0];
3233                         while(*p) {
3234                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3235                             while(*p && *p++ != ' '); // next
3236                         }
3237                         looking_at(buf, &i, "*% "); // eat prompt
3238                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3239                         next_out = i;
3240                         continue;
3241                     }
3242                 }
3243             }
3244
3245             /* skip formula vars */
3246             if (started == STARTED_NONE &&
3247                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3248               started = STARTED_CHATTER;
3249               i += 3;
3250               continue;
3251             }
3252
3253             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3254             if (appData.autoKibitz && started == STARTED_NONE &&
3255                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3256                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3257                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3258                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3259                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3260                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3261                         suppressKibitz = TRUE;
3262                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3263                         next_out = i;
3264                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3265                                 && (gameMode == IcsPlayingWhite)) ||
3266                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3267                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3268                             started = STARTED_CHATTER; // own kibitz we simply discard
3269                         else {
3270                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3271                             parse_pos = 0; parse[0] = NULLCHAR;
3272                             savingComment = TRUE;
3273                             suppressKibitz = gameMode != IcsObserving ? 2 :
3274                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3275                         }
3276                         continue;
3277                 } else
3278                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3279                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3280                          && atoi(star_match[0])) {
3281                     // suppress the acknowledgements of our own autoKibitz
3282                     char *p;
3283                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3284                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3285                     SendToPlayer(star_match[0], strlen(star_match[0]));
3286                     if(looking_at(buf, &i, "*% ")) // eat prompt
3287                         suppressKibitz = FALSE;
3288                     next_out = i;
3289                     continue;
3290                 }
3291             } // [HGM] kibitz: end of patch
3292
3293             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3294
3295             // [HGM] chat: intercept tells by users for which we have an open chat window
3296             channel = -1;
3297             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3298                                            looking_at(buf, &i, "* whispers:") ||
3299                                            looking_at(buf, &i, "* kibitzes:") ||
3300                                            looking_at(buf, &i, "* shouts:") ||
3301                                            looking_at(buf, &i, "* c-shouts:") ||
3302                                            looking_at(buf, &i, "--> * ") ||
3303                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3304                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3305                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3306                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3307                 int p;
3308                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3309                 chattingPartner = -1; collective = 0;
3310
3311                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3312                 for(p=0; p<MAX_CHAT; p++) {
3313                     collective = 1;
3314                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3315                     talker[0] = '['; strcat(talker, "] ");
3316                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3317                     chattingPartner = p; break;
3318                     }
3319                 } else
3320                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3321                 for(p=0; p<MAX_CHAT; p++) {
3322                     collective = 1;
3323                     if(!strcmp("kibitzes", chatPartner[p])) {
3324                         talker[0] = '['; strcat(talker, "] ");
3325                         chattingPartner = p; break;
3326                     }
3327                 } else
3328                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3329                 for(p=0; p<MAX_CHAT; p++) {
3330                     collective = 1;
3331                     if(!strcmp("whispers", chatPartner[p])) {
3332                         talker[0] = '['; strcat(talker, "] ");
3333                         chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3337                   if(buf[i-8] == '-' && buf[i-3] == 't')
3338                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3339                     collective = 1;
3340                     if(!strcmp("c-shouts", chatPartner[p])) {
3341                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3342                         chattingPartner = p; break;
3343                     }
3344                   }
3345                   if(chattingPartner < 0)
3346                   for(p=0; p<MAX_CHAT; p++) {
3347                     collective = 1;
3348                     if(!strcmp("shouts", chatPartner[p])) {
3349                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3350                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3351                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3352                         chattingPartner = p; break;
3353                     }
3354                   }
3355                 }
3356                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3357                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3358                     talker[0] = 0;
3359                     Colorize(ColorTell, FALSE);
3360                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3361                     collective |= 2;
3362                     chattingPartner = p; break;
3363                 }
3364                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3365                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3366                     started = STARTED_COMMENT;
3367                     parse_pos = 0; parse[0] = NULLCHAR;
3368                     savingComment = 3 + chattingPartner; // counts as TRUE
3369                     if(collective == 3) i = oldi; else {
3370                         suppressKibitz = TRUE;
3371                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3372                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3373                         continue;
3374                     }
3375                 }
3376             } // [HGM] chat: end of patch
3377
3378           backup = i;
3379             if (appData.zippyTalk || appData.zippyPlay) {
3380                 /* [DM] Backup address for color zippy lines */
3381 #if ZIPPY
3382                if (loggedOn == TRUE)
3383                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3384                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3385 #endif
3386             } // [DM] 'else { ' deleted
3387                 if (
3388                     /* Regular tells and says */
3389                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3390                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3391                     looking_at(buf, &i, "* says: ") ||
3392                     /* Don't color "message" or "messages" output */
3393                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3394                     looking_at(buf, &i, "*. * at *:*: ") ||
3395                     looking_at(buf, &i, "--* (*:*): ") ||
3396                     /* Message notifications (same color as tells) */
3397                     looking_at(buf, &i, "* has left a message ") ||
3398                     looking_at(buf, &i, "* just sent you a message:\n") ||
3399                     /* Whispers and kibitzes */
3400                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3401                     looking_at(buf, &i, "* kibitzes: ") ||
3402                     /* Channel tells */
3403                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3404
3405                   if (tkind == 1 && strchr(star_match[0], ':')) {
3406                       /* Avoid "tells you:" spoofs in channels */
3407                      tkind = 3;
3408                   }
3409                   if (star_match[0][0] == NULLCHAR ||
3410                       strchr(star_match[0], ' ') ||
3411                       (tkind == 3 && strchr(star_match[1], ' '))) {
3412                     /* Reject bogus matches */
3413                     i = oldi;
3414                   } else {
3415                     if (appData.colorize) {
3416                       if (oldi > next_out) {
3417                         SendToPlayer(&buf[next_out], oldi - next_out);
3418                         next_out = oldi;
3419                       }
3420                       switch (tkind) {
3421                       case 1:
3422                         Colorize(ColorTell, FALSE);
3423                         curColor = ColorTell;
3424                         break;
3425                       case 2:
3426                         Colorize(ColorKibitz, FALSE);
3427                         curColor = ColorKibitz;
3428                         break;
3429                       case 3:
3430                         p = strrchr(star_match[1], '(');
3431                         if (p == NULL) {
3432                           p = star_match[1];
3433                         } else {
3434                           p++;
3435                         }
3436                         if (atoi(p) == 1) {
3437                           Colorize(ColorChannel1, FALSE);
3438                           curColor = ColorChannel1;
3439                         } else {
3440                           Colorize(ColorChannel, FALSE);
3441                           curColor = ColorChannel;
3442                         }
3443                         break;
3444                       case 5:
3445                         curColor = ColorNormal;
3446                         break;
3447                       }
3448                     }
3449                     if (started == STARTED_NONE && appData.autoComment &&
3450                         (gameMode == IcsObserving ||
3451                          gameMode == IcsPlayingWhite ||
3452                          gameMode == IcsPlayingBlack)) {
3453                       parse_pos = i - oldi;
3454                       memcpy(parse, &buf[oldi], parse_pos);
3455                       parse[parse_pos] = NULLCHAR;
3456                       started = STARTED_COMMENT;
3457                       savingComment = TRUE;
3458                     } else if(collective != 3) {
3459                       started = STARTED_CHATTER;
3460                       savingComment = FALSE;
3461                     }
3462                     loggedOn = TRUE;
3463                     continue;
3464                   }
3465                 }
3466
3467                 if (looking_at(buf, &i, "* s-shouts: ") ||
3468                     looking_at(buf, &i, "* c-shouts: ")) {
3469                     if (appData.colorize) {
3470                         if (oldi > next_out) {
3471                             SendToPlayer(&buf[next_out], oldi - next_out);
3472                             next_out = oldi;
3473                         }
3474                         Colorize(ColorSShout, FALSE);
3475                         curColor = ColorSShout;
3476                     }
3477                     loggedOn = TRUE;
3478                     started = STARTED_CHATTER;
3479                     continue;
3480                 }
3481
3482                 if (looking_at(buf, &i, "--->")) {
3483                     loggedOn = TRUE;
3484                     continue;
3485                 }
3486
3487                 if (looking_at(buf, &i, "* shouts: ") ||
3488                     looking_at(buf, &i, "--> ")) {
3489                     if (appData.colorize) {
3490                         if (oldi > next_out) {
3491                             SendToPlayer(&buf[next_out], oldi - next_out);
3492                             next_out = oldi;
3493                         }
3494                         Colorize(ColorShout, FALSE);
3495                         curColor = ColorShout;
3496                     }
3497                     loggedOn = TRUE;
3498                     started = STARTED_CHATTER;
3499                     continue;
3500                 }
3501
3502                 if (looking_at( buf, &i, "Challenge:")) {
3503                     if (appData.colorize) {
3504                         if (oldi > next_out) {
3505                             SendToPlayer(&buf[next_out], oldi - next_out);
3506                             next_out = oldi;
3507                         }
3508                         Colorize(ColorChallenge, FALSE);
3509                         curColor = ColorChallenge;
3510                     }
3511                     loggedOn = TRUE;
3512                     continue;
3513                 }
3514
3515                 if (looking_at(buf, &i, "* offers you") ||
3516                     looking_at(buf, &i, "* offers to be") ||
3517                     looking_at(buf, &i, "* would like to") ||
3518                     looking_at(buf, &i, "* requests to") ||
3519                     looking_at(buf, &i, "Your opponent offers") ||
3520                     looking_at(buf, &i, "Your opponent requests")) {
3521
3522                     if (appData.colorize) {
3523                         if (oldi > next_out) {
3524                             SendToPlayer(&buf[next_out], oldi - next_out);
3525                             next_out = oldi;
3526                         }
3527                         Colorize(ColorRequest, FALSE);
3528                         curColor = ColorRequest;
3529                     }
3530                     continue;
3531                 }
3532
3533                 if (looking_at(buf, &i, "* (*) seeking")) {
3534                     if (appData.colorize) {
3535                         if (oldi > next_out) {
3536                             SendToPlayer(&buf[next_out], oldi - next_out);
3537                             next_out = oldi;
3538                         }
3539                         Colorize(ColorSeek, FALSE);
3540                         curColor = ColorSeek;
3541                     }
3542                     continue;
3543             }
3544
3545           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3546
3547             if (looking_at(buf, &i, "\\   ")) {
3548                 if (prevColor != ColorNormal) {
3549                     if (oldi > next_out) {
3550                         SendToPlayer(&buf[next_out], oldi - next_out);
3551                         next_out = oldi;
3552                     }
3553                     Colorize(prevColor, TRUE);
3554                     curColor = prevColor;
3555                 }
3556                 if (savingComment) {
3557                     parse_pos = i - oldi;
3558                     memcpy(parse, &buf[oldi], parse_pos);
3559                     parse[parse_pos] = NULLCHAR;
3560                     started = STARTED_COMMENT;
3561                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3562                         chattingPartner = savingComment - 3; // kludge to remember the box
3563                 } else {
3564                     started = STARTED_CHATTER;
3565                 }
3566                 continue;
3567             }
3568
3569             if (looking_at(buf, &i, "Black Strength :") ||
3570                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3571                 looking_at(buf, &i, "<10>") ||
3572                 looking_at(buf, &i, "#@#")) {
3573                 /* Wrong board style */
3574                 loggedOn = TRUE;
3575                 SendToICS(ics_prefix);
3576                 SendToICS("set style 12\n");
3577                 SendToICS(ics_prefix);
3578                 SendToICS("refresh\n");
3579                 continue;
3580             }
3581
3582             if (looking_at(buf, &i, "login:")) {
3583               if (!have_sent_ICS_logon) {
3584                 if(ICSInitScript())
3585                   have_sent_ICS_logon = 1;
3586                 else // no init script was found
3587                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3588               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3589                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3590               }
3591                 continue;
3592             }
3593
3594             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3595                 (looking_at(buf, &i, "\n<12> ") ||
3596                  looking_at(buf, &i, "<12> "))) {
3597                 loggedOn = TRUE;
3598                 if (oldi > next_out) {
3599                     SendToPlayer(&buf[next_out], oldi - next_out);
3600                 }
3601                 next_out = i;
3602                 started = STARTED_BOARD;
3603                 parse_pos = 0;
3604                 continue;
3605             }
3606
3607             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3608                 looking_at(buf, &i, "<b1> ")) {
3609                 if (oldi > next_out) {
3610                     SendToPlayer(&buf[next_out], oldi - next_out);
3611                 }
3612                 next_out = i;
3613                 started = STARTED_HOLDINGS;
3614                 parse_pos = 0;
3615                 continue;
3616             }
3617
3618             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3619                 loggedOn = TRUE;
3620                 /* Header for a move list -- first line */
3621
3622                 switch (ics_getting_history) {
3623                   case H_FALSE:
3624                     switch (gameMode) {
3625                       case IcsIdle:
3626                       case BeginningOfGame:
3627                         /* User typed "moves" or "oldmoves" while we
3628                            were idle.  Pretend we asked for these
3629                            moves and soak them up so user can step
3630                            through them and/or save them.
3631                            */
3632                         Reset(FALSE, TRUE);
3633                         gameMode = IcsObserving;
3634                         ModeHighlight();
3635                         ics_gamenum = -1;
3636                         ics_getting_history = H_GOT_UNREQ_HEADER;
3637                         break;
3638                       case EditGame: /*?*/
3639                       case EditPosition: /*?*/
3640                         /* Should above feature work in these modes too? */
3641                         /* For now it doesn't */
3642                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3643                         break;
3644                       default:
3645                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3646                         break;
3647                     }
3648                     break;
3649                   case H_REQUESTED:
3650                     /* Is this the right one? */
3651                     if (gameInfo.white && gameInfo.black &&
3652                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3653                         strcmp(gameInfo.black, star_match[2]) == 0) {
3654                         /* All is well */
3655                         ics_getting_history = H_GOT_REQ_HEADER;
3656                     }
3657                     break;
3658                   case H_GOT_REQ_HEADER:
3659                   case H_GOT_UNREQ_HEADER:
3660                   case H_GOT_UNWANTED_HEADER:
3661                   case H_GETTING_MOVES:
3662                     /* Should not happen */
3663                     DisplayError(_("Error gathering move list: two headers"), 0);
3664                     ics_getting_history = H_FALSE;
3665                     break;
3666                 }
3667
3668                 /* Save player ratings into gameInfo if needed */
3669                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3670                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3671                     (gameInfo.whiteRating == -1 ||
3672                      gameInfo.blackRating == -1)) {
3673
3674                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3675                     gameInfo.blackRating = string_to_rating(star_match[3]);
3676                     if (appData.debugMode)
3677                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3678                               gameInfo.whiteRating, gameInfo.blackRating);
3679                 }
3680                 continue;
3681             }
3682
3683             if (looking_at(buf, &i,
3684               "* * match, initial time: * minute*, increment: * second")) {
3685                 /* Header for a move list -- second line */
3686                 /* Initial board will follow if this is a wild game */
3687                 if (gameInfo.event != NULL) free(gameInfo.event);
3688                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3689                 gameInfo.event = StrSave(str);
3690                 /* [HGM] we switched variant. Translate boards if needed. */
3691                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3692                 continue;
3693             }
3694
3695             if (looking_at(buf, &i, "Move  ")) {
3696                 /* Beginning of a move list */
3697                 switch (ics_getting_history) {
3698                   case H_FALSE:
3699                     /* Normally should not happen */
3700                     /* Maybe user hit reset while we were parsing */
3701                     break;
3702                   case H_REQUESTED:
3703                     /* Happens if we are ignoring a move list that is not
3704                      * the one we just requested.  Common if the user
3705                      * tries to observe two games without turning off
3706                      * getMoveList */
3707                     break;
3708                   case H_GETTING_MOVES:
3709                     /* Should not happen */
3710                     DisplayError(_("Error gathering move list: nested"), 0);
3711                     ics_getting_history = H_FALSE;
3712                     break;
3713                   case H_GOT_REQ_HEADER:
3714                     ics_getting_history = H_GETTING_MOVES;
3715                     started = STARTED_MOVES;
3716                     parse_pos = 0;
3717                     if (oldi > next_out) {
3718                         SendToPlayer(&buf[next_out], oldi - next_out);
3719                     }
3720                     break;
3721                   case H_GOT_UNREQ_HEADER:
3722                     ics_getting_history = H_GETTING_MOVES;
3723                     started = STARTED_MOVES_NOHIDE;
3724                     parse_pos = 0;
3725                     break;
3726                   case H_GOT_UNWANTED_HEADER:
3727                     ics_getting_history = H_FALSE;
3728                     break;
3729                 }
3730                 continue;
3731             }
3732
3733             if (looking_at(buf, &i, "% ") ||
3734                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3735                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3736                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3737                     soughtPending = FALSE;
3738                     seekGraphUp = TRUE;
3739                     DrawSeekGraph();
3740                 }
3741                 if(suppressKibitz) next_out = i;
3742                 savingComment = FALSE;
3743                 suppressKibitz = 0;
3744                 switch (started) {
3745                   case STARTED_MOVES:
3746                   case STARTED_MOVES_NOHIDE:
3747                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3748                     parse[parse_pos + i - oldi] = NULLCHAR;
3749                     ParseGameHistory(parse);
3750 #if ZIPPY
3751                     if (appData.zippyPlay && first.initDone) {
3752                         FeedMovesToProgram(&first, forwardMostMove);
3753                         if (gameMode == IcsPlayingWhite) {
3754                             if (WhiteOnMove(forwardMostMove)) {
3755                                 if (first.sendTime) {
3756                                   if (first.useColors) {
3757                                     SendToProgram("black\n", &first);
3758                                   }
3759                                   SendTimeRemaining(&first, TRUE);
3760                                 }
3761                                 if (first.useColors) {
3762                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3763                                 }
3764                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3765                                 first.maybeThinking = TRUE;
3766                             } else {
3767                                 if (first.usePlayother) {
3768                                   if (first.sendTime) {
3769                                     SendTimeRemaining(&first, TRUE);
3770                                   }
3771                                   SendToProgram("playother\n", &first);
3772                                   firstMove = FALSE;
3773                                 } else {
3774                                   firstMove = TRUE;
3775                                 }
3776                             }
3777                         } else if (gameMode == IcsPlayingBlack) {
3778                             if (!WhiteOnMove(forwardMostMove)) {
3779                                 if (first.sendTime) {
3780                                   if (first.useColors) {
3781                                     SendToProgram("white\n", &first);
3782                                   }
3783                                   SendTimeRemaining(&first, FALSE);
3784                                 }
3785                                 if (first.useColors) {
3786                                   SendToProgram("black\n", &first);
3787                                 }
3788                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3789                                 first.maybeThinking = TRUE;
3790                             } else {
3791                                 if (first.usePlayother) {
3792                                   if (first.sendTime) {
3793                                     SendTimeRemaining(&first, FALSE);
3794                                   }
3795                                   SendToProgram("playother\n", &first);
3796                                   firstMove = FALSE;
3797                                 } else {
3798                                   firstMove = TRUE;
3799                                 }
3800                             }
3801                         }
3802                     }
3803 #endif
3804                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3805                         /* Moves came from oldmoves or moves command
3806                            while we weren't doing anything else.
3807                            */
3808                         currentMove = forwardMostMove;
3809                         ClearHighlights();/*!!could figure this out*/
3810                         flipView = appData.flipView;
3811                         DrawPosition(TRUE, boards[currentMove]);
3812                         DisplayBothClocks();
3813                         snprintf(str, MSG_SIZ, "%s %s %s",
3814                                 gameInfo.white, _("vs."),  gameInfo.black);
3815                         DisplayTitle(str);
3816                         gameMode = IcsIdle;
3817                     } else {
3818                         /* Moves were history of an active game */
3819                         if (gameInfo.resultDetails != NULL) {
3820                             free(gameInfo.resultDetails);
3821                             gameInfo.resultDetails = NULL;
3822                         }
3823                     }
3824                     HistorySet(parseList, backwardMostMove,
3825                                forwardMostMove, currentMove-1);
3826                     DisplayMove(currentMove - 1);
3827                     if (started == STARTED_MOVES) next_out = i;
3828                     started = STARTED_NONE;
3829                     ics_getting_history = H_FALSE;
3830                     break;
3831
3832                   case STARTED_OBSERVE:
3833                     started = STARTED_NONE;
3834                     SendToICS(ics_prefix);
3835                     SendToICS("refresh\n");
3836                     break;
3837
3838                   default:
3839                     break;
3840                 }
3841                 if(bookHit) { // [HGM] book: simulate book reply
3842                     static char bookMove[MSG_SIZ]; // a bit generous?
3843
3844                     programStats.nodes = programStats.depth = programStats.time =
3845                     programStats.score = programStats.got_only_move = 0;
3846                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3847
3848                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3849                     strcat(bookMove, bookHit);
3850                     HandleMachineMove(bookMove, &first);
3851                 }
3852                 continue;
3853             }
3854
3855             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3856                  started == STARTED_HOLDINGS ||
3857                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3858                 /* Accumulate characters in move list or board */
3859                 parse[parse_pos++] = buf[i];
3860             }
3861
3862             /* Start of game messages.  Mostly we detect start of game
3863                when the first board image arrives.  On some versions
3864                of the ICS, though, we need to do a "refresh" after starting
3865                to observe in order to get the current board right away. */
3866             if (looking_at(buf, &i, "Adding game * to observation list")) {
3867                 started = STARTED_OBSERVE;
3868                 continue;
3869             }
3870
3871             /* Handle auto-observe */
3872             if (appData.autoObserve &&
3873                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3874                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3875                 char *player;
3876                 /* Choose the player that was highlighted, if any. */
3877                 if (star_match[0][0] == '\033' ||
3878                     star_match[1][0] != '\033') {
3879                     player = star_match[0];
3880                 } else {
3881                     player = star_match[2];
3882                 }
3883                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3884                         ics_prefix, StripHighlightAndTitle(player));
3885                 SendToICS(str);
3886
3887                 /* Save ratings from notify string */
3888                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3889                 player1Rating = string_to_rating(star_match[1]);
3890                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3891                 player2Rating = string_to_rating(star_match[3]);
3892
3893                 if (appData.debugMode)
3894                   fprintf(debugFP,
3895                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3896                           player1Name, player1Rating,
3897                           player2Name, player2Rating);
3898
3899                 continue;
3900             }
3901
3902             /* Deal with automatic examine mode after a game,
3903                and with IcsObserving -> IcsExamining transition */
3904             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3905                 looking_at(buf, &i, "has made you an examiner of game *")) {
3906
3907                 int gamenum = atoi(star_match[0]);
3908                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3909                     gamenum == ics_gamenum) {
3910                     /* We were already playing or observing this game;
3911                        no need to refetch history */
3912                     gameMode = IcsExamining;
3913                     if (pausing) {
3914                         pauseExamForwardMostMove = forwardMostMove;
3915                     } else if (currentMove < forwardMostMove) {
3916                         ForwardInner(forwardMostMove);
3917                     }
3918                 } else {
3919                     /* I don't think this case really can happen */
3920                     SendToICS(ics_prefix);
3921                     SendToICS("refresh\n");
3922                 }
3923                 continue;
3924             }
3925
3926             /* Error messages */
3927 //          if (ics_user_moved) {
3928             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3929                 if (looking_at(buf, &i, "Illegal move") ||
3930                     looking_at(buf, &i, "Not a legal move") ||
3931                     looking_at(buf, &i, "Your king is in check") ||
3932                     looking_at(buf, &i, "It isn't your turn") ||
3933                     looking_at(buf, &i, "It is not your move")) {
3934                     /* Illegal move */
3935                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3936                         currentMove = forwardMostMove-1;
3937                         DisplayMove(currentMove - 1); /* before DMError */
3938                         DrawPosition(FALSE, boards[currentMove]);
3939                         SwitchClocks(forwardMostMove-1); // [HGM] race
3940                         DisplayBothClocks();
3941                     }
3942                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3943                     ics_user_moved = 0;
3944                     continue;
3945                 }
3946             }
3947
3948             if (looking_at(buf, &i, "still have time") ||
3949                 looking_at(buf, &i, "not out of time") ||
3950                 looking_at(buf, &i, "either player is out of time") ||
3951                 looking_at(buf, &i, "has timeseal; checking")) {
3952                 /* We must have called his flag a little too soon */
3953                 whiteFlag = blackFlag = FALSE;
3954                 continue;
3955             }
3956
3957             if (looking_at(buf, &i, "added * seconds to") ||
3958                 looking_at(buf, &i, "seconds were added to")) {
3959                 /* Update the clocks */
3960                 SendToICS(ics_prefix);
3961                 SendToICS("refresh\n");
3962                 continue;
3963             }
3964
3965             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3966                 ics_clock_paused = TRUE;
3967                 StopClocks();
3968                 continue;
3969             }
3970
3971             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3972                 ics_clock_paused = FALSE;
3973                 StartClocks();
3974                 continue;
3975             }
3976
3977             /* Grab player ratings from the Creating: message.
3978                Note we have to check for the special case when
3979                the ICS inserts things like [white] or [black]. */
3980             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3981                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3982                 /* star_matches:
3983                    0    player 1 name (not necessarily white)
3984                    1    player 1 rating
3985                    2    empty, white, or black (IGNORED)
3986                    3    player 2 name (not necessarily black)
3987                    4    player 2 rating
3988
3989                    The names/ratings are sorted out when the game
3990                    actually starts (below).
3991                 */
3992                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3993                 player1Rating = string_to_rating(star_match[1]);
3994                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3995                 player2Rating = string_to_rating(star_match[4]);
3996
3997                 if (appData.debugMode)
3998                   fprintf(debugFP,
3999                           "Ratings from 'Creating:' %s %d, %s %d\n",
4000                           player1Name, player1Rating,
4001                           player2Name, player2Rating);
4002
4003                 continue;
4004             }
4005
4006             /* Improved generic start/end-of-game messages */
4007             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4008                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4009                 /* If tkind == 0: */
4010                 /* star_match[0] is the game number */
4011                 /*           [1] is the white player's name */
4012                 /*           [2] is the black player's name */
4013                 /* For end-of-game: */
4014                 /*           [3] is the reason for the game end */
4015                 /*           [4] is a PGN end game-token, preceded by " " */
4016                 /* For start-of-game: */
4017                 /*           [3] begins with "Creating" or "Continuing" */
4018                 /*           [4] is " *" or empty (don't care). */
4019                 int gamenum = atoi(star_match[0]);
4020                 char *whitename, *blackname, *why, *endtoken;
4021                 ChessMove endtype = EndOfFile;
4022
4023                 if (tkind == 0) {
4024                   whitename = star_match[1];
4025                   blackname = star_match[2];
4026                   why = star_match[3];
4027                   endtoken = star_match[4];
4028                 } else {
4029                   whitename = star_match[1];
4030                   blackname = star_match[3];
4031                   why = star_match[5];
4032                   endtoken = star_match[6];
4033                 }
4034
4035                 /* Game start messages */
4036                 if (strncmp(why, "Creating ", 9) == 0 ||
4037                     strncmp(why, "Continuing ", 11) == 0) {
4038                     gs_gamenum = gamenum;
4039                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4040                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4041                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4042 #if ZIPPY
4043                     if (appData.zippyPlay) {
4044                         ZippyGameStart(whitename, blackname);
4045                     }
4046 #endif /*ZIPPY*/
4047                     partnerBoardValid = FALSE; // [HGM] bughouse
4048                     continue;
4049                 }
4050
4051                 /* Game end messages */
4052                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4053                     ics_gamenum != gamenum) {
4054                     continue;
4055                 }
4056                 while (endtoken[0] == ' ') endtoken++;
4057                 switch (endtoken[0]) {
4058                   case '*':
4059                   default:
4060                     endtype = GameUnfinished;
4061                     break;
4062                   case '0':
4063                     endtype = BlackWins;
4064                     break;
4065                   case '1':
4066                     if (endtoken[1] == '/')
4067                       endtype = GameIsDrawn;
4068                     else
4069                       endtype = WhiteWins;
4070                     break;
4071                 }
4072                 GameEnds(endtype, why, GE_ICS);
4073 #if ZIPPY
4074                 if (appData.zippyPlay && first.initDone) {
4075                     ZippyGameEnd(endtype, why);
4076                     if (first.pr == NoProc) {
4077                       /* Start the next process early so that we'll
4078                          be ready for the next challenge */
4079                       StartChessProgram(&first);
4080                     }
4081                     /* Send "new" early, in case this command takes
4082                        a long time to finish, so that we'll be ready
4083                        for the next challenge. */
4084                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4085                     Reset(TRUE, TRUE);
4086                 }
4087 #endif /*ZIPPY*/
4088                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4089                 continue;
4090             }
4091
4092             if (looking_at(buf, &i, "Removing game * from observation") ||
4093                 looking_at(buf, &i, "no longer observing game *") ||
4094                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4095                 if (gameMode == IcsObserving &&
4096                     atoi(star_match[0]) == ics_gamenum)
4097                   {
4098                       /* icsEngineAnalyze */
4099                       if (appData.icsEngineAnalyze) {
4100                             ExitAnalyzeMode();
4101                             ModeHighlight();
4102                       }
4103                       StopClocks();
4104                       gameMode = IcsIdle;
4105                       ics_gamenum = -1;
4106                       ics_user_moved = FALSE;
4107                   }
4108                 continue;
4109             }
4110
4111             if (looking_at(buf, &i, "no longer examining game *")) {
4112                 if (gameMode == IcsExamining &&
4113                     atoi(star_match[0]) == ics_gamenum)
4114                   {
4115                       gameMode = IcsIdle;
4116                       ics_gamenum = -1;
4117                       ics_user_moved = FALSE;
4118                   }
4119                 continue;
4120             }
4121
4122             /* Advance leftover_start past any newlines we find,
4123                so only partial lines can get reparsed */
4124             if (looking_at(buf, &i, "\n")) {
4125                 prevColor = curColor;
4126                 if (curColor != ColorNormal) {
4127                     if (oldi > next_out) {
4128                         SendToPlayer(&buf[next_out], oldi - next_out);
4129                         next_out = oldi;
4130                     }
4131                     Colorize(ColorNormal, FALSE);
4132                     curColor = ColorNormal;
4133                 }
4134                 if (started == STARTED_BOARD) {
4135                     started = STARTED_NONE;
4136                     parse[parse_pos] = NULLCHAR;
4137                     ParseBoard12(parse);
4138                     ics_user_moved = 0;
4139
4140                     /* Send premove here */
4141                     if (appData.premove) {
4142                       char str[MSG_SIZ];
4143                       if (currentMove == 0 &&
4144                           gameMode == IcsPlayingWhite &&
4145                           appData.premoveWhite) {
4146                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4147                         if (appData.debugMode)
4148                           fprintf(debugFP, "Sending premove:\n");
4149                         SendToICS(str);
4150                       } else if (currentMove == 1 &&
4151                                  gameMode == IcsPlayingBlack &&
4152                                  appData.premoveBlack) {
4153                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                         SendToICS(str);
4157                       } else if (gotPremove) {
4158                         gotPremove = 0;
4159                         ClearPremoveHighlights();
4160                         if (appData.debugMode)
4161                           fprintf(debugFP, "Sending premove:\n");
4162                           UserMoveEvent(premoveFromX, premoveFromY,
4163                                         premoveToX, premoveToY,
4164                                         premovePromoChar);
4165                       }
4166                     }
4167
4168                     /* Usually suppress following prompt */
4169                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4170                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4171                         if (looking_at(buf, &i, "*% ")) {
4172                             savingComment = FALSE;
4173                             suppressKibitz = 0;
4174                         }
4175                     }
4176                     next_out = i;
4177                 } else if (started == STARTED_HOLDINGS) {
4178                     int gamenum;
4179                     char new_piece[MSG_SIZ];
4180                     started = STARTED_NONE;
4181                     parse[parse_pos] = NULLCHAR;
4182                     if (appData.debugMode)
4183                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4184                                                         parse, currentMove);
4185                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4186                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4187                         if (gameInfo.variant == VariantNormal) {
4188                           /* [HGM] We seem to switch variant during a game!
4189                            * Presumably no holdings were displayed, so we have
4190                            * to move the position two files to the right to
4191                            * create room for them!
4192                            */
4193                           VariantClass newVariant;
4194                           switch(gameInfo.boardWidth) { // base guess on board width
4195                                 case 9:  newVariant = VariantShogi; break;
4196                                 case 10: newVariant = VariantGreat; break;
4197                                 default: newVariant = VariantCrazyhouse; break;
4198                           }
4199                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4200                           /* Get a move list just to see the header, which
4201                              will tell us whether this is really bug or zh */
4202                           if (ics_getting_history == H_FALSE) {
4203                             ics_getting_history = H_REQUESTED;
4204                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4205                             SendToICS(str);
4206                           }
4207                         }
4208                         new_piece[0] = NULLCHAR;
4209                         sscanf(parse, "game %d white [%s black [%s <- %s",
4210                                &gamenum, white_holding, black_holding,
4211                                new_piece);
4212                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4213                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4214                         /* [HGM] copy holdings to board holdings area */
4215                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4216                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4217                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4218 #if ZIPPY
4219                         if (appData.zippyPlay && first.initDone) {
4220                             ZippyHoldings(white_holding, black_holding,
4221                                           new_piece);
4222                         }
4223 #endif /*ZIPPY*/
4224                         if (tinyLayout || smallLayout) {
4225                             char wh[16], bh[16];
4226                             PackHolding(wh, white_holding);
4227                             PackHolding(bh, black_holding);
4228                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4229                                     gameInfo.white, gameInfo.black);
4230                         } else {
4231                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4232                                     gameInfo.white, white_holding, _("vs."),
4233                                     gameInfo.black, black_holding);
4234                         }
4235                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4236                         DrawPosition(FALSE, boards[currentMove]);
4237                         DisplayTitle(str);
4238                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4239                         sscanf(parse, "game %d white [%s black [%s <- %s",
4240                                &gamenum, white_holding, black_holding,
4241                                new_piece);
4242                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4243                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4244                         /* [HGM] copy holdings to partner-board holdings area */
4245                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4246                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4247                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4248                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4249                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4250                       }
4251                     }
4252                     /* Suppress following prompt */
4253                     if (looking_at(buf, &i, "*% ")) {
4254                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4255                         savingComment = FALSE;
4256                         suppressKibitz = 0;
4257                     }
4258                     next_out = i;
4259                 }
4260                 continue;
4261             }
4262
4263             i++;                /* skip unparsed character and loop back */
4264         }
4265
4266         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4267 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4268 //          SendToPlayer(&buf[next_out], i - next_out);
4269             started != STARTED_HOLDINGS && leftover_start > next_out) {
4270             SendToPlayer(&buf[next_out], leftover_start - next_out);
4271             next_out = i;
4272         }
4273
4274         leftover_len = buf_len - leftover_start;
4275         /* if buffer ends with something we couldn't parse,
4276            reparse it after appending the next read */
4277
4278     } else if (count == 0) {
4279         RemoveInputSource(isr);
4280         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4281     } else {
4282         DisplayFatalError(_("Error reading from ICS"), error, 1);
4283     }
4284 }
4285
4286
4287 /* Board style 12 looks like this:
4288
4289    <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
4290
4291  * The "<12> " is stripped before it gets to this routine.  The two
4292  * trailing 0's (flip state and clock ticking) are later addition, and
4293  * some chess servers may not have them, or may have only the first.
4294  * Additional trailing fields may be added in the future.
4295  */
4296
4297 #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"
4298
4299 #define RELATION_OBSERVING_PLAYED    0
4300 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4301 #define RELATION_PLAYING_MYMOVE      1
4302 #define RELATION_PLAYING_NOTMYMOVE  -1
4303 #define RELATION_EXAMINING           2
4304 #define RELATION_ISOLATED_BOARD     -3
4305 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4306
4307 void
4308 ParseBoard12 (char *string)
4309 {
4310 #if ZIPPY
4311     int i, takeback;
4312     char *bookHit = NULL; // [HGM] book
4313 #endif
4314     GameMode newGameMode;
4315     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4316     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4317     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4318     char to_play, board_chars[200];
4319     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4320     char black[32], white[32];
4321     Board board;
4322     int prevMove = currentMove;
4323     int ticking = 2;
4324     ChessMove moveType;
4325     int fromX, fromY, toX, toY;
4326     char promoChar;
4327     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4328     Boolean weird = FALSE, reqFlag = FALSE;
4329
4330     fromX = fromY = toX = toY = -1;
4331
4332     newGame = FALSE;
4333
4334     if (appData.debugMode)
4335       fprintf(debugFP, "Parsing board: %s\n", string);
4336
4337     move_str[0] = NULLCHAR;
4338     elapsed_time[0] = NULLCHAR;
4339     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4340         int  i = 0, j;
4341         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4342             if(string[i] == ' ') { ranks++; files = 0; }
4343             else files++;
4344             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4345             i++;
4346         }
4347         for(j = 0; j <i; j++) board_chars[j] = string[j];
4348         board_chars[i] = '\0';
4349         string += i + 1;
4350     }
4351     n = sscanf(string, PATTERN, &to_play, &double_push,
4352                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4353                &gamenum, white, black, &relation, &basetime, &increment,
4354                &white_stren, &black_stren, &white_time, &black_time,
4355                &moveNum, str, elapsed_time, move_str, &ics_flip,
4356                &ticking);
4357
4358     if (n < 21) {
4359         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4360         DisplayError(str, 0);
4361         return;
4362     }
4363
4364     /* Convert the move number to internal form */
4365     moveNum = (moveNum - 1) * 2;
4366     if (to_play == 'B') moveNum++;
4367     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4368       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4369                         0, 1);
4370       return;
4371     }
4372
4373     switch (relation) {
4374       case RELATION_OBSERVING_PLAYED:
4375       case RELATION_OBSERVING_STATIC:
4376         if (gamenum == -1) {
4377             /* Old ICC buglet */
4378             relation = RELATION_OBSERVING_STATIC;
4379         }
4380         newGameMode = IcsObserving;
4381         break;
4382       case RELATION_PLAYING_MYMOVE:
4383       case RELATION_PLAYING_NOTMYMOVE:
4384         newGameMode =
4385           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4386             IcsPlayingWhite : IcsPlayingBlack;
4387         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4388         break;
4389       case RELATION_EXAMINING:
4390         newGameMode = IcsExamining;
4391         break;
4392       case RELATION_ISOLATED_BOARD:
4393       default:
4394         /* Just display this board.  If user was doing something else,
4395            we will forget about it until the next board comes. */
4396         newGameMode = IcsIdle;
4397         break;
4398       case RELATION_STARTING_POSITION:
4399         newGameMode = gameMode;
4400         break;
4401     }
4402
4403     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4404         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4405          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4406       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4407       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4408       static int lastBgGame = -1;
4409       char *toSqr;
4410       for (k = 0; k < ranks; k++) {
4411         for (j = 0; j < files; j++)
4412           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4413         if(gameInfo.holdingsWidth > 1) {
4414              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4415              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4416         }
4417       }
4418       CopyBoard(partnerBoard, board);
4419       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4420         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4421         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4422       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4423       if(toSqr = strchr(str, '-')) {
4424         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4425         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4426       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4427       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4428       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4429       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4430       if(twoBoards) {
4431           DisplayWhiteClock(white_time*fac, to_play == 'W');
4432           DisplayBlackClock(black_time*fac, to_play != 'W');
4433           activePartner = to_play;
4434           if(gamenum != lastBgGame) {
4435               char buf[MSG_SIZ];
4436               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4437               DisplayTitle(buf);
4438           }
4439           lastBgGame = gamenum;
4440           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4441                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4442       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4443                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4444       if(!twoBoards) DisplayMessage(partnerStatus, "");
4445         partnerBoardValid = TRUE;
4446       return;
4447     }
4448
4449     if(appData.dualBoard && appData.bgObserve) {
4450         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4451             SendToICS(ics_prefix), SendToICS("pobserve\n");
4452         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4453             char buf[MSG_SIZ];
4454             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4455             SendToICS(buf);
4456         }
4457     }
4458
4459     /* Modify behavior for initial board display on move listing
4460        of wild games.
4461        */
4462     switch (ics_getting_history) {
4463       case H_FALSE:
4464       case H_REQUESTED:
4465         break;
4466       case H_GOT_REQ_HEADER:
4467       case H_GOT_UNREQ_HEADER:
4468         /* This is the initial position of the current game */
4469         gamenum = ics_gamenum;
4470         moveNum = 0;            /* old ICS bug workaround */
4471         if (to_play == 'B') {
4472           startedFromSetupPosition = TRUE;
4473           blackPlaysFirst = TRUE;
4474           moveNum = 1;
4475           if (forwardMostMove == 0) forwardMostMove = 1;
4476           if (backwardMostMove == 0) backwardMostMove = 1;
4477           if (currentMove == 0) currentMove = 1;
4478         }
4479         newGameMode = gameMode;
4480         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4481         break;
4482       case H_GOT_UNWANTED_HEADER:
4483         /* This is an initial board that we don't want */
4484         return;
4485       case H_GETTING_MOVES:
4486         /* Should not happen */
4487         DisplayError(_("Error gathering move list: extra board"), 0);
4488         ics_getting_history = H_FALSE;
4489         return;
4490     }
4491
4492    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4493                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4494                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4495      /* [HGM] We seem to have switched variant unexpectedly
4496       * Try to guess new variant from board size
4497       */
4498           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4499           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4500           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4501           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4502           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4503           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4504           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4505           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4506           /* Get a move list just to see the header, which
4507              will tell us whether this is really bug or zh */
4508           if (ics_getting_history == H_FALSE) {
4509             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4510             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4511             SendToICS(str);
4512           }
4513     }
4514
4515     /* Take action if this is the first board of a new game, or of a
4516        different game than is currently being displayed.  */
4517     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4518         relation == RELATION_ISOLATED_BOARD) {
4519
4520         /* Forget the old game and get the history (if any) of the new one */
4521         if (gameMode != BeginningOfGame) {
4522           Reset(TRUE, TRUE);
4523         }
4524         newGame = TRUE;
4525         if (appData.autoRaiseBoard) BoardToTop();
4526         prevMove = -3;
4527         if (gamenum == -1) {
4528             newGameMode = IcsIdle;
4529         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4530                    appData.getMoveList && !reqFlag) {
4531             /* Need to get game history */
4532             ics_getting_history = H_REQUESTED;
4533             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4534             SendToICS(str);
4535         }
4536
4537         /* Initially flip the board to have black on the bottom if playing
4538            black or if the ICS flip flag is set, but let the user change
4539            it with the Flip View button. */
4540         flipView = appData.autoFlipView ?
4541           (newGameMode == IcsPlayingBlack) || ics_flip :
4542           appData.flipView;
4543
4544         /* Done with values from previous mode; copy in new ones */
4545         gameMode = newGameMode;
4546         ModeHighlight();
4547         ics_gamenum = gamenum;
4548         if (gamenum == gs_gamenum) {
4549             int klen = strlen(gs_kind);
4550             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4551             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4552             gameInfo.event = StrSave(str);
4553         } else {
4554             gameInfo.event = StrSave("ICS game");
4555         }
4556         gameInfo.site = StrSave(appData.icsHost);
4557         gameInfo.date = PGNDate();
4558         gameInfo.round = StrSave("-");
4559         gameInfo.white = StrSave(white);
4560         gameInfo.black = StrSave(black);
4561         timeControl = basetime * 60 * 1000;
4562         timeControl_2 = 0;
4563         timeIncrement = increment * 1000;
4564         movesPerSession = 0;
4565         gameInfo.timeControl = TimeControlTagValue();
4566         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4567   if (appData.debugMode) {
4568     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4569     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4570     setbuf(debugFP, NULL);
4571   }
4572
4573         gameInfo.outOfBook = NULL;
4574
4575         /* Do we have the ratings? */
4576         if (strcmp(player1Name, white) == 0 &&
4577             strcmp(player2Name, black) == 0) {
4578             if (appData.debugMode)
4579               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4580                       player1Rating, player2Rating);
4581             gameInfo.whiteRating = player1Rating;
4582             gameInfo.blackRating = player2Rating;
4583         } else if (strcmp(player2Name, white) == 0 &&
4584                    strcmp(player1Name, black) == 0) {
4585             if (appData.debugMode)
4586               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4587                       player2Rating, player1Rating);
4588             gameInfo.whiteRating = player2Rating;
4589             gameInfo.blackRating = player1Rating;
4590         }
4591         player1Name[0] = player2Name[0] = NULLCHAR;
4592
4593         /* Silence shouts if requested */
4594         if (appData.quietPlay &&
4595             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4596             SendToICS(ics_prefix);
4597             SendToICS("set shout 0\n");
4598         }
4599     }
4600
4601     /* Deal with midgame name changes */
4602     if (!newGame) {
4603         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4604             if (gameInfo.white) free(gameInfo.white);
4605             gameInfo.white = StrSave(white);
4606         }
4607         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4608             if (gameInfo.black) free(gameInfo.black);
4609             gameInfo.black = StrSave(black);
4610         }
4611     }
4612
4613     /* Throw away game result if anything actually changes in examine mode */
4614     if (gameMode == IcsExamining && !newGame) {
4615         gameInfo.result = GameUnfinished;
4616         if (gameInfo.resultDetails != NULL) {
4617             free(gameInfo.resultDetails);
4618             gameInfo.resultDetails = NULL;
4619         }
4620     }
4621
4622     /* In pausing && IcsExamining mode, we ignore boards coming
4623        in if they are in a different variation than we are. */
4624     if (pauseExamInvalid) return;
4625     if (pausing && gameMode == IcsExamining) {
4626         if (moveNum <= pauseExamForwardMostMove) {
4627             pauseExamInvalid = TRUE;
4628             forwardMostMove = pauseExamForwardMostMove;
4629             return;
4630         }
4631     }
4632
4633   if (appData.debugMode) {
4634     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4635   }
4636     /* Parse the board */
4637     for (k = 0; k < ranks; k++) {
4638       for (j = 0; j < files; j++)
4639         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4640       if(gameInfo.holdingsWidth > 1) {
4641            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4642            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4643       }
4644     }
4645     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4646       board[5][BOARD_RGHT+1] = WhiteAngel;
4647       board[6][BOARD_RGHT+1] = WhiteMarshall;
4648       board[1][0] = BlackMarshall;
4649       board[2][0] = BlackAngel;
4650       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4651     }
4652     CopyBoard(boards[moveNum], board);
4653     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4654     if (moveNum == 0) {
4655         startedFromSetupPosition =
4656           !CompareBoards(board, initialPosition);
4657         if(startedFromSetupPosition)
4658             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4659     }
4660
4661     /* [HGM] Set castling rights. Take the outermost Rooks,
4662        to make it also work for FRC opening positions. Note that board12
4663        is really defective for later FRC positions, as it has no way to
4664        indicate which Rook can castle if they are on the same side of King.
4665        For the initial position we grant rights to the outermost Rooks,
4666        and remember thos rights, and we then copy them on positions
4667        later in an FRC game. This means WB might not recognize castlings with
4668        Rooks that have moved back to their original position as illegal,
4669        but in ICS mode that is not its job anyway.
4670     */
4671     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4672     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4673
4674         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4675             if(board[0][i] == WhiteRook) j = i;
4676         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4677         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4678             if(board[0][i] == WhiteRook) j = i;
4679         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4680         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4681             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4682         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4683         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4684             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4685         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4686
4687         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4688         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4689         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4690             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4691         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4692             if(board[BOARD_HEIGHT-1][k] == bKing)
4693                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4694         if(gameInfo.variant == VariantTwoKings) {
4695             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4696             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4697             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4698         }
4699     } else { int r;
4700         r = boards[moveNum][CASTLING][0] = initialRights[0];
4701         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4702         r = boards[moveNum][CASTLING][1] = initialRights[1];
4703         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4704         r = boards[moveNum][CASTLING][3] = initialRights[3];
4705         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4706         r = boards[moveNum][CASTLING][4] = initialRights[4];
4707         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4708         /* wildcastle kludge: always assume King has rights */
4709         r = boards[moveNum][CASTLING][2] = initialRights[2];
4710         r = boards[moveNum][CASTLING][5] = initialRights[5];
4711     }
4712     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4713     boards[moveNum][EP_STATUS] = EP_NONE;
4714     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4715     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4716     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4717
4718
4719     if (ics_getting_history == H_GOT_REQ_HEADER ||
4720         ics_getting_history == H_GOT_UNREQ_HEADER) {
4721         /* This was an initial position from a move list, not
4722            the current position */
4723         return;
4724     }
4725
4726     /* Update currentMove and known move number limits */
4727     newMove = newGame || moveNum > forwardMostMove;
4728
4729     if (newGame) {
4730         forwardMostMove = backwardMostMove = currentMove = moveNum;
4731         if (gameMode == IcsExamining && moveNum == 0) {
4732           /* Workaround for ICS limitation: we are not told the wild
4733              type when starting to examine a game.  But if we ask for
4734              the move list, the move list header will tell us */
4735             ics_getting_history = H_REQUESTED;
4736             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4737             SendToICS(str);
4738         }
4739     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4740                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4741 #if ZIPPY
4742         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4743         /* [HGM] applied this also to an engine that is silently watching        */
4744         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4745             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4746             gameInfo.variant == currentlyInitializedVariant) {
4747           takeback = forwardMostMove - moveNum;
4748           for (i = 0; i < takeback; i++) {
4749             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4750             SendToProgram("undo\n", &first);
4751           }
4752         }
4753 #endif
4754
4755         forwardMostMove = moveNum;
4756         if (!pausing || currentMove > forwardMostMove)
4757           currentMove = forwardMostMove;
4758     } else {
4759         /* New part of history that is not contiguous with old part */
4760         if (pausing && gameMode == IcsExamining) {
4761             pauseExamInvalid = TRUE;
4762             forwardMostMove = pauseExamForwardMostMove;
4763             return;
4764         }
4765         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4766 #if ZIPPY
4767             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4768                 // [HGM] when we will receive the move list we now request, it will be
4769                 // fed to the engine from the first move on. So if the engine is not
4770                 // in the initial position now, bring it there.
4771                 InitChessProgram(&first, 0);
4772             }
4773 #endif
4774             ics_getting_history = H_REQUESTED;
4775             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4776             SendToICS(str);
4777         }
4778         forwardMostMove = backwardMostMove = currentMove = moveNum;
4779     }
4780
4781     /* Update the clocks */
4782     if (strchr(elapsed_time, '.')) {
4783       /* Time is in ms */
4784       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4785       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4786     } else {
4787       /* Time is in seconds */
4788       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4789       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4790     }
4791
4792
4793 #if ZIPPY
4794     if (appData.zippyPlay && newGame &&
4795         gameMode != IcsObserving && gameMode != IcsIdle &&
4796         gameMode != IcsExamining)
4797       ZippyFirstBoard(moveNum, basetime, increment);
4798 #endif
4799
4800     /* Put the move on the move list, first converting
4801        to canonical algebraic form. */
4802     if (moveNum > 0) {
4803   if (appData.debugMode) {
4804     int f = forwardMostMove;
4805     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4806             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4807             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4808     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4809     fprintf(debugFP, "moveNum = %d\n", moveNum);
4810     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4811     setbuf(debugFP, NULL);
4812   }
4813         if (moveNum <= backwardMostMove) {
4814             /* We don't know what the board looked like before
4815                this move.  Punt. */
4816           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4817             strcat(parseList[moveNum - 1], " ");
4818             strcat(parseList[moveNum - 1], elapsed_time);
4819             moveList[moveNum - 1][0] = NULLCHAR;
4820         } else if (strcmp(move_str, "none") == 0) {
4821             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4822             /* Again, we don't know what the board looked like;
4823                this is really the start of the game. */
4824             parseList[moveNum - 1][0] = NULLCHAR;
4825             moveList[moveNum - 1][0] = NULLCHAR;
4826             backwardMostMove = moveNum;
4827             startedFromSetupPosition = TRUE;
4828             fromX = fromY = toX = toY = -1;
4829         } else {
4830           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4831           //                 So we parse the long-algebraic move string in stead of the SAN move
4832           int valid; char buf[MSG_SIZ], *prom;
4833
4834           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4835                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4836           // str looks something like "Q/a1-a2"; kill the slash
4837           if(str[1] == '/')
4838             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4839           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4840           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4841                 strcat(buf, prom); // long move lacks promo specification!
4842           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4843                 if(appData.debugMode)
4844                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4845                 safeStrCpy(move_str, buf, MSG_SIZ);
4846           }
4847           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4848                                 &fromX, &fromY, &toX, &toY, &promoChar)
4849                || ParseOneMove(buf, moveNum - 1, &moveType,
4850                                 &fromX, &fromY, &toX, &toY, &promoChar);
4851           // end of long SAN patch
4852           if (valid) {
4853             (void) CoordsToAlgebraic(boards[moveNum - 1],
4854                                      PosFlags(moveNum - 1),
4855                                      fromY, fromX, toY, toX, promoChar,
4856                                      parseList[moveNum-1]);
4857             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4858               case MT_NONE:
4859               case MT_STALEMATE:
4860               default:
4861                 break;
4862               case MT_CHECK:
4863                 if(!IS_SHOGI(gameInfo.variant))
4864                     strcat(parseList[moveNum - 1], "+");
4865                 break;
4866               case MT_CHECKMATE:
4867               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4868                 strcat(parseList[moveNum - 1], "#");
4869                 break;
4870             }
4871             strcat(parseList[moveNum - 1], " ");
4872             strcat(parseList[moveNum - 1], elapsed_time);
4873             /* currentMoveString is set as a side-effect of ParseOneMove */
4874             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4875             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4876             strcat(moveList[moveNum - 1], "\n");
4877
4878             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4879                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4880               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4881                 ChessSquare old, new = boards[moveNum][k][j];
4882                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4883                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4884                   if(old == new) continue;
4885                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4886                   else if(new == WhiteWazir || new == BlackWazir) {
4887                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4888                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4889                       else boards[moveNum][k][j] = old; // preserve type of Gold
4890                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4891                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4892               }
4893           } else {
4894             /* Move from ICS was illegal!?  Punt. */
4895             if (appData.debugMode) {
4896               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4897               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4898             }
4899             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4900             strcat(parseList[moveNum - 1], " ");
4901             strcat(parseList[moveNum - 1], elapsed_time);
4902             moveList[moveNum - 1][0] = NULLCHAR;
4903             fromX = fromY = toX = toY = -1;
4904           }
4905         }
4906   if (appData.debugMode) {
4907     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4908     setbuf(debugFP, NULL);
4909   }
4910
4911 #if ZIPPY
4912         /* Send move to chess program (BEFORE animating it). */
4913         if (appData.zippyPlay && !newGame && newMove &&
4914            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4915
4916             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4917                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4918                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4919                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4920                             move_str);
4921                     DisplayError(str, 0);
4922                 } else {
4923                     if (first.sendTime) {
4924                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4925                     }
4926                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4927                     if (firstMove && !bookHit) {
4928                         firstMove = FALSE;
4929                         if (first.useColors) {
4930                           SendToProgram(gameMode == IcsPlayingWhite ?
4931                                         "white\ngo\n" :
4932                                         "black\ngo\n", &first);
4933                         } else {
4934                           SendToProgram("go\n", &first);
4935                         }
4936                         first.maybeThinking = TRUE;
4937                     }
4938                 }
4939             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4940               if (moveList[moveNum - 1][0] == NULLCHAR) {
4941                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4942                 DisplayError(str, 0);
4943               } else {
4944                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4945                 SendMoveToProgram(moveNum - 1, &first);
4946               }
4947             }
4948         }
4949 #endif
4950     }
4951
4952     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4953         /* If move comes from a remote source, animate it.  If it
4954            isn't remote, it will have already been animated. */
4955         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4956             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4957         }
4958         if (!pausing && appData.highlightLastMove) {
4959             SetHighlights(fromX, fromY, toX, toY);
4960         }
4961     }
4962
4963     /* Start the clocks */
4964     whiteFlag = blackFlag = FALSE;
4965     appData.clockMode = !(basetime == 0 && increment == 0);
4966     if (ticking == 0) {
4967       ics_clock_paused = TRUE;
4968       StopClocks();
4969     } else if (ticking == 1) {
4970       ics_clock_paused = FALSE;
4971     }
4972     if (gameMode == IcsIdle ||
4973         relation == RELATION_OBSERVING_STATIC ||
4974         relation == RELATION_EXAMINING ||
4975         ics_clock_paused)
4976       DisplayBothClocks();
4977     else
4978       StartClocks();
4979
4980     /* Display opponents and material strengths */
4981     if (gameInfo.variant != VariantBughouse &&
4982         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4983         if (tinyLayout || smallLayout) {
4984             if(gameInfo.variant == VariantNormal)
4985               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4986                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4987                     basetime, increment);
4988             else
4989               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4990                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4991                     basetime, increment, (int) gameInfo.variant);
4992         } else {
4993             if(gameInfo.variant == VariantNormal)
4994               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4995                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4996                     basetime, increment);
4997             else
4998               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4999                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5000                     basetime, increment, VariantName(gameInfo.variant));
5001         }
5002         DisplayTitle(str);
5003   if (appData.debugMode) {
5004     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5005   }
5006     }
5007
5008
5009     /* Display the board */
5010     if (!pausing && !appData.noGUI) {
5011
5012       if (appData.premove)
5013           if (!gotPremove ||
5014              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5015              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5016               ClearPremoveHighlights();
5017
5018       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5019         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5020       DrawPosition(j, boards[currentMove]);
5021
5022       DisplayMove(moveNum - 1);
5023       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5024             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5025               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5026         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5027       }
5028     }
5029
5030     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5031 #if ZIPPY
5032     if(bookHit) { // [HGM] book: simulate book reply
5033         static char bookMove[MSG_SIZ]; // a bit generous?
5034
5035         programStats.nodes = programStats.depth = programStats.time =
5036         programStats.score = programStats.got_only_move = 0;
5037         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5038
5039         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5040         strcat(bookMove, bookHit);
5041         HandleMachineMove(bookMove, &first);
5042     }
5043 #endif
5044 }
5045
5046 void
5047 GetMoveListEvent ()
5048 {
5049     char buf[MSG_SIZ];
5050     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5051         ics_getting_history = H_REQUESTED;
5052         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5053         SendToICS(buf);
5054     }
5055 }
5056
5057 void
5058 SendToBoth (char *msg)
5059 {   // to make it easy to keep two engines in step in dual analysis
5060     SendToProgram(msg, &first);
5061     if(second.analyzing) SendToProgram(msg, &second);
5062 }
5063
5064 void
5065 AnalysisPeriodicEvent (int force)
5066 {
5067     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5068          && !force) || !appData.periodicUpdates)
5069       return;
5070
5071     /* Send . command to Crafty to collect stats */
5072     SendToBoth(".\n");
5073
5074     /* Don't send another until we get a response (this makes
5075        us stop sending to old Crafty's which don't understand
5076        the "." command (sending illegal cmds resets node count & time,
5077        which looks bad)) */
5078     programStats.ok_to_send = 0;
5079 }
5080
5081 void
5082 ics_update_width (int new_width)
5083 {
5084         ics_printf("set width %d\n", new_width);
5085 }
5086
5087 void
5088 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5089 {
5090     char buf[MSG_SIZ];
5091
5092     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5093         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5094             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5095             SendToProgram(buf, cps);
5096             return;
5097         }
5098         // null move in variant where engine does not understand it (for analysis purposes)
5099         SendBoard(cps, moveNum + 1); // send position after move in stead.
5100         return;
5101     }
5102     if (cps->useUsermove) {
5103       SendToProgram("usermove ", cps);
5104     }
5105     if (cps->useSAN) {
5106       char *space;
5107       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5108         int len = space - parseList[moveNum];
5109         memcpy(buf, parseList[moveNum], len);
5110         buf[len++] = '\n';
5111         buf[len] = NULLCHAR;
5112       } else {
5113         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5114       }
5115       SendToProgram(buf, cps);
5116     } else {
5117       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5118         AlphaRank(moveList[moveNum], 4);
5119         SendToProgram(moveList[moveNum], cps);
5120         AlphaRank(moveList[moveNum], 4); // and back
5121       } else
5122       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5123        * the engine. It would be nice to have a better way to identify castle
5124        * moves here. */
5125       if(appData.fischerCastling && cps->useOOCastle) {
5126         int fromX = moveList[moveNum][0] - AAA;
5127         int fromY = moveList[moveNum][1] - ONE;
5128         int toX = moveList[moveNum][2] - AAA;
5129         int toY = moveList[moveNum][3] - ONE;
5130         if((boards[moveNum][fromY][fromX] == WhiteKing
5131             && boards[moveNum][toY][toX] == WhiteRook)
5132            || (boards[moveNum][fromY][fromX] == BlackKing
5133                && boards[moveNum][toY][toX] == BlackRook)) {
5134           if(toX > fromX) SendToProgram("O-O\n", cps);
5135           else SendToProgram("O-O-O\n", cps);
5136         }
5137         else SendToProgram(moveList[moveNum], cps);
5138       } else
5139       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5140           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5141                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5142                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5143                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5144           SendToProgram(buf, cps);
5145       } else
5146       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5147         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5148           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5149           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5150                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5151         } else
5152           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5153                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5154         SendToProgram(buf, cps);
5155       }
5156       else SendToProgram(moveList[moveNum], cps);
5157       /* End of additions by Tord */
5158     }
5159
5160     /* [HGM] setting up the opening has brought engine in force mode! */
5161     /*       Send 'go' if we are in a mode where machine should play. */
5162     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5163         (gameMode == TwoMachinesPlay   ||
5164 #if ZIPPY
5165          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5166 #endif
5167          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5168         SendToProgram("go\n", cps);
5169   if (appData.debugMode) {
5170     fprintf(debugFP, "(extra)\n");
5171   }
5172     }
5173     setboardSpoiledMachineBlack = 0;
5174 }
5175
5176 void
5177 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5178 {
5179     char user_move[MSG_SIZ];
5180     char suffix[4];
5181
5182     if(gameInfo.variant == VariantSChess && promoChar) {
5183         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5184         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5185     } else suffix[0] = NULLCHAR;
5186
5187     switch (moveType) {
5188       default:
5189         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5190                 (int)moveType, fromX, fromY, toX, toY);
5191         DisplayError(user_move + strlen("say "), 0);
5192         break;
5193       case WhiteKingSideCastle:
5194       case BlackKingSideCastle:
5195       case WhiteQueenSideCastleWild:
5196       case BlackQueenSideCastleWild:
5197       /* PUSH Fabien */
5198       case WhiteHSideCastleFR:
5199       case BlackHSideCastleFR:
5200       /* POP Fabien */
5201         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5202         break;
5203       case WhiteQueenSideCastle:
5204       case BlackQueenSideCastle:
5205       case WhiteKingSideCastleWild:
5206       case BlackKingSideCastleWild:
5207       /* PUSH Fabien */
5208       case WhiteASideCastleFR:
5209       case BlackASideCastleFR:
5210       /* POP Fabien */
5211         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5212         break;
5213       case WhiteNonPromotion:
5214       case BlackNonPromotion:
5215         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5216         break;
5217       case WhitePromotion:
5218       case BlackPromotion:
5219         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5220            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5221           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5222                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5223                 PieceToChar(WhiteFerz));
5224         else if(gameInfo.variant == VariantGreat)
5225           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5227                 PieceToChar(WhiteMan));
5228         else
5229           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5231                 promoChar);
5232         break;
5233       case WhiteDrop:
5234       case BlackDrop:
5235       drop:
5236         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5237                  ToUpper(PieceToChar((ChessSquare) fromX)),
5238                  AAA + toX, ONE + toY);
5239         break;
5240       case IllegalMove:  /* could be a variant we don't quite understand */
5241         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5242       case NormalMove:
5243       case WhiteCapturesEnPassant:
5244       case BlackCapturesEnPassant:
5245         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5246                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5247         break;
5248     }
5249     SendToICS(user_move);
5250     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5251         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5252 }
5253
5254 void
5255 UploadGameEvent ()
5256 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5257     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5258     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5259     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5260       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5261       return;
5262     }
5263     if(gameMode != IcsExamining) { // is this ever not the case?
5264         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5265
5266         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5267           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5268         } else { // on FICS we must first go to general examine mode
5269           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5270         }
5271         if(gameInfo.variant != VariantNormal) {
5272             // try figure out wild number, as xboard names are not always valid on ICS
5273             for(i=1; i<=36; i++) {
5274               snprintf(buf, MSG_SIZ, "wild/%d", i);
5275                 if(StringToVariant(buf) == gameInfo.variant) break;
5276             }
5277             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5278             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5279             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5280         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5281         SendToICS(ics_prefix);
5282         SendToICS(buf);
5283         if(startedFromSetupPosition || backwardMostMove != 0) {
5284           fen = PositionToFEN(backwardMostMove, NULL, 1);
5285           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5286             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5287             SendToICS(buf);
5288           } else { // FICS: everything has to set by separate bsetup commands
5289             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5290             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5291             SendToICS(buf);
5292             if(!WhiteOnMove(backwardMostMove)) {
5293                 SendToICS("bsetup tomove black\n");
5294             }
5295             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5296             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5297             SendToICS(buf);
5298             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5299             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5300             SendToICS(buf);
5301             i = boards[backwardMostMove][EP_STATUS];
5302             if(i >= 0) { // set e.p.
5303               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5304                 SendToICS(buf);
5305             }
5306             bsetup++;
5307           }
5308         }
5309       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5310             SendToICS("bsetup done\n"); // switch to normal examining.
5311     }
5312     for(i = backwardMostMove; i<last; i++) {
5313         char buf[20];
5314         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5315         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5316             int len = strlen(moveList[i]);
5317             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5318             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5319         }
5320         SendToICS(buf);
5321     }
5322     SendToICS(ics_prefix);
5323     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5324 }
5325
5326 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5327
5328 void
5329 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5330 {
5331     if (rf == DROP_RANK) {
5332       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5333       sprintf(move, "%c@%c%c\n",
5334                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5335     } else {
5336         if (promoChar == 'x' || promoChar == NULLCHAR) {
5337           sprintf(move, "%c%c%c%c\n",
5338                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5339           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5340         } else {
5341             sprintf(move, "%c%c%c%c%c\n",
5342                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5343         }
5344     }
5345 }
5346
5347 void
5348 ProcessICSInitScript (FILE *f)
5349 {
5350     char buf[MSG_SIZ];
5351
5352     while (fgets(buf, MSG_SIZ, f)) {
5353         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5354     }
5355
5356     fclose(f);
5357 }
5358
5359
5360 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5361 int dragging;
5362 static ClickType lastClickType;
5363
5364 int
5365 Partner (ChessSquare *p)
5366 { // change piece into promotion partner if one shogi-promotes to the other
5367   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5368   ChessSquare partner;
5369   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5370   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5371   *p = partner;
5372   return 1;
5373 }
5374
5375 void
5376 Sweep (int step)
5377 {
5378     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5379     static int toggleFlag;
5380     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5381     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5382     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5383     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5384     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5385     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5386     do {
5387         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5388         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5389         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5390         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5391         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5392         if(!step) step = -1;
5393     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5394             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5395             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5396             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5397     if(toX >= 0) {
5398         int victim = boards[currentMove][toY][toX];
5399         boards[currentMove][toY][toX] = promoSweep;
5400         DrawPosition(FALSE, boards[currentMove]);
5401         boards[currentMove][toY][toX] = victim;
5402     } else
5403     ChangeDragPiece(promoSweep);
5404 }
5405
5406 int
5407 PromoScroll (int x, int y)
5408 {
5409   int step = 0;
5410
5411   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5412   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5413   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5414   if(!step) return FALSE;
5415   lastX = x; lastY = y;
5416   if((promoSweep < BlackPawn) == flipView) step = -step;
5417   if(step > 0) selectFlag = 1;
5418   if(!selectFlag) Sweep(step);
5419   return FALSE;
5420 }
5421
5422 void
5423 NextPiece (int step)
5424 {
5425     ChessSquare piece = boards[currentMove][toY][toX];
5426     do {
5427         pieceSweep -= step;
5428         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5429         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5430         if(!step) step = -1;
5431     } while(PieceToChar(pieceSweep) == '.');
5432     boards[currentMove][toY][toX] = pieceSweep;
5433     DrawPosition(FALSE, boards[currentMove]);
5434     boards[currentMove][toY][toX] = piece;
5435 }
5436 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5437 void
5438 AlphaRank (char *move, int n)
5439 {
5440 //    char *p = move, c; int x, y;
5441
5442     if (appData.debugMode) {
5443         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5444     }
5445
5446     if(move[1]=='*' &&
5447        move[2]>='0' && move[2]<='9' &&
5448        move[3]>='a' && move[3]<='x'    ) {
5449         move[1] = '@';
5450         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5451         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5452     } else
5453     if(move[0]>='0' && move[0]<='9' &&
5454        move[1]>='a' && move[1]<='x' &&
5455        move[2]>='0' && move[2]<='9' &&
5456        move[3]>='a' && move[3]<='x'    ) {
5457         /* input move, Shogi -> normal */
5458         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5459         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5460         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5461         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5462     } else
5463     if(move[1]=='@' &&
5464        move[3]>='0' && move[3]<='9' &&
5465        move[2]>='a' && move[2]<='x'    ) {
5466         move[1] = '*';
5467         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5468         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5469     } else
5470     if(
5471        move[0]>='a' && move[0]<='x' &&
5472        move[3]>='0' && move[3]<='9' &&
5473        move[2]>='a' && move[2]<='x'    ) {
5474          /* output move, normal -> Shogi */
5475         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5476         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5477         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5478         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5479         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5480     }
5481     if (appData.debugMode) {
5482         fprintf(debugFP, "   out = '%s'\n", move);
5483     }
5484 }
5485
5486 char yy_textstr[8000];
5487
5488 /* Parser for moves from gnuchess, ICS, or user typein box */
5489 Boolean
5490 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5491 {
5492     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5493
5494     switch (*moveType) {
5495       case WhitePromotion:
5496       case BlackPromotion:
5497       case WhiteNonPromotion:
5498       case BlackNonPromotion:
5499       case NormalMove:
5500       case FirstLeg:
5501       case WhiteCapturesEnPassant:
5502       case BlackCapturesEnPassant:
5503       case WhiteKingSideCastle:
5504       case WhiteQueenSideCastle:
5505       case BlackKingSideCastle:
5506       case BlackQueenSideCastle:
5507       case WhiteKingSideCastleWild:
5508       case WhiteQueenSideCastleWild:
5509       case BlackKingSideCastleWild:
5510       case BlackQueenSideCastleWild:
5511       /* Code added by Tord: */
5512       case WhiteHSideCastleFR:
5513       case WhiteASideCastleFR:
5514       case BlackHSideCastleFR:
5515       case BlackASideCastleFR:
5516       /* End of code added by Tord */
5517       case IllegalMove:         /* bug or odd chess variant */
5518         *fromX = currentMoveString[0] - AAA;
5519         *fromY = currentMoveString[1] - ONE;
5520         *toX = currentMoveString[2] - AAA;
5521         *toY = currentMoveString[3] - ONE;
5522         *promoChar = currentMoveString[4];
5523         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5524             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5525     if (appData.debugMode) {
5526         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5527     }
5528             *fromX = *fromY = *toX = *toY = 0;
5529             return FALSE;
5530         }
5531         if (appData.testLegality) {
5532           return (*moveType != IllegalMove);
5533         } else {
5534           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5535                          // [HGM] lion: if this is a double move we are less critical
5536                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5537         }
5538
5539       case WhiteDrop:
5540       case BlackDrop:
5541         *fromX = *moveType == WhiteDrop ?
5542           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5543           (int) CharToPiece(ToLower(currentMoveString[0]));
5544         *fromY = DROP_RANK;
5545         *toX = currentMoveString[2] - AAA;
5546         *toY = currentMoveString[3] - ONE;
5547         *promoChar = NULLCHAR;
5548         return TRUE;
5549
5550       case AmbiguousMove:
5551       case ImpossibleMove:
5552       case EndOfFile:
5553       case ElapsedTime:
5554       case Comment:
5555       case PGNTag:
5556       case NAG:
5557       case WhiteWins:
5558       case BlackWins:
5559       case GameIsDrawn:
5560       default:
5561     if (appData.debugMode) {
5562         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5563     }
5564         /* bug? */
5565         *fromX = *fromY = *toX = *toY = 0;
5566         *promoChar = NULLCHAR;
5567         return FALSE;
5568     }
5569 }
5570
5571 Boolean pushed = FALSE;
5572 char *lastParseAttempt;
5573
5574 void
5575 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5576 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5577   int fromX, fromY, toX, toY; char promoChar;
5578   ChessMove moveType;
5579   Boolean valid;
5580   int nr = 0;
5581
5582   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5583   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5584     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5585     pushed = TRUE;
5586   }
5587   endPV = forwardMostMove;
5588   do {
5589     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5590     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5591     lastParseAttempt = pv;
5592     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5593     if(!valid && nr == 0 &&
5594        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5595         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5596         // Hande case where played move is different from leading PV move
5597         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5598         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5599         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5600         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5601           endPV += 2; // if position different, keep this
5602           moveList[endPV-1][0] = fromX + AAA;
5603           moveList[endPV-1][1] = fromY + ONE;
5604           moveList[endPV-1][2] = toX + AAA;
5605           moveList[endPV-1][3] = toY + ONE;
5606           parseList[endPV-1][0] = NULLCHAR;
5607           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5608         }
5609       }
5610     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5611     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5612     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5613     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5614         valid++; // allow comments in PV
5615         continue;
5616     }
5617     nr++;
5618     if(endPV+1 > framePtr) break; // no space, truncate
5619     if(!valid) break;
5620     endPV++;
5621     CopyBoard(boards[endPV], boards[endPV-1]);
5622     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5623     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5624     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5625     CoordsToAlgebraic(boards[endPV - 1],
5626                              PosFlags(endPV - 1),
5627                              fromY, fromX, toY, toX, promoChar,
5628                              parseList[endPV - 1]);
5629   } while(valid);
5630   if(atEnd == 2) return; // used hidden, for PV conversion
5631   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5632   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5633   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5634                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5635   DrawPosition(TRUE, boards[currentMove]);
5636 }
5637
5638 int
5639 MultiPV (ChessProgramState *cps)
5640 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5641         int i;
5642         for(i=0; i<cps->nrOptions; i++)
5643             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5644                 return i;
5645         return -1;
5646 }
5647
5648 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5649
5650 Boolean
5651 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5652 {
5653         int startPV, multi, lineStart, origIndex = index;
5654         char *p, buf2[MSG_SIZ];
5655         ChessProgramState *cps = (pane ? &second : &first);
5656
5657         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5658         lastX = x; lastY = y;
5659         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5660         lineStart = startPV = index;
5661         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5662         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5663         index = startPV;
5664         do{ while(buf[index] && buf[index] != '\n') index++;
5665         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5666         buf[index] = 0;
5667         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5668                 int n = cps->option[multi].value;
5669                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5670                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5671                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5672                 cps->option[multi].value = n;
5673                 *start = *end = 0;
5674                 return FALSE;
5675         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5676                 ExcludeClick(origIndex - lineStart);
5677                 return FALSE;
5678         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5679                 Collapse(origIndex - lineStart);
5680                 return FALSE;
5681         }
5682         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5683         *start = startPV; *end = index-1;
5684         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5685         return TRUE;
5686 }
5687
5688 char *
5689 PvToSAN (char *pv)
5690 {
5691         static char buf[10*MSG_SIZ];
5692         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5693         *buf = NULLCHAR;
5694         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5695         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5696         for(i = forwardMostMove; i<endPV; i++){
5697             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5698             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5699             k += strlen(buf+k);
5700         }
5701         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5702         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5703         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5704         endPV = savedEnd;
5705         return buf;
5706 }
5707
5708 Boolean
5709 LoadPV (int x, int y)
5710 { // called on right mouse click to load PV
5711   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5712   lastX = x; lastY = y;
5713   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5714   extendGame = FALSE;
5715   return TRUE;
5716 }
5717
5718 void
5719 UnLoadPV ()
5720 {
5721   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5722   if(endPV < 0) return;
5723   if(appData.autoCopyPV) CopyFENToClipboard();
5724   endPV = -1;
5725   if(extendGame && currentMove > forwardMostMove) {
5726         Boolean saveAnimate = appData.animate;
5727         if(pushed) {
5728             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5729                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5730             } else storedGames--; // abandon shelved tail of original game
5731         }
5732         pushed = FALSE;
5733         forwardMostMove = currentMove;
5734         currentMove = oldFMM;
5735         appData.animate = FALSE;
5736         ToNrEvent(forwardMostMove);
5737         appData.animate = saveAnimate;
5738   }
5739   currentMove = forwardMostMove;
5740   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5741   ClearPremoveHighlights();
5742   DrawPosition(TRUE, boards[currentMove]);
5743 }
5744
5745 void
5746 MovePV (int x, int y, int h)
5747 { // step through PV based on mouse coordinates (called on mouse move)
5748   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5749
5750   // we must somehow check if right button is still down (might be released off board!)
5751   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5752   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5753   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5754   if(!step) return;
5755   lastX = x; lastY = y;
5756
5757   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5758   if(endPV < 0) return;
5759   if(y < margin) step = 1; else
5760   if(y > h - margin) step = -1;
5761   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5762   currentMove += step;
5763   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5764   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5765                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5766   DrawPosition(FALSE, boards[currentMove]);
5767 }
5768
5769
5770 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5771 // All positions will have equal probability, but the current method will not provide a unique
5772 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5773 #define DARK 1
5774 #define LITE 2
5775 #define ANY 3
5776
5777 int squaresLeft[4];
5778 int piecesLeft[(int)BlackPawn];
5779 int seed, nrOfShuffles;
5780
5781 void
5782 GetPositionNumber ()
5783 {       // sets global variable seed
5784         int i;
5785
5786         seed = appData.defaultFrcPosition;
5787         if(seed < 0) { // randomize based on time for negative FRC position numbers
5788                 for(i=0; i<50; i++) seed += random();
5789                 seed = random() ^ random() >> 8 ^ random() << 8;
5790                 if(seed<0) seed = -seed;
5791         }
5792 }
5793
5794 int
5795 put (Board board, int pieceType, int rank, int n, int shade)
5796 // put the piece on the (n-1)-th empty squares of the given shade
5797 {
5798         int i;
5799
5800         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5801                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5802                         board[rank][i] = (ChessSquare) pieceType;
5803                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5804                         squaresLeft[ANY]--;
5805                         piecesLeft[pieceType]--;
5806                         return i;
5807                 }
5808         }
5809         return -1;
5810 }
5811
5812
5813 void
5814 AddOnePiece (Board board, int pieceType, int rank, int shade)
5815 // calculate where the next piece goes, (any empty square), and put it there
5816 {
5817         int i;
5818
5819         i = seed % squaresLeft[shade];
5820         nrOfShuffles *= squaresLeft[shade];
5821         seed /= squaresLeft[shade];
5822         put(board, pieceType, rank, i, shade);
5823 }
5824
5825 void
5826 AddTwoPieces (Board board, int pieceType, int rank)
5827 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5828 {
5829         int i, n=squaresLeft[ANY], j=n-1, k;
5830
5831         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5832         i = seed % k;  // pick one
5833         nrOfShuffles *= k;
5834         seed /= k;
5835         while(i >= j) i -= j--;
5836         j = n - 1 - j; i += j;
5837         put(board, pieceType, rank, j, ANY);
5838         put(board, pieceType, rank, i, ANY);
5839 }
5840
5841 void
5842 SetUpShuffle (Board board, int number)
5843 {
5844         int i, p, first=1;
5845
5846         GetPositionNumber(); nrOfShuffles = 1;
5847
5848         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5849         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5850         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5851
5852         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5853
5854         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5855             p = (int) board[0][i];
5856             if(p < (int) BlackPawn) piecesLeft[p] ++;
5857             board[0][i] = EmptySquare;
5858         }
5859
5860         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5861             // shuffles restricted to allow normal castling put KRR first
5862             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5863                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5864             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5865                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5866             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5867                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5868             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5869                 put(board, WhiteRook, 0, 0, ANY);
5870             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5871         }
5872
5873         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5874             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5875             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5876                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5877                 while(piecesLeft[p] >= 2) {
5878                     AddOnePiece(board, p, 0, LITE);
5879                     AddOnePiece(board, p, 0, DARK);
5880                 }
5881                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5882             }
5883
5884         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5885             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5886             // but we leave King and Rooks for last, to possibly obey FRC restriction
5887             if(p == (int)WhiteRook) continue;
5888             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5889             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5890         }
5891
5892         // now everything is placed, except perhaps King (Unicorn) and Rooks
5893
5894         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5895             // Last King gets castling rights
5896             while(piecesLeft[(int)WhiteUnicorn]) {
5897                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5898                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5899             }
5900
5901             while(piecesLeft[(int)WhiteKing]) {
5902                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5903                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5904             }
5905
5906
5907         } else {
5908             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5909             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5910         }
5911
5912         // Only Rooks can be left; simply place them all
5913         while(piecesLeft[(int)WhiteRook]) {
5914                 i = put(board, WhiteRook, 0, 0, ANY);
5915                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5916                         if(first) {
5917                                 first=0;
5918                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5919                         }
5920                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5921                 }
5922         }
5923         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5924             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5925         }
5926
5927         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5928 }
5929
5930 int
5931 SetCharTable (char *table, const char * map)
5932 /* [HGM] moved here from winboard.c because of its general usefulness */
5933 /*       Basically a safe strcpy that uses the last character as King */
5934 {
5935     int result = FALSE; int NrPieces;
5936
5937     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5938                     && NrPieces >= 12 && !(NrPieces&1)) {
5939         int i; /* [HGM] Accept even length from 12 to 34 */
5940
5941         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5942         for( i=0; i<NrPieces/2-1; i++ ) {
5943             table[i] = map[i];
5944             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5945         }
5946         table[(int) WhiteKing]  = map[NrPieces/2-1];
5947         table[(int) BlackKing]  = map[NrPieces-1];
5948
5949         result = TRUE;
5950     }
5951
5952     return result;
5953 }
5954
5955 void
5956 Prelude (Board board)
5957 {       // [HGM] superchess: random selection of exo-pieces
5958         int i, j, k; ChessSquare p;
5959         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5960
5961         GetPositionNumber(); // use FRC position number
5962
5963         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5964             SetCharTable(pieceToChar, appData.pieceToCharTable);
5965             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5966                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5967         }
5968
5969         j = seed%4;                 seed /= 4;
5970         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5971         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5972         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5973         j = seed%3 + (seed%3 >= j); seed /= 3;
5974         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5975         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5976         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5977         j = seed%3;                 seed /= 3;
5978         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5979         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5980         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5981         j = seed%2 + (seed%2 >= j); seed /= 2;
5982         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5983         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5984         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5985         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5986         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5987         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5988         put(board, exoPieces[0],    0, 0, ANY);
5989         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5990 }
5991
5992 void
5993 InitPosition (int redraw)
5994 {
5995     ChessSquare (* pieces)[BOARD_FILES];
5996     int i, j, pawnRow=1, pieceRows=1, overrule,
5997     oldx = gameInfo.boardWidth,
5998     oldy = gameInfo.boardHeight,
5999     oldh = gameInfo.holdingsWidth;
6000     static int oldv;
6001
6002     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6003
6004     /* [AS] Initialize pv info list [HGM] and game status */
6005     {
6006         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6007             pvInfoList[i].depth = 0;
6008             boards[i][EP_STATUS] = EP_NONE;
6009             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6010         }
6011
6012         initialRulePlies = 0; /* 50-move counter start */
6013
6014         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6015         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6016     }
6017
6018
6019     /* [HGM] logic here is completely changed. In stead of full positions */
6020     /* the initialized data only consist of the two backranks. The switch */
6021     /* selects which one we will use, which is than copied to the Board   */
6022     /* initialPosition, which for the rest is initialized by Pawns and    */
6023     /* empty squares. This initial position is then copied to boards[0],  */
6024     /* possibly after shuffling, so that it remains available.            */
6025
6026     gameInfo.holdingsWidth = 0; /* default board sizes */
6027     gameInfo.boardWidth    = 8;
6028     gameInfo.boardHeight   = 8;
6029     gameInfo.holdingsSize  = 0;
6030     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6031     for(i=0; i<BOARD_FILES-2; i++)
6032       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6033     initialPosition[EP_STATUS] = EP_NONE;
6034     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6035     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6036          SetCharTable(pieceNickName, appData.pieceNickNames);
6037     else SetCharTable(pieceNickName, "............");
6038     pieces = FIDEArray;
6039
6040     switch (gameInfo.variant) {
6041     case VariantFischeRandom:
6042       shuffleOpenings = TRUE;
6043       appData.fischerCastling = TRUE;
6044     default:
6045       break;
6046     case VariantShatranj:
6047       pieces = ShatranjArray;
6048       nrCastlingRights = 0;
6049       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6050       break;
6051     case VariantMakruk:
6052       pieces = makrukArray;
6053       nrCastlingRights = 0;
6054       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6055       break;
6056     case VariantASEAN:
6057       pieces = aseanArray;
6058       nrCastlingRights = 0;
6059       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6060       break;
6061     case VariantTwoKings:
6062       pieces = twoKingsArray;
6063       break;
6064     case VariantGrand:
6065       pieces = GrandArray;
6066       nrCastlingRights = 0;
6067       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6068       gameInfo.boardWidth = 10;
6069       gameInfo.boardHeight = 10;
6070       gameInfo.holdingsSize = 7;
6071       break;
6072     case VariantCapaRandom:
6073       shuffleOpenings = TRUE;
6074       appData.fischerCastling = TRUE;
6075     case VariantCapablanca:
6076       pieces = CapablancaArray;
6077       gameInfo.boardWidth = 10;
6078       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6079       break;
6080     case VariantGothic:
6081       pieces = GothicArray;
6082       gameInfo.boardWidth = 10;
6083       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6084       break;
6085     case VariantSChess:
6086       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6087       gameInfo.holdingsSize = 7;
6088       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6089       break;
6090     case VariantJanus:
6091       pieces = JanusArray;
6092       gameInfo.boardWidth = 10;
6093       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6094       nrCastlingRights = 6;
6095         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6096         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6097         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6098         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6099         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6100         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6101       break;
6102     case VariantFalcon:
6103       pieces = FalconArray;
6104       gameInfo.boardWidth = 10;
6105       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6106       break;
6107     case VariantXiangqi:
6108       pieces = XiangqiArray;
6109       gameInfo.boardWidth  = 9;
6110       gameInfo.boardHeight = 10;
6111       nrCastlingRights = 0;
6112       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6113       break;
6114     case VariantShogi:
6115       pieces = ShogiArray;
6116       gameInfo.boardWidth  = 9;
6117       gameInfo.boardHeight = 9;
6118       gameInfo.holdingsSize = 7;
6119       nrCastlingRights = 0;
6120       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6121       break;
6122     case VariantChu:
6123       pieces = ChuArray; pieceRows = 3;
6124       gameInfo.boardWidth  = 12;
6125       gameInfo.boardHeight = 12;
6126       nrCastlingRights = 0;
6127       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6128                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6129       break;
6130     case VariantCourier:
6131       pieces = CourierArray;
6132       gameInfo.boardWidth  = 12;
6133       nrCastlingRights = 0;
6134       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6135       break;
6136     case VariantKnightmate:
6137       pieces = KnightmateArray;
6138       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6139       break;
6140     case VariantSpartan:
6141       pieces = SpartanArray;
6142       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6143       break;
6144     case VariantLion:
6145       pieces = lionArray;
6146       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6147       break;
6148     case VariantChuChess:
6149       pieces = ChuChessArray;
6150       gameInfo.boardWidth = 10;
6151       gameInfo.boardHeight = 10;
6152       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6153       break;
6154     case VariantFairy:
6155       pieces = fairyArray;
6156       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6157       break;
6158     case VariantGreat:
6159       pieces = GreatArray;
6160       gameInfo.boardWidth = 10;
6161       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6162       gameInfo.holdingsSize = 8;
6163       break;
6164     case VariantSuper:
6165       pieces = FIDEArray;
6166       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6167       gameInfo.holdingsSize = 8;
6168       startedFromSetupPosition = TRUE;
6169       break;
6170     case VariantCrazyhouse:
6171     case VariantBughouse:
6172       pieces = FIDEArray;
6173       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6174       gameInfo.holdingsSize = 5;
6175       break;
6176     case VariantWildCastle:
6177       pieces = FIDEArray;
6178       /* !!?shuffle with kings guaranteed to be on d or e file */
6179       shuffleOpenings = 1;
6180       break;
6181     case VariantNoCastle:
6182       pieces = FIDEArray;
6183       nrCastlingRights = 0;
6184       /* !!?unconstrained back-rank shuffle */
6185       shuffleOpenings = 1;
6186       break;
6187     }
6188
6189     overrule = 0;
6190     if(appData.NrFiles >= 0) {
6191         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6192         gameInfo.boardWidth = appData.NrFiles;
6193     }
6194     if(appData.NrRanks >= 0) {
6195         gameInfo.boardHeight = appData.NrRanks;
6196     }
6197     if(appData.holdingsSize >= 0) {
6198         i = appData.holdingsSize;
6199         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6200         gameInfo.holdingsSize = i;
6201     }
6202     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6203     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6204         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6205
6206     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6207     if(pawnRow < 1) pawnRow = 1;
6208     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6209        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6210     if(gameInfo.variant == VariantChu) pawnRow = 3;
6211
6212     /* User pieceToChar list overrules defaults */
6213     if(appData.pieceToCharTable != NULL)
6214         SetCharTable(pieceToChar, appData.pieceToCharTable);
6215
6216     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6217
6218         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6219             s = (ChessSquare) 0; /* account holding counts in guard band */
6220         for( i=0; i<BOARD_HEIGHT; i++ )
6221             initialPosition[i][j] = s;
6222
6223         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6224         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6225         initialPosition[pawnRow][j] = WhitePawn;
6226         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6227         if(gameInfo.variant == VariantXiangqi) {
6228             if(j&1) {
6229                 initialPosition[pawnRow][j] =
6230                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6231                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6232                    initialPosition[2][j] = WhiteCannon;
6233                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6234                 }
6235             }
6236         }
6237         if(gameInfo.variant == VariantChu) {
6238              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6239                initialPosition[pawnRow+1][j] = WhiteCobra,
6240                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6241              for(i=1; i<pieceRows; i++) {
6242                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6243                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6244              }
6245         }
6246         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6247             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6248                initialPosition[0][j] = WhiteRook;
6249                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6250             }
6251         }
6252         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6253     }
6254     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6255     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6256
6257             j=BOARD_LEFT+1;
6258             initialPosition[1][j] = WhiteBishop;
6259             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6260             j=BOARD_RGHT-2;
6261             initialPosition[1][j] = WhiteRook;
6262             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6263     }
6264
6265     if( nrCastlingRights == -1) {
6266         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6267         /*       This sets default castling rights from none to normal corners   */
6268         /* Variants with other castling rights must set them themselves above    */
6269         nrCastlingRights = 6;
6270
6271         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6272         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6273         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6274         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6275         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6276         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6277      }
6278
6279      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6280      if(gameInfo.variant == VariantGreat) { // promotion commoners
6281         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6282         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6283         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6284         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6285      }
6286      if( gameInfo.variant == VariantSChess ) {
6287       initialPosition[1][0] = BlackMarshall;
6288       initialPosition[2][0] = BlackAngel;
6289       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6290       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6291       initialPosition[1][1] = initialPosition[2][1] =
6292       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6293      }
6294   if (appData.debugMode) {
6295     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6296   }
6297     if(shuffleOpenings) {
6298         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6299         startedFromSetupPosition = TRUE;
6300     }
6301     if(startedFromPositionFile) {
6302       /* [HGM] loadPos: use PositionFile for every new game */
6303       CopyBoard(initialPosition, filePosition);
6304       for(i=0; i<nrCastlingRights; i++)
6305           initialRights[i] = filePosition[CASTLING][i];
6306       startedFromSetupPosition = TRUE;
6307     }
6308
6309     CopyBoard(boards[0], initialPosition);
6310
6311     if(oldx != gameInfo.boardWidth ||
6312        oldy != gameInfo.boardHeight ||
6313        oldv != gameInfo.variant ||
6314        oldh != gameInfo.holdingsWidth
6315                                          )
6316             InitDrawingSizes(-2 ,0);
6317
6318     oldv = gameInfo.variant;
6319     if (redraw)
6320       DrawPosition(TRUE, boards[currentMove]);
6321 }
6322
6323 void
6324 SendBoard (ChessProgramState *cps, int moveNum)
6325 {
6326     char message[MSG_SIZ];
6327
6328     if (cps->useSetboard) {
6329       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6330       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6331       SendToProgram(message, cps);
6332       free(fen);
6333
6334     } else {
6335       ChessSquare *bp;
6336       int i, j, left=0, right=BOARD_WIDTH;
6337       /* Kludge to set black to move, avoiding the troublesome and now
6338        * deprecated "black" command.
6339        */
6340       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6341         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6342
6343       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6344
6345       SendToProgram("edit\n", cps);
6346       SendToProgram("#\n", cps);
6347       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6348         bp = &boards[moveNum][i][left];
6349         for (j = left; j < right; j++, bp++) {
6350           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6351           if ((int) *bp < (int) BlackPawn) {
6352             if(j == BOARD_RGHT+1)
6353                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6354             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6355             if(message[0] == '+' || message[0] == '~') {
6356               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6357                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6358                         AAA + j, ONE + i);
6359             }
6360             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6361                 message[1] = BOARD_RGHT   - 1 - j + '1';
6362                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6363             }
6364             SendToProgram(message, cps);
6365           }
6366         }
6367       }
6368
6369       SendToProgram("c\n", cps);
6370       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6371         bp = &boards[moveNum][i][left];
6372         for (j = left; j < right; j++, bp++) {
6373           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6374           if (((int) *bp != (int) EmptySquare)
6375               && ((int) *bp >= (int) BlackPawn)) {
6376             if(j == BOARD_LEFT-2)
6377                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6378             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6379                     AAA + j, ONE + i);
6380             if(message[0] == '+' || message[0] == '~') {
6381               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6382                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6383                         AAA + j, ONE + i);
6384             }
6385             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6386                 message[1] = BOARD_RGHT   - 1 - j + '1';
6387                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6388             }
6389             SendToProgram(message, cps);
6390           }
6391         }
6392       }
6393
6394       SendToProgram(".\n", cps);
6395     }
6396     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6397 }
6398
6399 char exclusionHeader[MSG_SIZ];
6400 int exCnt, excludePtr;
6401 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6402 static Exclusion excluTab[200];
6403 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6404
6405 static void
6406 WriteMap (int s)
6407 {
6408     int j;
6409     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6410     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6411 }
6412
6413 static void
6414 ClearMap ()
6415 {
6416     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6417     excludePtr = 24; exCnt = 0;
6418     WriteMap(0);
6419 }
6420
6421 static void
6422 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6423 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6424     char buf[2*MOVE_LEN], *p;
6425     Exclusion *e = excluTab;
6426     int i;
6427     for(i=0; i<exCnt; i++)
6428         if(e[i].ff == fromX && e[i].fr == fromY &&
6429            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6430     if(i == exCnt) { // was not in exclude list; add it
6431         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6432         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6433             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6434             return; // abort
6435         }
6436         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6437         excludePtr++; e[i].mark = excludePtr++;
6438         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6439         exCnt++;
6440     }
6441     exclusionHeader[e[i].mark] = state;
6442 }
6443
6444 static int
6445 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6446 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6447     char buf[MSG_SIZ];
6448     int j, k;
6449     ChessMove moveType;
6450     if((signed char)promoChar == -1) { // kludge to indicate best move
6451         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6452             return 1; // if unparsable, abort
6453     }
6454     // update exclusion map (resolving toggle by consulting existing state)
6455     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6456     j = k%8; k >>= 3;
6457     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6458     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6459          excludeMap[k] |=   1<<j;
6460     else excludeMap[k] &= ~(1<<j);
6461     // update header
6462     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6463     // inform engine
6464     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6465     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6466     SendToBoth(buf);
6467     return (state == '+');
6468 }
6469
6470 static void
6471 ExcludeClick (int index)
6472 {
6473     int i, j;
6474     Exclusion *e = excluTab;
6475     if(index < 25) { // none, best or tail clicked
6476         if(index < 13) { // none: include all
6477             WriteMap(0); // clear map
6478             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6479             SendToBoth("include all\n"); // and inform engine
6480         } else if(index > 18) { // tail
6481             if(exclusionHeader[19] == '-') { // tail was excluded
6482                 SendToBoth("include all\n");
6483                 WriteMap(0); // clear map completely
6484                 // now re-exclude selected moves
6485                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6486                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6487             } else { // tail was included or in mixed state
6488                 SendToBoth("exclude all\n");
6489                 WriteMap(0xFF); // fill map completely
6490                 // now re-include selected moves
6491                 j = 0; // count them
6492                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6493                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6494                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6495             }
6496         } else { // best
6497             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6498         }
6499     } else {
6500         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6501             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6502             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6503             break;
6504         }
6505     }
6506 }
6507
6508 ChessSquare
6509 DefaultPromoChoice (int white)
6510 {
6511     ChessSquare result;
6512     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6513        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6514         result = WhiteFerz; // no choice
6515     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6516         result= WhiteKing; // in Suicide Q is the last thing we want
6517     else if(gameInfo.variant == VariantSpartan)
6518         result = white ? WhiteQueen : WhiteAngel;
6519     else result = WhiteQueen;
6520     if(!white) result = WHITE_TO_BLACK result;
6521     return result;
6522 }
6523
6524 static int autoQueen; // [HGM] oneclick
6525
6526 int
6527 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6528 {
6529     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6530     /* [HGM] add Shogi promotions */
6531     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6532     ChessSquare piece, partner;
6533     ChessMove moveType;
6534     Boolean premove;
6535
6536     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6537     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6538
6539     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6540       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6541         return FALSE;
6542
6543     piece = boards[currentMove][fromY][fromX];
6544     if(gameInfo.variant == VariantChu) {
6545         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6546         promotionZoneSize = BOARD_HEIGHT/3;
6547         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6548     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6549         promotionZoneSize = BOARD_HEIGHT/3;
6550         highestPromotingPiece = (int)WhiteAlfil;
6551     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6552         promotionZoneSize = 3;
6553     }
6554
6555     // Treat Lance as Pawn when it is not representing Amazon or Lance
6556     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6557         if(piece == WhiteLance) piece = WhitePawn; else
6558         if(piece == BlackLance) piece = BlackPawn;
6559     }
6560
6561     // next weed out all moves that do not touch the promotion zone at all
6562     if((int)piece >= BlackPawn) {
6563         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6564              return FALSE;
6565         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6566         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6567     } else {
6568         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6569            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6570         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6571              return FALSE;
6572     }
6573
6574     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6575
6576     // weed out mandatory Shogi promotions
6577     if(gameInfo.variant == VariantShogi) {
6578         if(piece >= BlackPawn) {
6579             if(toY == 0 && piece == BlackPawn ||
6580                toY == 0 && piece == BlackQueen ||
6581                toY <= 1 && piece == BlackKnight) {
6582                 *promoChoice = '+';
6583                 return FALSE;
6584             }
6585         } else {
6586             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6587                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6588                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6589                 *promoChoice = '+';
6590                 return FALSE;
6591             }
6592         }
6593     }
6594
6595     // weed out obviously illegal Pawn moves
6596     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6597         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6598         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6599         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6600         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6601         // note we are not allowed to test for valid (non-)capture, due to premove
6602     }
6603
6604     // we either have a choice what to promote to, or (in Shogi) whether to promote
6605     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6606        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6607         ChessSquare p=BlackFerz;  // no choice
6608         while(p < EmptySquare) {  //but make sure we use piece that exists
6609             *promoChoice = PieceToChar(p++);
6610             if(*promoChoice != '.') break;
6611         }
6612         return FALSE;
6613     }
6614     // no sense asking what we must promote to if it is going to explode...
6615     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6616         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6617         return FALSE;
6618     }
6619     // give caller the default choice even if we will not make it
6620     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6621     partner = piece; // pieces can promote if the pieceToCharTable says so
6622     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6623     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6624     if(        sweepSelect && gameInfo.variant != VariantGreat
6625                            && gameInfo.variant != VariantGrand
6626                            && gameInfo.variant != VariantSuper) return FALSE;
6627     if(autoQueen) return FALSE; // predetermined
6628
6629     // suppress promotion popup on illegal moves that are not premoves
6630     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6631               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6632     if(appData.testLegality && !premove) {
6633         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6634                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6635         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6636         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6637             return FALSE;
6638     }
6639
6640     return TRUE;
6641 }
6642
6643 int
6644 InPalace (int row, int column)
6645 {   /* [HGM] for Xiangqi */
6646     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6647          column < (BOARD_WIDTH + 4)/2 &&
6648          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6649     return FALSE;
6650 }
6651
6652 int
6653 PieceForSquare (int x, int y)
6654 {
6655   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6656      return -1;
6657   else
6658      return boards[currentMove][y][x];
6659 }
6660
6661 int
6662 OKToStartUserMove (int x, int y)
6663 {
6664     ChessSquare from_piece;
6665     int white_piece;
6666
6667     if (matchMode) return FALSE;
6668     if (gameMode == EditPosition) return TRUE;
6669
6670     if (x >= 0 && y >= 0)
6671       from_piece = boards[currentMove][y][x];
6672     else
6673       from_piece = EmptySquare;
6674
6675     if (from_piece == EmptySquare) return FALSE;
6676
6677     white_piece = (int)from_piece >= (int)WhitePawn &&
6678       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6679
6680     switch (gameMode) {
6681       case AnalyzeFile:
6682       case TwoMachinesPlay:
6683       case EndOfGame:
6684         return FALSE;
6685
6686       case IcsObserving:
6687       case IcsIdle:
6688         return FALSE;
6689
6690       case MachinePlaysWhite:
6691       case IcsPlayingBlack:
6692         if (appData.zippyPlay) return FALSE;
6693         if (white_piece) {
6694             DisplayMoveError(_("You are playing Black"));
6695             return FALSE;
6696         }
6697         break;
6698
6699       case MachinePlaysBlack:
6700       case IcsPlayingWhite:
6701         if (appData.zippyPlay) return FALSE;
6702         if (!white_piece) {
6703             DisplayMoveError(_("You are playing White"));
6704             return FALSE;
6705         }
6706         break;
6707
6708       case PlayFromGameFile:
6709             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6710       case EditGame:
6711         if (!white_piece && WhiteOnMove(currentMove)) {
6712             DisplayMoveError(_("It is White's turn"));
6713             return FALSE;
6714         }
6715         if (white_piece && !WhiteOnMove(currentMove)) {
6716             DisplayMoveError(_("It is Black's turn"));
6717             return FALSE;
6718         }
6719         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6720             /* Editing correspondence game history */
6721             /* Could disallow this or prompt for confirmation */
6722             cmailOldMove = -1;
6723         }
6724         break;
6725
6726       case BeginningOfGame:
6727         if (appData.icsActive) return FALSE;
6728         if (!appData.noChessProgram) {
6729             if (!white_piece) {
6730                 DisplayMoveError(_("You are playing White"));
6731                 return FALSE;
6732             }
6733         }
6734         break;
6735
6736       case Training:
6737         if (!white_piece && WhiteOnMove(currentMove)) {
6738             DisplayMoveError(_("It is White's turn"));
6739             return FALSE;
6740         }
6741         if (white_piece && !WhiteOnMove(currentMove)) {
6742             DisplayMoveError(_("It is Black's turn"));
6743             return FALSE;
6744         }
6745         break;
6746
6747       default:
6748       case IcsExamining:
6749         break;
6750     }
6751     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6752         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6753         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6754         && gameMode != AnalyzeFile && gameMode != Training) {
6755         DisplayMoveError(_("Displayed position is not current"));
6756         return FALSE;
6757     }
6758     return TRUE;
6759 }
6760
6761 Boolean
6762 OnlyMove (int *x, int *y, Boolean captures)
6763 {
6764     DisambiguateClosure cl;
6765     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6766     switch(gameMode) {
6767       case MachinePlaysBlack:
6768       case IcsPlayingWhite:
6769       case BeginningOfGame:
6770         if(!WhiteOnMove(currentMove)) return FALSE;
6771         break;
6772       case MachinePlaysWhite:
6773       case IcsPlayingBlack:
6774         if(WhiteOnMove(currentMove)) return FALSE;
6775         break;
6776       case EditGame:
6777         break;
6778       default:
6779         return FALSE;
6780     }
6781     cl.pieceIn = EmptySquare;
6782     cl.rfIn = *y;
6783     cl.ffIn = *x;
6784     cl.rtIn = -1;
6785     cl.ftIn = -1;
6786     cl.promoCharIn = NULLCHAR;
6787     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6788     if( cl.kind == NormalMove ||
6789         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6790         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6791         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6792       fromX = cl.ff;
6793       fromY = cl.rf;
6794       *x = cl.ft;
6795       *y = cl.rt;
6796       return TRUE;
6797     }
6798     if(cl.kind != ImpossibleMove) return FALSE;
6799     cl.pieceIn = EmptySquare;
6800     cl.rfIn = -1;
6801     cl.ffIn = -1;
6802     cl.rtIn = *y;
6803     cl.ftIn = *x;
6804     cl.promoCharIn = NULLCHAR;
6805     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6806     if( cl.kind == NormalMove ||
6807         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6808         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6809         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6810       fromX = cl.ff;
6811       fromY = cl.rf;
6812       *x = cl.ft;
6813       *y = cl.rt;
6814       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6815       return TRUE;
6816     }
6817     return FALSE;
6818 }
6819
6820 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6821 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6822 int lastLoadGameUseList = FALSE;
6823 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6824 ChessMove lastLoadGameStart = EndOfFile;
6825 int doubleClick;
6826 Boolean addToBookFlag;
6827
6828 void
6829 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6830 {
6831     ChessMove moveType;
6832     ChessSquare pup;
6833     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6834
6835     /* Check if the user is playing in turn.  This is complicated because we
6836        let the user "pick up" a piece before it is his turn.  So the piece he
6837        tried to pick up may have been captured by the time he puts it down!
6838        Therefore we use the color the user is supposed to be playing in this
6839        test, not the color of the piece that is currently on the starting
6840        square---except in EditGame mode, where the user is playing both
6841        sides; fortunately there the capture race can't happen.  (It can
6842        now happen in IcsExamining mode, but that's just too bad.  The user
6843        will get a somewhat confusing message in that case.)
6844        */
6845
6846     switch (gameMode) {
6847       case AnalyzeFile:
6848       case TwoMachinesPlay:
6849       case EndOfGame:
6850       case IcsObserving:
6851       case IcsIdle:
6852         /* We switched into a game mode where moves are not accepted,
6853            perhaps while the mouse button was down. */
6854         return;
6855
6856       case MachinePlaysWhite:
6857         /* User is moving for Black */
6858         if (WhiteOnMove(currentMove)) {
6859             DisplayMoveError(_("It is White's turn"));
6860             return;
6861         }
6862         break;
6863
6864       case MachinePlaysBlack:
6865         /* User is moving for White */
6866         if (!WhiteOnMove(currentMove)) {
6867             DisplayMoveError(_("It is Black's turn"));
6868             return;
6869         }
6870         break;
6871
6872       case PlayFromGameFile:
6873             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6874       case EditGame:
6875       case IcsExamining:
6876       case BeginningOfGame:
6877       case AnalyzeMode:
6878       case Training:
6879         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6880         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6881             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6882             /* User is moving for Black */
6883             if (WhiteOnMove(currentMove)) {
6884                 DisplayMoveError(_("It is White's turn"));
6885                 return;
6886             }
6887         } else {
6888             /* User is moving for White */
6889             if (!WhiteOnMove(currentMove)) {
6890                 DisplayMoveError(_("It is Black's turn"));
6891                 return;
6892             }
6893         }
6894         break;
6895
6896       case IcsPlayingBlack:
6897         /* User is moving for Black */
6898         if (WhiteOnMove(currentMove)) {
6899             if (!appData.premove) {
6900                 DisplayMoveError(_("It is White's turn"));
6901             } else if (toX >= 0 && toY >= 0) {
6902                 premoveToX = toX;
6903                 premoveToY = toY;
6904                 premoveFromX = fromX;
6905                 premoveFromY = fromY;
6906                 premovePromoChar = promoChar;
6907                 gotPremove = 1;
6908                 if (appData.debugMode)
6909                     fprintf(debugFP, "Got premove: fromX %d,"
6910                             "fromY %d, toX %d, toY %d\n",
6911                             fromX, fromY, toX, toY);
6912             }
6913             return;
6914         }
6915         break;
6916
6917       case IcsPlayingWhite:
6918         /* User is moving for White */
6919         if (!WhiteOnMove(currentMove)) {
6920             if (!appData.premove) {
6921                 DisplayMoveError(_("It is Black's turn"));
6922             } else if (toX >= 0 && toY >= 0) {
6923                 premoveToX = toX;
6924                 premoveToY = toY;
6925                 premoveFromX = fromX;
6926                 premoveFromY = fromY;
6927                 premovePromoChar = promoChar;
6928                 gotPremove = 1;
6929                 if (appData.debugMode)
6930                     fprintf(debugFP, "Got premove: fromX %d,"
6931                             "fromY %d, toX %d, toY %d\n",
6932                             fromX, fromY, toX, toY);
6933             }
6934             return;
6935         }
6936         break;
6937
6938       default:
6939         break;
6940
6941       case EditPosition:
6942         /* EditPosition, empty square, or different color piece;
6943            click-click move is possible */
6944         if (toX == -2 || toY == -2) {
6945             boards[0][fromY][fromX] = EmptySquare;
6946             DrawPosition(FALSE, boards[currentMove]);
6947             return;
6948         } else if (toX >= 0 && toY >= 0) {
6949             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6950                 ChessSquare q, p = boards[0][rf][ff];
6951                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6952                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6953                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6954                 if(PieceToChar(q) == '+') gatingPiece = p;
6955             }
6956             boards[0][toY][toX] = boards[0][fromY][fromX];
6957             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6958                 if(boards[0][fromY][0] != EmptySquare) {
6959                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6960                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6961                 }
6962             } else
6963             if(fromX == BOARD_RGHT+1) {
6964                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6965                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6966                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6967                 }
6968             } else
6969             boards[0][fromY][fromX] = gatingPiece;
6970             DrawPosition(FALSE, boards[currentMove]);
6971             return;
6972         }
6973         return;
6974     }
6975
6976     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6977     pup = boards[currentMove][toY][toX];
6978
6979     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6980     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6981          if( pup != EmptySquare ) return;
6982          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6983            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6984                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6985            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6986            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6987            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6988            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6989          fromY = DROP_RANK;
6990     }
6991
6992     /* [HGM] always test for legality, to get promotion info */
6993     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6994                                          fromY, fromX, toY, toX, promoChar);
6995
6996     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
6997
6998     /* [HGM] but possibly ignore an IllegalMove result */
6999     if (appData.testLegality) {
7000         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7001             DisplayMoveError(_("Illegal move"));
7002             return;
7003         }
7004     }
7005
7006     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7007         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7008              ClearPremoveHighlights(); // was included
7009         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7010         return;
7011     }
7012
7013     if(addToBookFlag) { // adding moves to book
7014         char buf[MSG_SIZ], move[MSG_SIZ];
7015         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7016         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7017         AddBookMove(buf);
7018         addToBookFlag = FALSE;
7019         ClearHighlights();
7020         return;
7021     }
7022
7023     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7024 }
7025
7026 /* Common tail of UserMoveEvent and DropMenuEvent */
7027 int
7028 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7029 {
7030     char *bookHit = 0;
7031
7032     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7033         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7034         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7035         if(WhiteOnMove(currentMove)) {
7036             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7037         } else {
7038             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7039         }
7040     }
7041
7042     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7043        move type in caller when we know the move is a legal promotion */
7044     if(moveType == NormalMove && promoChar)
7045         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7046
7047     /* [HGM] <popupFix> The following if has been moved here from
7048        UserMoveEvent(). Because it seemed to belong here (why not allow
7049        piece drops in training games?), and because it can only be
7050        performed after it is known to what we promote. */
7051     if (gameMode == Training) {
7052       /* compare the move played on the board to the next move in the
7053        * game. If they match, display the move and the opponent's response.
7054        * If they don't match, display an error message.
7055        */
7056       int saveAnimate;
7057       Board testBoard;
7058       CopyBoard(testBoard, boards[currentMove]);
7059       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7060
7061       if (CompareBoards(testBoard, boards[currentMove+1])) {
7062         ForwardInner(currentMove+1);
7063
7064         /* Autoplay the opponent's response.
7065          * if appData.animate was TRUE when Training mode was entered,
7066          * the response will be animated.
7067          */
7068         saveAnimate = appData.animate;
7069         appData.animate = animateTraining;
7070         ForwardInner(currentMove+1);
7071         appData.animate = saveAnimate;
7072
7073         /* check for the end of the game */
7074         if (currentMove >= forwardMostMove) {
7075           gameMode = PlayFromGameFile;
7076           ModeHighlight();
7077           SetTrainingModeOff();
7078           DisplayInformation(_("End of game"));
7079         }
7080       } else {
7081         DisplayError(_("Incorrect move"), 0);
7082       }
7083       return 1;
7084     }
7085
7086   /* Ok, now we know that the move is good, so we can kill
7087      the previous line in Analysis Mode */
7088   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7089                                 && currentMove < forwardMostMove) {
7090     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7091     else forwardMostMove = currentMove;
7092   }
7093
7094   ClearMap();
7095
7096   /* If we need the chess program but it's dead, restart it */
7097   ResurrectChessProgram();
7098
7099   /* A user move restarts a paused game*/
7100   if (pausing)
7101     PauseEvent();
7102
7103   thinkOutput[0] = NULLCHAR;
7104
7105   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7106
7107   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7108     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7109     return 1;
7110   }
7111
7112   if (gameMode == BeginningOfGame) {
7113     if (appData.noChessProgram) {
7114       gameMode = EditGame;
7115       SetGameInfo();
7116     } else {
7117       char buf[MSG_SIZ];
7118       gameMode = MachinePlaysBlack;
7119       StartClocks();
7120       SetGameInfo();
7121       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7122       DisplayTitle(buf);
7123       if (first.sendName) {
7124         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7125         SendToProgram(buf, &first);
7126       }
7127       StartClocks();
7128     }
7129     ModeHighlight();
7130   }
7131
7132   /* Relay move to ICS or chess engine */
7133   if (appData.icsActive) {
7134     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7135         gameMode == IcsExamining) {
7136       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7137         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7138         SendToICS("draw ");
7139         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7140       }
7141       // also send plain move, in case ICS does not understand atomic claims
7142       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7143       ics_user_moved = 1;
7144     }
7145   } else {
7146     if (first.sendTime && (gameMode == BeginningOfGame ||
7147                            gameMode == MachinePlaysWhite ||
7148                            gameMode == MachinePlaysBlack)) {
7149       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7150     }
7151     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7152          // [HGM] book: if program might be playing, let it use book
7153         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7154         first.maybeThinking = TRUE;
7155     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7156         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7157         SendBoard(&first, currentMove+1);
7158         if(second.analyzing) {
7159             if(!second.useSetboard) SendToProgram("undo\n", &second);
7160             SendBoard(&second, currentMove+1);
7161         }
7162     } else {
7163         SendMoveToProgram(forwardMostMove-1, &first);
7164         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7165     }
7166     if (currentMove == cmailOldMove + 1) {
7167       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7168     }
7169   }
7170
7171   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7172
7173   switch (gameMode) {
7174   case EditGame:
7175     if(appData.testLegality)
7176     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7177     case MT_NONE:
7178     case MT_CHECK:
7179       break;
7180     case MT_CHECKMATE:
7181     case MT_STAINMATE:
7182       if (WhiteOnMove(currentMove)) {
7183         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7184       } else {
7185         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7186       }
7187       break;
7188     case MT_STALEMATE:
7189       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7190       break;
7191     }
7192     break;
7193
7194   case MachinePlaysBlack:
7195   case MachinePlaysWhite:
7196     /* disable certain menu options while machine is thinking */
7197     SetMachineThinkingEnables();
7198     break;
7199
7200   default:
7201     break;
7202   }
7203
7204   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7205   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7206
7207   if(bookHit) { // [HGM] book: simulate book reply
7208         static char bookMove[MSG_SIZ]; // a bit generous?
7209
7210         programStats.nodes = programStats.depth = programStats.time =
7211         programStats.score = programStats.got_only_move = 0;
7212         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7213
7214         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7215         strcat(bookMove, bookHit);
7216         HandleMachineMove(bookMove, &first);
7217   }
7218   return 1;
7219 }
7220
7221 void
7222 MarkByFEN(char *fen)
7223 {
7224         int r, f;
7225         if(!appData.markers || !appData.highlightDragging) return;
7226         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7227         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7228         while(*fen) {
7229             int s = 0;
7230             marker[r][f] = 0;
7231             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7232             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7233             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7234             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7235             if(*fen == 'T') marker[r][f++] = 0; else
7236             if(*fen == 'Y') marker[r][f++] = 1; else
7237             if(*fen == 'G') marker[r][f++] = 3; else
7238             if(*fen == 'B') marker[r][f++] = 4; else
7239             if(*fen == 'C') marker[r][f++] = 5; else
7240             if(*fen == 'M') marker[r][f++] = 6; else
7241             if(*fen == 'W') marker[r][f++] = 7; else
7242             if(*fen == 'D') marker[r][f++] = 8; else
7243             if(*fen == 'R') marker[r][f++] = 2; else {
7244                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7245               f += s; fen -= s>0;
7246             }
7247             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7248             if(r < 0) break;
7249             fen++;
7250         }
7251         DrawPosition(TRUE, NULL);
7252 }
7253
7254 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7255
7256 void
7257 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7258 {
7259     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7260     Markers *m = (Markers *) closure;
7261     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7262         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7263                          || kind == WhiteCapturesEnPassant
7264                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7265     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7266 }
7267
7268 static int hoverSavedValid;
7269
7270 void
7271 MarkTargetSquares (int clear)
7272 {
7273   int x, y, sum=0;
7274   if(clear) { // no reason to ever suppress clearing
7275     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7276     hoverSavedValid = 0;
7277     if(!sum) return; // nothing was cleared,no redraw needed
7278   } else {
7279     int capt = 0;
7280     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7281        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7282     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7283     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7284       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7285       if(capt)
7286       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7287     }
7288   }
7289   DrawPosition(FALSE, NULL);
7290 }
7291
7292 int
7293 Explode (Board board, int fromX, int fromY, int toX, int toY)
7294 {
7295     if(gameInfo.variant == VariantAtomic &&
7296        (board[toY][toX] != EmptySquare ||                     // capture?
7297         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7298                          board[fromY][fromX] == BlackPawn   )
7299       )) {
7300         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7301         return TRUE;
7302     }
7303     return FALSE;
7304 }
7305
7306 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7307
7308 int
7309 CanPromote (ChessSquare piece, int y)
7310 {
7311         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7312         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7313         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7314         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7315            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7316            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7317          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7318         return (piece == BlackPawn && y <= zone ||
7319                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7320                 piece == BlackLance && y == 1 ||
7321                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7322 }
7323
7324 void
7325 HoverEvent (int xPix, int yPix, int x, int y)
7326 {
7327         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7328         int r, f;
7329         if(!first.highlight) return;
7330         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7331         if(x == oldX && y == oldY) return; // only do something if we enter new square
7332         oldFromX = fromX; oldFromY = fromY;
7333         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7334           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7335             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7336           hoverSavedValid = 1;
7337         } else if(oldX != x || oldY != y) {
7338           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7339           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7340           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7341             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7342           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7343             char buf[MSG_SIZ];
7344             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7345             SendToProgram(buf, &first);
7346           }
7347           oldX = x; oldY = y;
7348 //        SetHighlights(fromX, fromY, x, y);
7349         }
7350 }
7351
7352 void ReportClick(char *action, int x, int y)
7353 {
7354         char buf[MSG_SIZ]; // Inform engine of what user does
7355         int r, f;
7356         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7357           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = !pieceDefs, marker[r][f] = 0;
7358         if(!first.highlight || gameMode == EditPosition) return;
7359         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7360         SendToProgram(buf, &first);
7361 }
7362
7363 void
7364 LeftClick (ClickType clickType, int xPix, int yPix)
7365 {
7366     int x, y;
7367     Boolean saveAnimate;
7368     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7369     char promoChoice = NULLCHAR;
7370     ChessSquare piece;
7371     static TimeMark lastClickTime, prevClickTime;
7372
7373     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7374
7375     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7376
7377     if (clickType == Press) ErrorPopDown();
7378     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7379
7380     x = EventToSquare(xPix, BOARD_WIDTH);
7381     y = EventToSquare(yPix, BOARD_HEIGHT);
7382     if (!flipView && y >= 0) {
7383         y = BOARD_HEIGHT - 1 - y;
7384     }
7385     if (flipView && x >= 0) {
7386         x = BOARD_WIDTH - 1 - x;
7387     }
7388
7389     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7390         defaultPromoChoice = promoSweep;
7391         promoSweep = EmptySquare;   // terminate sweep
7392         promoDefaultAltered = TRUE;
7393         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7394     }
7395
7396     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7397         if(clickType == Release) return; // ignore upclick of click-click destination
7398         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7399         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7400         if(gameInfo.holdingsWidth &&
7401                 (WhiteOnMove(currentMove)
7402                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7403                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7404             // click in right holdings, for determining promotion piece
7405             ChessSquare p = boards[currentMove][y][x];
7406             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7407             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7408             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7409                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7410                 fromX = fromY = -1;
7411                 return;
7412             }
7413         }
7414         DrawPosition(FALSE, boards[currentMove]);
7415         return;
7416     }
7417
7418     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7419     if(clickType == Press
7420             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7421               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7422               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7423         return;
7424
7425     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7426         // could be static click on premove from-square: abort premove
7427         gotPremove = 0;
7428         ClearPremoveHighlights();
7429     }
7430
7431     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7432         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7433
7434     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7435         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7436                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7437         defaultPromoChoice = DefaultPromoChoice(side);
7438     }
7439
7440     autoQueen = appData.alwaysPromoteToQueen;
7441
7442     if (fromX == -1) {
7443       int originalY = y;
7444       gatingPiece = EmptySquare;
7445       if (clickType != Press) {
7446         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7447             DragPieceEnd(xPix, yPix); dragging = 0;
7448             DrawPosition(FALSE, NULL);
7449         }
7450         return;
7451       }
7452       doubleClick = FALSE;
7453       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7454         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7455       }
7456       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7457       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7458          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7459          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7460             /* First square */
7461             if (OKToStartUserMove(fromX, fromY)) {
7462                 second = 0;
7463                 ReportClick("lift", x, y);
7464                 MarkTargetSquares(0);
7465                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7466                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7467                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7468                     promoSweep = defaultPromoChoice;
7469                     selectFlag = 0; lastX = xPix; lastY = yPix;
7470                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7471                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7472                 }
7473                 if (appData.highlightDragging) {
7474                     SetHighlights(fromX, fromY, -1, -1);
7475                 } else {
7476                     ClearHighlights();
7477                 }
7478             } else fromX = fromY = -1;
7479             return;
7480         }
7481     }
7482
7483     /* fromX != -1 */
7484     if (clickType == Press && gameMode != EditPosition) {
7485         ChessSquare fromP;
7486         ChessSquare toP;
7487         int frc;
7488
7489         // ignore off-board to clicks
7490         if(y < 0 || x < 0) return;
7491
7492         /* Check if clicking again on the same color piece */
7493         fromP = boards[currentMove][fromY][fromX];
7494         toP = boards[currentMove][y][x];
7495         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7496         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7497            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7498              WhitePawn <= toP && toP <= WhiteKing &&
7499              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7500              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7501             (BlackPawn <= fromP && fromP <= BlackKing &&
7502              BlackPawn <= toP && toP <= BlackKing &&
7503              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7504              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7505             /* Clicked again on same color piece -- changed his mind */
7506             second = (x == fromX && y == fromY);
7507             killX = killY = -1;
7508             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7509                 second = FALSE; // first double-click rather than scond click
7510                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7511             }
7512             promoDefaultAltered = FALSE;
7513             MarkTargetSquares(1);
7514            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7515             if (appData.highlightDragging) {
7516                 SetHighlights(x, y, -1, -1);
7517             } else {
7518                 ClearHighlights();
7519             }
7520             if (OKToStartUserMove(x, y)) {
7521                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7522                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7523                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7524                  gatingPiece = boards[currentMove][fromY][fromX];
7525                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7526                 fromX = x;
7527                 fromY = y; dragging = 1;
7528                 ReportClick("lift", x, y);
7529                 MarkTargetSquares(0);
7530                 DragPieceBegin(xPix, yPix, FALSE);
7531                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7532                     promoSweep = defaultPromoChoice;
7533                     selectFlag = 0; lastX = xPix; lastY = yPix;
7534                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7535                 }
7536             }
7537            }
7538            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7539            second = FALSE;
7540         }
7541         // ignore clicks on holdings
7542         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7543     }
7544
7545     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7546         DragPieceEnd(xPix, yPix); dragging = 0;
7547         if(clearFlag) {
7548             // a deferred attempt to click-click move an empty square on top of a piece
7549             boards[currentMove][y][x] = EmptySquare;
7550             ClearHighlights();
7551             DrawPosition(FALSE, boards[currentMove]);
7552             fromX = fromY = -1; clearFlag = 0;
7553             return;
7554         }
7555         if (appData.animateDragging) {
7556             /* Undo animation damage if any */
7557             DrawPosition(FALSE, NULL);
7558         }
7559         if (second || sweepSelecting) {
7560             /* Second up/down in same square; just abort move */
7561             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7562             second = sweepSelecting = 0;
7563             fromX = fromY = -1;
7564             gatingPiece = EmptySquare;
7565             MarkTargetSquares(1);
7566             ClearHighlights();
7567             gotPremove = 0;
7568             ClearPremoveHighlights();
7569         } else {
7570             /* First upclick in same square; start click-click mode */
7571             SetHighlights(x, y, -1, -1);
7572         }
7573         return;
7574     }
7575
7576     clearFlag = 0;
7577
7578     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7579        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7580         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7581         DisplayMessage(_("only marked squares are legal"),"");
7582         DrawPosition(TRUE, NULL);
7583         return; // ignore to-click
7584     }
7585
7586     /* we now have a different from- and (possibly off-board) to-square */
7587     /* Completed move */
7588     if(!sweepSelecting) {
7589         toX = x;
7590         toY = y;
7591     }
7592
7593     piece = boards[currentMove][fromY][fromX];
7594
7595     saveAnimate = appData.animate;
7596     if (clickType == Press) {
7597         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7598         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7599             // must be Edit Position mode with empty-square selected
7600             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7601             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7602             return;
7603         }
7604         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7605             return;
7606         }
7607         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7608             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7609         } else
7610         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7611         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7612           if(appData.sweepSelect) {
7613             promoSweep = defaultPromoChoice;
7614             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7615             selectFlag = 0; lastX = xPix; lastY = yPix;
7616             Sweep(0); // Pawn that is going to promote: preview promotion piece
7617             sweepSelecting = 1;
7618             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7619             MarkTargetSquares(1);
7620           }
7621           return; // promo popup appears on up-click
7622         }
7623         /* Finish clickclick move */
7624         if (appData.animate || appData.highlightLastMove) {
7625             SetHighlights(fromX, fromY, toX, toY);
7626         } else {
7627             ClearHighlights();
7628         }
7629     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7630         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7631         if (appData.animate || appData.highlightLastMove) {
7632             SetHighlights(fromX, fromY, toX, toY);
7633         } else {
7634             ClearHighlights();
7635         }
7636     } else {
7637 #if 0
7638 // [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
7639         /* Finish drag move */
7640         if (appData.highlightLastMove) {
7641             SetHighlights(fromX, fromY, toX, toY);
7642         } else {
7643             ClearHighlights();
7644         }
7645 #endif
7646         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7647         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7648           dragging *= 2;            // flag button-less dragging if we are dragging
7649           MarkTargetSquares(1);
7650           if(x == killX && y == killY) killX = killY = -1; else {
7651             killX = x; killY = y;     //remeber this square as intermediate
7652             ReportClick("put", x, y); // and inform engine
7653             ReportClick("lift", x, y);
7654             MarkTargetSquares(0);
7655             return;
7656           }
7657         }
7658         DragPieceEnd(xPix, yPix); dragging = 0;
7659         /* Don't animate move and drag both */
7660         appData.animate = FALSE;
7661     }
7662
7663     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7664     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7665         ChessSquare piece = boards[currentMove][fromY][fromX];
7666         if(gameMode == EditPosition && piece != EmptySquare &&
7667            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7668             int n;
7669
7670             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7671                 n = PieceToNumber(piece - (int)BlackPawn);
7672                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7673                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7674                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7675             } else
7676             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7677                 n = PieceToNumber(piece);
7678                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7679                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7680                 boards[currentMove][n][BOARD_WIDTH-2]++;
7681             }
7682             boards[currentMove][fromY][fromX] = EmptySquare;
7683         }
7684         ClearHighlights();
7685         fromX = fromY = -1;
7686         MarkTargetSquares(1);
7687         DrawPosition(TRUE, boards[currentMove]);
7688         return;
7689     }
7690
7691     // off-board moves should not be highlighted
7692     if(x < 0 || y < 0) ClearHighlights();
7693     else ReportClick("put", x, y);
7694
7695     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7696
7697     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7698         SetHighlights(fromX, fromY, toX, toY);
7699         MarkTargetSquares(1);
7700         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7701             // [HGM] super: promotion to captured piece selected from holdings
7702             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7703             promotionChoice = TRUE;
7704             // kludge follows to temporarily execute move on display, without promoting yet
7705             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7706             boards[currentMove][toY][toX] = p;
7707             DrawPosition(FALSE, boards[currentMove]);
7708             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7709             boards[currentMove][toY][toX] = q;
7710             DisplayMessage("Click in holdings to choose piece", "");
7711             return;
7712         }
7713         PromotionPopUp(promoChoice);
7714     } else {
7715         int oldMove = currentMove;
7716         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7717         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7718         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7719         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7720            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7721             DrawPosition(TRUE, boards[currentMove]);
7722         MarkTargetSquares(1);
7723         fromX = fromY = -1;
7724     }
7725     appData.animate = saveAnimate;
7726     if (appData.animate || appData.animateDragging) {
7727         /* Undo animation damage if needed */
7728         DrawPosition(FALSE, NULL);
7729     }
7730 }
7731
7732 int
7733 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7734 {   // front-end-free part taken out of PieceMenuPopup
7735     int whichMenu; int xSqr, ySqr;
7736
7737     if(seekGraphUp) { // [HGM] seekgraph
7738         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7739         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7740         return -2;
7741     }
7742
7743     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7744          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7745         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7746         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7747         if(action == Press)   {
7748             originalFlip = flipView;
7749             flipView = !flipView; // temporarily flip board to see game from partners perspective
7750             DrawPosition(TRUE, partnerBoard);
7751             DisplayMessage(partnerStatus, "");
7752             partnerUp = TRUE;
7753         } else if(action == Release) {
7754             flipView = originalFlip;
7755             DrawPosition(TRUE, boards[currentMove]);
7756             partnerUp = FALSE;
7757         }
7758         return -2;
7759     }
7760
7761     xSqr = EventToSquare(x, BOARD_WIDTH);
7762     ySqr = EventToSquare(y, BOARD_HEIGHT);
7763     if (action == Release) {
7764         if(pieceSweep != EmptySquare) {
7765             EditPositionMenuEvent(pieceSweep, toX, toY);
7766             pieceSweep = EmptySquare;
7767         } else UnLoadPV(); // [HGM] pv
7768     }
7769     if (action != Press) return -2; // return code to be ignored
7770     switch (gameMode) {
7771       case IcsExamining:
7772         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7773       case EditPosition:
7774         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7775         if (xSqr < 0 || ySqr < 0) return -1;
7776         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7777         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7778         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7779         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7780         NextPiece(0);
7781         return 2; // grab
7782       case IcsObserving:
7783         if(!appData.icsEngineAnalyze) return -1;
7784       case IcsPlayingWhite:
7785       case IcsPlayingBlack:
7786         if(!appData.zippyPlay) goto noZip;
7787       case AnalyzeMode:
7788       case AnalyzeFile:
7789       case MachinePlaysWhite:
7790       case MachinePlaysBlack:
7791       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7792         if (!appData.dropMenu) {
7793           LoadPV(x, y);
7794           return 2; // flag front-end to grab mouse events
7795         }
7796         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7797            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7798       case EditGame:
7799       noZip:
7800         if (xSqr < 0 || ySqr < 0) return -1;
7801         if (!appData.dropMenu || appData.testLegality &&
7802             gameInfo.variant != VariantBughouse &&
7803             gameInfo.variant != VariantCrazyhouse) return -1;
7804         whichMenu = 1; // drop menu
7805         break;
7806       default:
7807         return -1;
7808     }
7809
7810     if (((*fromX = xSqr) < 0) ||
7811         ((*fromY = ySqr) < 0)) {
7812         *fromX = *fromY = -1;
7813         return -1;
7814     }
7815     if (flipView)
7816       *fromX = BOARD_WIDTH - 1 - *fromX;
7817     else
7818       *fromY = BOARD_HEIGHT - 1 - *fromY;
7819
7820     return whichMenu;
7821 }
7822
7823 void
7824 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7825 {
7826 //    char * hint = lastHint;
7827     FrontEndProgramStats stats;
7828
7829     stats.which = cps == &first ? 0 : 1;
7830     stats.depth = cpstats->depth;
7831     stats.nodes = cpstats->nodes;
7832     stats.score = cpstats->score;
7833     stats.time = cpstats->time;
7834     stats.pv = cpstats->movelist;
7835     stats.hint = lastHint;
7836     stats.an_move_index = 0;
7837     stats.an_move_count = 0;
7838
7839     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7840         stats.hint = cpstats->move_name;
7841         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7842         stats.an_move_count = cpstats->nr_moves;
7843     }
7844
7845     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
7846
7847     SetProgramStats( &stats );
7848 }
7849
7850 void
7851 ClearEngineOutputPane (int which)
7852 {
7853     static FrontEndProgramStats dummyStats;
7854     dummyStats.which = which;
7855     dummyStats.pv = "#";
7856     SetProgramStats( &dummyStats );
7857 }
7858
7859 #define MAXPLAYERS 500
7860
7861 char *
7862 TourneyStandings (int display)
7863 {
7864     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7865     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7866     char result, *p, *names[MAXPLAYERS];
7867
7868     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7869         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7870     names[0] = p = strdup(appData.participants);
7871     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7872
7873     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7874
7875     while(result = appData.results[nr]) {
7876         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7877         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7878         wScore = bScore = 0;
7879         switch(result) {
7880           case '+': wScore = 2; break;
7881           case '-': bScore = 2; break;
7882           case '=': wScore = bScore = 1; break;
7883           case ' ':
7884           case '*': return strdup("busy"); // tourney not finished
7885         }
7886         score[w] += wScore;
7887         score[b] += bScore;
7888         games[w]++;
7889         games[b]++;
7890         nr++;
7891     }
7892     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7893     for(w=0; w<nPlayers; w++) {
7894         bScore = -1;
7895         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7896         ranking[w] = b; points[w] = bScore; score[b] = -2;
7897     }
7898     p = malloc(nPlayers*34+1);
7899     for(w=0; w<nPlayers && w<display; w++)
7900         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7901     free(names[0]);
7902     return p;
7903 }
7904
7905 void
7906 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7907 {       // count all piece types
7908         int p, f, r;
7909         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7910         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7911         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7912                 p = board[r][f];
7913                 pCnt[p]++;
7914                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7915                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7916                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7917                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7918                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7919                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7920         }
7921 }
7922
7923 int
7924 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7925 {
7926         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7927         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7928
7929         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7930         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7931         if(myPawns == 2 && nMine == 3) // KPP
7932             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7933         if(myPawns == 1 && nMine == 2) // KP
7934             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7935         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7936             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7937         if(myPawns) return FALSE;
7938         if(pCnt[WhiteRook+side])
7939             return pCnt[BlackRook-side] ||
7940                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7941                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7942                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7943         if(pCnt[WhiteCannon+side]) {
7944             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7945             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7946         }
7947         if(pCnt[WhiteKnight+side])
7948             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7949         return FALSE;
7950 }
7951
7952 int
7953 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7954 {
7955         VariantClass v = gameInfo.variant;
7956
7957         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7958         if(v == VariantShatranj) return TRUE; // always winnable through baring
7959         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7960         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7961
7962         if(v == VariantXiangqi) {
7963                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7964
7965                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7966                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7967                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7968                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7969                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7970                 if(stale) // we have at least one last-rank P plus perhaps C
7971                     return majors // KPKX
7972                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7973                 else // KCA*E*
7974                     return pCnt[WhiteFerz+side] // KCAK
7975                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7976                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7977                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7978
7979         } else if(v == VariantKnightmate) {
7980                 if(nMine == 1) return FALSE;
7981                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7982         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7983                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7984
7985                 if(nMine == 1) return FALSE; // bare King
7986                 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
7987                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7988                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7989                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7990                 if(pCnt[WhiteKnight+side])
7991                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7992                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7993                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7994                 if(nBishops)
7995                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7996                 if(pCnt[WhiteAlfil+side])
7997                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7998                 if(pCnt[WhiteWazir+side])
7999                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8000         }
8001
8002         return TRUE;
8003 }
8004
8005 int
8006 CompareWithRights (Board b1, Board b2)
8007 {
8008     int rights = 0;
8009     if(!CompareBoards(b1, b2)) return FALSE;
8010     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8011     /* compare castling rights */
8012     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8013            rights++; /* King lost rights, while rook still had them */
8014     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8015         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8016            rights++; /* but at least one rook lost them */
8017     }
8018     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8019            rights++;
8020     if( b1[CASTLING][5] != NoRights ) {
8021         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8022            rights++;
8023     }
8024     return rights == 0;
8025 }
8026
8027 int
8028 Adjudicate (ChessProgramState *cps)
8029 {       // [HGM] some adjudications useful with buggy engines
8030         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8031         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8032         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8033         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8034         int k, drop, count = 0; static int bare = 1;
8035         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8036         Boolean canAdjudicate = !appData.icsActive;
8037
8038         // most tests only when we understand the game, i.e. legality-checking on
8039             if( appData.testLegality )
8040             {   /* [HGM] Some more adjudications for obstinate engines */
8041                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8042                 static int moveCount = 6;
8043                 ChessMove result;
8044                 char *reason = NULL;
8045
8046                 /* Count what is on board. */
8047                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8048
8049                 /* Some material-based adjudications that have to be made before stalemate test */
8050                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8051                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8052                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8053                      if(canAdjudicate && appData.checkMates) {
8054                          if(engineOpponent)
8055                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8056                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8057                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8058                          return 1;
8059                      }
8060                 }
8061
8062                 /* Bare King in Shatranj (loses) or Losers (wins) */
8063                 if( nrW == 1 || nrB == 1) {
8064                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8065                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8066                      if(canAdjudicate && appData.checkMates) {
8067                          if(engineOpponent)
8068                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8069                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8070                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8071                          return 1;
8072                      }
8073                   } else
8074                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8075                   {    /* bare King */
8076                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8077                         if(canAdjudicate && appData.checkMates) {
8078                             /* but only adjudicate if adjudication enabled */
8079                             if(engineOpponent)
8080                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8081                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8082                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8083                             return 1;
8084                         }
8085                   }
8086                 } else bare = 1;
8087
8088
8089             // don't wait for engine to announce game end if we can judge ourselves
8090             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8091               case MT_CHECK:
8092                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8093                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8094                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8095                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8096                             checkCnt++;
8097                         if(checkCnt >= 2) {
8098                             reason = "Xboard adjudication: 3rd check";
8099                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8100                             break;
8101                         }
8102                     }
8103                 }
8104               case MT_NONE:
8105               default:
8106                 break;
8107               case MT_STEALMATE:
8108               case MT_STALEMATE:
8109               case MT_STAINMATE:
8110                 reason = "Xboard adjudication: Stalemate";
8111                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8112                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8113                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8114                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8115                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8116                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8117                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8118                                                                         EP_CHECKMATE : EP_WINS);
8119                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8120                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8121                 }
8122                 break;
8123               case MT_CHECKMATE:
8124                 reason = "Xboard adjudication: Checkmate";
8125                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8126                 if(gameInfo.variant == VariantShogi) {
8127                     if(forwardMostMove > backwardMostMove
8128                        && moveList[forwardMostMove-1][1] == '@'
8129                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8130                         reason = "XBoard adjudication: pawn-drop mate";
8131                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8132                     }
8133                 }
8134                 break;
8135             }
8136
8137                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8138                     case EP_STALEMATE:
8139                         result = GameIsDrawn; break;
8140                     case EP_CHECKMATE:
8141                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8142                     case EP_WINS:
8143                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8144                     default:
8145                         result = EndOfFile;
8146                 }
8147                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8148                     if(engineOpponent)
8149                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8150                     GameEnds( result, reason, GE_XBOARD );
8151                     return 1;
8152                 }
8153
8154                 /* Next absolutely insufficient mating material. */
8155                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8156                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8157                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8158
8159                      /* always flag draws, for judging claims */
8160                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8161
8162                      if(canAdjudicate && appData.materialDraws) {
8163                          /* but only adjudicate them if adjudication enabled */
8164                          if(engineOpponent) {
8165                            SendToProgram("force\n", engineOpponent); // suppress reply
8166                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8167                          }
8168                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8169                          return 1;
8170                      }
8171                 }
8172
8173                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8174                 if(gameInfo.variant == VariantXiangqi ?
8175                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8176                  : nrW + nrB == 4 &&
8177                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8178                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8179                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8180                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8181                    ) ) {
8182                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8183                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8184                           if(engineOpponent) {
8185                             SendToProgram("force\n", engineOpponent); // suppress reply
8186                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8187                           }
8188                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8189                           return 1;
8190                      }
8191                 } else moveCount = 6;
8192             }
8193
8194         // Repetition draws and 50-move rule can be applied independently of legality testing
8195
8196                 /* Check for rep-draws */
8197                 count = 0;
8198                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8199                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8200                 for(k = forwardMostMove-2;
8201                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8202                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8203                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8204                     k-=2)
8205                 {   int rights=0;
8206                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8207                         /* compare castling rights */
8208                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8209                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8210                                 rights++; /* King lost rights, while rook still had them */
8211                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8212                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8213                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8214                                    rights++; /* but at least one rook lost them */
8215                         }
8216                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8217                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8218                                 rights++;
8219                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8220                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8221                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8222                                    rights++;
8223                         }
8224                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8225                             && appData.drawRepeats > 1) {
8226                              /* adjudicate after user-specified nr of repeats */
8227                              int result = GameIsDrawn;
8228                              char *details = "XBoard adjudication: repetition draw";
8229                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8230                                 // [HGM] xiangqi: check for forbidden perpetuals
8231                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8232                                 for(m=forwardMostMove; m>k; m-=2) {
8233                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8234                                         ourPerpetual = 0; // the current mover did not always check
8235                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8236                                         hisPerpetual = 0; // the opponent did not always check
8237                                 }
8238                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8239                                                                         ourPerpetual, hisPerpetual);
8240                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8241                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8242                                     details = "Xboard adjudication: perpetual checking";
8243                                 } else
8244                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8245                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8246                                 } else
8247                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8248                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8249                                         result = BlackWins;
8250                                         details = "Xboard adjudication: repetition";
8251                                     }
8252                                 } else // it must be XQ
8253                                 // Now check for perpetual chases
8254                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8255                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8256                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8257                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8258                                         static char resdet[MSG_SIZ];
8259                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8260                                         details = resdet;
8261                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8262                                     } else
8263                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8264                                         break; // Abort repetition-checking loop.
8265                                 }
8266                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8267                              }
8268                              if(engineOpponent) {
8269                                SendToProgram("force\n", engineOpponent); // suppress reply
8270                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8271                              }
8272                              GameEnds( result, details, GE_XBOARD );
8273                              return 1;
8274                         }
8275                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8276                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8277                     }
8278                 }
8279
8280                 /* Now we test for 50-move draws. Determine ply count */
8281                 count = forwardMostMove;
8282                 /* look for last irreversble move */
8283                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8284                     count--;
8285                 /* if we hit starting position, add initial plies */
8286                 if( count == backwardMostMove )
8287                     count -= initialRulePlies;
8288                 count = forwardMostMove - count;
8289                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8290                         // adjust reversible move counter for checks in Xiangqi
8291                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8292                         if(i < backwardMostMove) i = backwardMostMove;
8293                         while(i <= forwardMostMove) {
8294                                 lastCheck = inCheck; // check evasion does not count
8295                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8296                                 if(inCheck || lastCheck) count--; // check does not count
8297                                 i++;
8298                         }
8299                 }
8300                 if( count >= 100)
8301                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8302                          /* this is used to judge if draw claims are legal */
8303                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8304                          if(engineOpponent) {
8305                            SendToProgram("force\n", engineOpponent); // suppress reply
8306                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8307                          }
8308                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8309                          return 1;
8310                 }
8311
8312                 /* if draw offer is pending, treat it as a draw claim
8313                  * when draw condition present, to allow engines a way to
8314                  * claim draws before making their move to avoid a race
8315                  * condition occurring after their move
8316                  */
8317                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8318                          char *p = NULL;
8319                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8320                              p = "Draw claim: 50-move rule";
8321                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8322                              p = "Draw claim: 3-fold repetition";
8323                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8324                              p = "Draw claim: insufficient mating material";
8325                          if( p != NULL && canAdjudicate) {
8326                              if(engineOpponent) {
8327                                SendToProgram("force\n", engineOpponent); // suppress reply
8328                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8329                              }
8330                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8331                              return 1;
8332                          }
8333                 }
8334
8335                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8336                     if(engineOpponent) {
8337                       SendToProgram("force\n", engineOpponent); // suppress reply
8338                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8339                     }
8340                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8341                     return 1;
8342                 }
8343         return 0;
8344 }
8345
8346 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8347 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8348 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8349
8350 static int
8351 BitbaseProbe ()
8352 {
8353     int pieces[10], squares[10], cnt=0, r, f, res;
8354     static int loaded;
8355     static PPROBE_EGBB probeBB;
8356     if(!appData.testLegality) return 10;
8357     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8358     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8359     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8360     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8361         ChessSquare piece = boards[forwardMostMove][r][f];
8362         int black = (piece >= BlackPawn);
8363         int type = piece - black*BlackPawn;
8364         if(piece == EmptySquare) continue;
8365         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8366         if(type == WhiteKing) type = WhiteQueen + 1;
8367         type = egbbCode[type];
8368         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8369         pieces[cnt] = type + black*6;
8370         if(++cnt > 5) return 11;
8371     }
8372     pieces[cnt] = squares[cnt] = 0;
8373     // probe EGBB
8374     if(loaded == 2) return 13; // loading failed before
8375     if(loaded == 0) {
8376         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8377         HMODULE lib;
8378         PLOAD_EGBB loadBB;
8379         loaded = 2; // prepare for failure
8380         if(!path) return 13; // no egbb installed
8381         strncpy(buf, path + 8, MSG_SIZ);
8382         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8383         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8384         lib = LoadLibrary(buf);
8385         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8386         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8387         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8388         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8389         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8390         loaded = 1; // success!
8391     }
8392     res = probeBB(forwardMostMove & 1, pieces, squares);
8393     return res > 0 ? 1 : res < 0 ? -1 : 0;
8394 }
8395
8396 char *
8397 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8398 {   // [HGM] book: this routine intercepts moves to simulate book replies
8399     char *bookHit = NULL;
8400
8401     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8402         char buf[MSG_SIZ];
8403         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8404         SendToProgram(buf, cps);
8405     }
8406     //first determine if the incoming move brings opponent into his book
8407     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8408         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8409     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8410     if(bookHit != NULL && !cps->bookSuspend) {
8411         // make sure opponent is not going to reply after receiving move to book position
8412         SendToProgram("force\n", cps);
8413         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8414     }
8415     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8416     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8417     // now arrange restart after book miss
8418     if(bookHit) {
8419         // after a book hit we never send 'go', and the code after the call to this routine
8420         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8421         char buf[MSG_SIZ], *move = bookHit;
8422         if(cps->useSAN) {
8423             int fromX, fromY, toX, toY;
8424             char promoChar;
8425             ChessMove moveType;
8426             move = buf + 30;
8427             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8428                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8429                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8430                                     PosFlags(forwardMostMove),
8431                                     fromY, fromX, toY, toX, promoChar, move);
8432             } else {
8433                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8434                 bookHit = NULL;
8435             }
8436         }
8437         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8438         SendToProgram(buf, cps);
8439         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8440     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8441         SendToProgram("go\n", cps);
8442         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8443     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8444         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8445             SendToProgram("go\n", cps);
8446         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8447     }
8448     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8449 }
8450
8451 int
8452 LoadError (char *errmess, ChessProgramState *cps)
8453 {   // unloads engine and switches back to -ncp mode if it was first
8454     if(cps->initDone) return FALSE;
8455     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8456     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8457     cps->pr = NoProc;
8458     if(cps == &first) {
8459         appData.noChessProgram = TRUE;
8460         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8461         gameMode = BeginningOfGame; ModeHighlight();
8462         SetNCPMode();
8463     }
8464     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8465     DisplayMessage("", ""); // erase waiting message
8466     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8467     return TRUE;
8468 }
8469
8470 char *savedMessage;
8471 ChessProgramState *savedState;
8472 void
8473 DeferredBookMove (void)
8474 {
8475         if(savedState->lastPing != savedState->lastPong)
8476                     ScheduleDelayedEvent(DeferredBookMove, 10);
8477         else
8478         HandleMachineMove(savedMessage, savedState);
8479 }
8480
8481 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8482 static ChessProgramState *stalledEngine;
8483 static char stashedInputMove[MSG_SIZ];
8484
8485 void
8486 HandleMachineMove (char *message, ChessProgramState *cps)
8487 {
8488     static char firstLeg[20];
8489     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8490     char realname[MSG_SIZ];
8491     int fromX, fromY, toX, toY;
8492     ChessMove moveType;
8493     char promoChar, roar;
8494     char *p, *pv=buf1;
8495     int machineWhite, oldError;
8496     char *bookHit;
8497
8498     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8499         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8500         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8501             DisplayError(_("Invalid pairing from pairing engine"), 0);
8502             return;
8503         }
8504         pairingReceived = 1;
8505         NextMatchGame();
8506         return; // Skim the pairing messages here.
8507     }
8508
8509     oldError = cps->userError; cps->userError = 0;
8510
8511 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8512     /*
8513      * Kludge to ignore BEL characters
8514      */
8515     while (*message == '\007') message++;
8516
8517     /*
8518      * [HGM] engine debug message: ignore lines starting with '#' character
8519      */
8520     if(cps->debug && *message == '#') return;
8521
8522     /*
8523      * Look for book output
8524      */
8525     if (cps == &first && bookRequested) {
8526         if (message[0] == '\t' || message[0] == ' ') {
8527             /* Part of the book output is here; append it */
8528             strcat(bookOutput, message);
8529             strcat(bookOutput, "  \n");
8530             return;
8531         } else if (bookOutput[0] != NULLCHAR) {
8532             /* All of book output has arrived; display it */
8533             char *p = bookOutput;
8534             while (*p != NULLCHAR) {
8535                 if (*p == '\t') *p = ' ';
8536                 p++;
8537             }
8538             DisplayInformation(bookOutput);
8539             bookRequested = FALSE;
8540             /* Fall through to parse the current output */
8541         }
8542     }
8543
8544     /*
8545      * Look for machine move.
8546      */
8547     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8548         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8549     {
8550         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8551             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8552             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8553             stalledEngine = cps;
8554             if(appData.ponderNextMove) { // bring opponent out of ponder
8555                 if(gameMode == TwoMachinesPlay) {
8556                     if(cps->other->pause)
8557                         PauseEngine(cps->other);
8558                     else
8559                         SendToProgram("easy\n", cps->other);
8560                 }
8561             }
8562             StopClocks();
8563             return;
8564         }
8565
8566         /* This method is only useful on engines that support ping */
8567         if (cps->lastPing != cps->lastPong) {
8568           if (gameMode == BeginningOfGame) {
8569             /* Extra move from before last new; ignore */
8570             if (appData.debugMode) {
8571                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8572             }
8573           } else {
8574             if (appData.debugMode) {
8575                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8576                         cps->which, gameMode);
8577             }
8578
8579             SendToProgram("undo\n", cps);
8580           }
8581           return;
8582         }
8583
8584         switch (gameMode) {
8585           case BeginningOfGame:
8586             /* Extra move from before last reset; ignore */
8587             if (appData.debugMode) {
8588                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8589             }
8590             return;
8591
8592           case EndOfGame:
8593           case IcsIdle:
8594           default:
8595             /* Extra move after we tried to stop.  The mode test is
8596                not a reliable way of detecting this problem, but it's
8597                the best we can do on engines that don't support ping.
8598             */
8599             if (appData.debugMode) {
8600                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8601                         cps->which, gameMode);
8602             }
8603             SendToProgram("undo\n", cps);
8604             return;
8605
8606           case MachinePlaysWhite:
8607           case IcsPlayingWhite:
8608             machineWhite = TRUE;
8609             break;
8610
8611           case MachinePlaysBlack:
8612           case IcsPlayingBlack:
8613             machineWhite = FALSE;
8614             break;
8615
8616           case TwoMachinesPlay:
8617             machineWhite = (cps->twoMachinesColor[0] == 'w');
8618             break;
8619         }
8620         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8621             if (appData.debugMode) {
8622                 fprintf(debugFP,
8623                         "Ignoring move out of turn by %s, gameMode %d"
8624                         ", forwardMost %d\n",
8625                         cps->which, gameMode, forwardMostMove);
8626             }
8627             return;
8628         }
8629
8630         if(cps->alphaRank) AlphaRank(machineMove, 4);
8631
8632         // [HGM] lion: (some very limited) support for Alien protocol
8633         killX = killY = -1;
8634         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8635             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8636             return;
8637         } else if(firstLeg[0]) { // there was a previous leg;
8638             // only support case where same piece makes two step (and don't even test that!)
8639             char buf[20], *p = machineMove+1, *q = buf+1, f;
8640             safeStrCpy(buf, machineMove, 20);
8641             while(isdigit(*q)) q++; // find start of to-square
8642             safeStrCpy(machineMove, firstLeg, 20);
8643             while(isdigit(*p)) p++;
8644             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8645             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8646             firstLeg[0] = NULLCHAR;
8647         }
8648
8649         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8650                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8651             /* Machine move could not be parsed; ignore it. */
8652           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8653                     machineMove, _(cps->which));
8654             DisplayMoveError(buf1);
8655             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8656                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8657             if (gameMode == TwoMachinesPlay) {
8658               GameEnds(machineWhite ? BlackWins : WhiteWins,
8659                        buf1, GE_XBOARD);
8660             }
8661             return;
8662         }
8663
8664         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8665         /* So we have to redo legality test with true e.p. status here,  */
8666         /* to make sure an illegal e.p. capture does not slip through,   */
8667         /* to cause a forfeit on a justified illegal-move complaint      */
8668         /* of the opponent.                                              */
8669         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8670            ChessMove moveType;
8671            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8672                              fromY, fromX, toY, toX, promoChar);
8673             if(moveType == IllegalMove) {
8674               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8675                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8676                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8677                            buf1, GE_XBOARD);
8678                 return;
8679            } else if(!appData.fischerCastling)
8680            /* [HGM] Kludge to handle engines that send FRC-style castling
8681               when they shouldn't (like TSCP-Gothic) */
8682            switch(moveType) {
8683              case WhiteASideCastleFR:
8684              case BlackASideCastleFR:
8685                toX+=2;
8686                currentMoveString[2]++;
8687                break;
8688              case WhiteHSideCastleFR:
8689              case BlackHSideCastleFR:
8690                toX--;
8691                currentMoveString[2]--;
8692                break;
8693              default: ; // nothing to do, but suppresses warning of pedantic compilers
8694            }
8695         }
8696         hintRequested = FALSE;
8697         lastHint[0] = NULLCHAR;
8698         bookRequested = FALSE;
8699         /* Program may be pondering now */
8700         cps->maybeThinking = TRUE;
8701         if (cps->sendTime == 2) cps->sendTime = 1;
8702         if (cps->offeredDraw) cps->offeredDraw--;
8703
8704         /* [AS] Save move info*/
8705         pvInfoList[ forwardMostMove ].score = programStats.score;
8706         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8707         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8708
8709         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8710
8711         /* Test suites abort the 'game' after one move */
8712         if(*appData.finger) {
8713            static FILE *f;
8714            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8715            if(!f) f = fopen(appData.finger, "w");
8716            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8717            else { DisplayFatalError("Bad output file", errno, 0); return; }
8718            free(fen);
8719            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8720         }
8721
8722         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8723         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8724             int count = 0;
8725
8726             while( count < adjudicateLossPlies ) {
8727                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8728
8729                 if( count & 1 ) {
8730                     score = -score; /* Flip score for winning side */
8731                 }
8732
8733                 if( score > appData.adjudicateLossThreshold ) {
8734                     break;
8735                 }
8736
8737                 count++;
8738             }
8739
8740             if( count >= adjudicateLossPlies ) {
8741                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8742
8743                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8744                     "Xboard adjudication",
8745                     GE_XBOARD );
8746
8747                 return;
8748             }
8749         }
8750
8751         if(Adjudicate(cps)) {
8752             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8753             return; // [HGM] adjudicate: for all automatic game ends
8754         }
8755
8756 #if ZIPPY
8757         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8758             first.initDone) {
8759           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8760                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8761                 SendToICS("draw ");
8762                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8763           }
8764           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8765           ics_user_moved = 1;
8766           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8767                 char buf[3*MSG_SIZ];
8768
8769                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8770                         programStats.score / 100.,
8771                         programStats.depth,
8772                         programStats.time / 100.,
8773                         (unsigned int)programStats.nodes,
8774                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8775                         programStats.movelist);
8776                 SendToICS(buf);
8777           }
8778         }
8779 #endif
8780
8781         /* [AS] Clear stats for next move */
8782         ClearProgramStats();
8783         thinkOutput[0] = NULLCHAR;
8784         hiddenThinkOutputState = 0;
8785
8786         bookHit = NULL;
8787         if (gameMode == TwoMachinesPlay) {
8788             /* [HGM] relaying draw offers moved to after reception of move */
8789             /* and interpreting offer as claim if it brings draw condition */
8790             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8791                 SendToProgram("draw\n", cps->other);
8792             }
8793             if (cps->other->sendTime) {
8794                 SendTimeRemaining(cps->other,
8795                                   cps->other->twoMachinesColor[0] == 'w');
8796             }
8797             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8798             if (firstMove && !bookHit) {
8799                 firstMove = FALSE;
8800                 if (cps->other->useColors) {
8801                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8802                 }
8803                 SendToProgram("go\n", cps->other);
8804             }
8805             cps->other->maybeThinking = TRUE;
8806         }
8807
8808         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8809
8810         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8811
8812         if (!pausing && appData.ringBellAfterMoves) {
8813             if(!roar) RingBell();
8814         }
8815
8816         /*
8817          * Reenable menu items that were disabled while
8818          * machine was thinking
8819          */
8820         if (gameMode != TwoMachinesPlay)
8821             SetUserThinkingEnables();
8822
8823         // [HGM] book: after book hit opponent has received move and is now in force mode
8824         // force the book reply into it, and then fake that it outputted this move by jumping
8825         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8826         if(bookHit) {
8827                 static char bookMove[MSG_SIZ]; // a bit generous?
8828
8829                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8830                 strcat(bookMove, bookHit);
8831                 message = bookMove;
8832                 cps = cps->other;
8833                 programStats.nodes = programStats.depth = programStats.time =
8834                 programStats.score = programStats.got_only_move = 0;
8835                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8836
8837                 if(cps->lastPing != cps->lastPong) {
8838                     savedMessage = message; // args for deferred call
8839                     savedState = cps;
8840                     ScheduleDelayedEvent(DeferredBookMove, 10);
8841                     return;
8842                 }
8843                 goto FakeBookMove;
8844         }
8845
8846         return;
8847     }
8848
8849     /* Set special modes for chess engines.  Later something general
8850      *  could be added here; for now there is just one kludge feature,
8851      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8852      *  when "xboard" is given as an interactive command.
8853      */
8854     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8855         cps->useSigint = FALSE;
8856         cps->useSigterm = FALSE;
8857     }
8858     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8859       ParseFeatures(message+8, cps);
8860       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8861     }
8862
8863     if (!strncmp(message, "setup ", 6) && 
8864         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8865           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8866                                         ) { // [HGM] allow first engine to define opening position
8867       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8868       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8869       *buf = NULLCHAR;
8870       if(sscanf(message, "setup (%s", buf) == 1) {
8871         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8872         ASSIGN(appData.pieceToCharTable, buf);
8873       }
8874       if(startedFromSetupPosition) return;
8875       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8876       if(dummy >= 3) {
8877         while(message[s] && message[s++] != ' ');
8878         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8879            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8880             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8881             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8882           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8883           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8884         }
8885       }
8886       ParseFEN(boards[0], &dummy, message+s, FALSE);
8887       DrawPosition(TRUE, boards[0]);
8888       startedFromSetupPosition = TRUE;
8889       return;
8890     }
8891     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8892       ChessSquare piece = WhitePawn;
8893       char *p=buf2;
8894       if(cps != &first || appData.testLegality) return;
8895       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8896       piece += CharToPiece(*p) - WhitePawn;
8897       if(piece < EmptySquare) {
8898         pieceDefs = TRUE;
8899         ASSIGN(pieceDesc[piece], buf1);
8900         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8901       }
8902       return;
8903     }
8904     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8905      * want this, I was asked to put it in, and obliged.
8906      */
8907     if (!strncmp(message, "setboard ", 9)) {
8908         Board initial_position;
8909
8910         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8911
8912         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8913             DisplayError(_("Bad FEN received from engine"), 0);
8914             return ;
8915         } else {
8916            Reset(TRUE, FALSE);
8917            CopyBoard(boards[0], initial_position);
8918            initialRulePlies = FENrulePlies;
8919            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8920            else gameMode = MachinePlaysBlack;
8921            DrawPosition(FALSE, boards[currentMove]);
8922         }
8923         return;
8924     }
8925
8926     /*
8927      * Look for communication commands
8928      */
8929     if (!strncmp(message, "telluser ", 9)) {
8930         if(message[9] == '\\' && message[10] == '\\')
8931             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8932         PlayTellSound();
8933         DisplayNote(message + 9);
8934         return;
8935     }
8936     if (!strncmp(message, "tellusererror ", 14)) {
8937         cps->userError = 1;
8938         if(message[14] == '\\' && message[15] == '\\')
8939             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8940         PlayTellSound();
8941         DisplayError(message + 14, 0);
8942         return;
8943     }
8944     if (!strncmp(message, "tellopponent ", 13)) {
8945       if (appData.icsActive) {
8946         if (loggedOn) {
8947           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8948           SendToICS(buf1);
8949         }
8950       } else {
8951         DisplayNote(message + 13);
8952       }
8953       return;
8954     }
8955     if (!strncmp(message, "tellothers ", 11)) {
8956       if (appData.icsActive) {
8957         if (loggedOn) {
8958           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8959           SendToICS(buf1);
8960         }
8961       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8962       return;
8963     }
8964     if (!strncmp(message, "tellall ", 8)) {
8965       if (appData.icsActive) {
8966         if (loggedOn) {
8967           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8968           SendToICS(buf1);
8969         }
8970       } else {
8971         DisplayNote(message + 8);
8972       }
8973       return;
8974     }
8975     if (strncmp(message, "warning", 7) == 0) {
8976         /* Undocumented feature, use tellusererror in new code */
8977         DisplayError(message, 0);
8978         return;
8979     }
8980     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8981         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8982         strcat(realname, " query");
8983         AskQuestion(realname, buf2, buf1, cps->pr);
8984         return;
8985     }
8986     /* Commands from the engine directly to ICS.  We don't allow these to be
8987      *  sent until we are logged on. Crafty kibitzes have been known to
8988      *  interfere with the login process.
8989      */
8990     if (loggedOn) {
8991         if (!strncmp(message, "tellics ", 8)) {
8992             SendToICS(message + 8);
8993             SendToICS("\n");
8994             return;
8995         }
8996         if (!strncmp(message, "tellicsnoalias ", 15)) {
8997             SendToICS(ics_prefix);
8998             SendToICS(message + 15);
8999             SendToICS("\n");
9000             return;
9001         }
9002         /* The following are for backward compatibility only */
9003         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9004             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9005             SendToICS(ics_prefix);
9006             SendToICS(message);
9007             SendToICS("\n");
9008             return;
9009         }
9010     }
9011     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9012         if(initPing == cps->lastPong) {
9013             if(gameInfo.variant == VariantUnknown) {
9014                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9015                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9016                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9017             }
9018             initPing = -1;
9019         }
9020         return;
9021     }
9022     if(!strncmp(message, "highlight ", 10)) {
9023         if(appData.testLegality && appData.markers) return;
9024         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9025         return;
9026     }
9027     if(!strncmp(message, "click ", 6)) {
9028         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9029         if(appData.testLegality || !appData.oneClick) return;
9030         sscanf(message+6, "%c%d%c", &f, &y, &c);
9031         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9032         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9033         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9034         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9035         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9036         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9037             LeftClick(Release, lastLeftX, lastLeftY);
9038         controlKey  = (c == ',');
9039         LeftClick(Press, x, y);
9040         LeftClick(Release, x, y);
9041         first.highlight = f;
9042         return;
9043     }
9044     /*
9045      * If the move is illegal, cancel it and redraw the board.
9046      * Also deal with other error cases.  Matching is rather loose
9047      * here to accommodate engines written before the spec.
9048      */
9049     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9050         strncmp(message, "Error", 5) == 0) {
9051         if (StrStr(message, "name") ||
9052             StrStr(message, "rating") || StrStr(message, "?") ||
9053             StrStr(message, "result") || StrStr(message, "board") ||
9054             StrStr(message, "bk") || StrStr(message, "computer") ||
9055             StrStr(message, "variant") || StrStr(message, "hint") ||
9056             StrStr(message, "random") || StrStr(message, "depth") ||
9057             StrStr(message, "accepted")) {
9058             return;
9059         }
9060         if (StrStr(message, "protover")) {
9061           /* Program is responding to input, so it's apparently done
9062              initializing, and this error message indicates it is
9063              protocol version 1.  So we don't need to wait any longer
9064              for it to initialize and send feature commands. */
9065           FeatureDone(cps, 1);
9066           cps->protocolVersion = 1;
9067           return;
9068         }
9069         cps->maybeThinking = FALSE;
9070
9071         if (StrStr(message, "draw")) {
9072             /* Program doesn't have "draw" command */
9073             cps->sendDrawOffers = 0;
9074             return;
9075         }
9076         if (cps->sendTime != 1 &&
9077             (StrStr(message, "time") || StrStr(message, "otim"))) {
9078           /* Program apparently doesn't have "time" or "otim" command */
9079           cps->sendTime = 0;
9080           return;
9081         }
9082         if (StrStr(message, "analyze")) {
9083             cps->analysisSupport = FALSE;
9084             cps->analyzing = FALSE;
9085 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9086             EditGameEvent(); // [HGM] try to preserve loaded game
9087             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9088             DisplayError(buf2, 0);
9089             return;
9090         }
9091         if (StrStr(message, "(no matching move)st")) {
9092           /* Special kludge for GNU Chess 4 only */
9093           cps->stKludge = TRUE;
9094           SendTimeControl(cps, movesPerSession, timeControl,
9095                           timeIncrement, appData.searchDepth,
9096                           searchTime);
9097           return;
9098         }
9099         if (StrStr(message, "(no matching move)sd")) {
9100           /* Special kludge for GNU Chess 4 only */
9101           cps->sdKludge = TRUE;
9102           SendTimeControl(cps, movesPerSession, timeControl,
9103                           timeIncrement, appData.searchDepth,
9104                           searchTime);
9105           return;
9106         }
9107         if (!StrStr(message, "llegal")) {
9108             return;
9109         }
9110         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9111             gameMode == IcsIdle) return;
9112         if (forwardMostMove <= backwardMostMove) return;
9113         if (pausing) PauseEvent();
9114       if(appData.forceIllegal) {
9115             // [HGM] illegal: machine refused move; force position after move into it
9116           SendToProgram("force\n", cps);
9117           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9118                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9119                 // when black is to move, while there might be nothing on a2 or black
9120                 // might already have the move. So send the board as if white has the move.
9121                 // But first we must change the stm of the engine, as it refused the last move
9122                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9123                 if(WhiteOnMove(forwardMostMove)) {
9124                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9125                     SendBoard(cps, forwardMostMove); // kludgeless board
9126                 } else {
9127                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9128                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9129                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9130                 }
9131           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9132             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9133                  gameMode == TwoMachinesPlay)
9134               SendToProgram("go\n", cps);
9135             return;
9136       } else
9137         if (gameMode == PlayFromGameFile) {
9138             /* Stop reading this game file */
9139             gameMode = EditGame;
9140             ModeHighlight();
9141         }
9142         /* [HGM] illegal-move claim should forfeit game when Xboard */
9143         /* only passes fully legal moves                            */
9144         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9145             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9146                                 "False illegal-move claim", GE_XBOARD );
9147             return; // do not take back move we tested as valid
9148         }
9149         currentMove = forwardMostMove-1;
9150         DisplayMove(currentMove-1); /* before DisplayMoveError */
9151         SwitchClocks(forwardMostMove-1); // [HGM] race
9152         DisplayBothClocks();
9153         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9154                 parseList[currentMove], _(cps->which));
9155         DisplayMoveError(buf1);
9156         DrawPosition(FALSE, boards[currentMove]);
9157
9158         SetUserThinkingEnables();
9159         return;
9160     }
9161     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9162         /* Program has a broken "time" command that
9163            outputs a string not ending in newline.
9164            Don't use it. */
9165         cps->sendTime = 0;
9166     }
9167     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9168         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9169             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9170     }
9171
9172     /*
9173      * If chess program startup fails, exit with an error message.
9174      * Attempts to recover here are futile. [HGM] Well, we try anyway
9175      */
9176     if ((StrStr(message, "unknown host") != NULL)
9177         || (StrStr(message, "No remote directory") != NULL)
9178         || (StrStr(message, "not found") != NULL)
9179         || (StrStr(message, "No such file") != NULL)
9180         || (StrStr(message, "can't alloc") != NULL)
9181         || (StrStr(message, "Permission denied") != NULL)) {
9182
9183         cps->maybeThinking = FALSE;
9184         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9185                 _(cps->which), cps->program, cps->host, message);
9186         RemoveInputSource(cps->isr);
9187         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9188             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9189             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9190         }
9191         return;
9192     }
9193
9194     /*
9195      * Look for hint output
9196      */
9197     if (sscanf(message, "Hint: %s", buf1) == 1) {
9198         if (cps == &first && hintRequested) {
9199             hintRequested = FALSE;
9200             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9201                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9202                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9203                                     PosFlags(forwardMostMove),
9204                                     fromY, fromX, toY, toX, promoChar, buf1);
9205                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9206                 DisplayInformation(buf2);
9207             } else {
9208                 /* Hint move could not be parsed!? */
9209               snprintf(buf2, sizeof(buf2),
9210                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9211                         buf1, _(cps->which));
9212                 DisplayError(buf2, 0);
9213             }
9214         } else {
9215           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9216         }
9217         return;
9218     }
9219
9220     /*
9221      * Ignore other messages if game is not in progress
9222      */
9223     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9224         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9225
9226     /*
9227      * look for win, lose, draw, or draw offer
9228      */
9229     if (strncmp(message, "1-0", 3) == 0) {
9230         char *p, *q, *r = "";
9231         p = strchr(message, '{');
9232         if (p) {
9233             q = strchr(p, '}');
9234             if (q) {
9235                 *q = NULLCHAR;
9236                 r = p + 1;
9237             }
9238         }
9239         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9240         return;
9241     } else if (strncmp(message, "0-1", 3) == 0) {
9242         char *p, *q, *r = "";
9243         p = strchr(message, '{');
9244         if (p) {
9245             q = strchr(p, '}');
9246             if (q) {
9247                 *q = NULLCHAR;
9248                 r = p + 1;
9249             }
9250         }
9251         /* Kludge for Arasan 4.1 bug */
9252         if (strcmp(r, "Black resigns") == 0) {
9253             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9254             return;
9255         }
9256         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9257         return;
9258     } else if (strncmp(message, "1/2", 3) == 0) {
9259         char *p, *q, *r = "";
9260         p = strchr(message, '{');
9261         if (p) {
9262             q = strchr(p, '}');
9263             if (q) {
9264                 *q = NULLCHAR;
9265                 r = p + 1;
9266             }
9267         }
9268
9269         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9270         return;
9271
9272     } else if (strncmp(message, "White resign", 12) == 0) {
9273         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9274         return;
9275     } else if (strncmp(message, "Black resign", 12) == 0) {
9276         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9277         return;
9278     } else if (strncmp(message, "White matches", 13) == 0 ||
9279                strncmp(message, "Black matches", 13) == 0   ) {
9280         /* [HGM] ignore GNUShogi noises */
9281         return;
9282     } else if (strncmp(message, "White", 5) == 0 &&
9283                message[5] != '(' &&
9284                StrStr(message, "Black") == NULL) {
9285         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9286         return;
9287     } else if (strncmp(message, "Black", 5) == 0 &&
9288                message[5] != '(') {
9289         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9290         return;
9291     } else if (strcmp(message, "resign") == 0 ||
9292                strcmp(message, "computer resigns") == 0) {
9293         switch (gameMode) {
9294           case MachinePlaysBlack:
9295           case IcsPlayingBlack:
9296             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9297             break;
9298           case MachinePlaysWhite:
9299           case IcsPlayingWhite:
9300             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9301             break;
9302           case TwoMachinesPlay:
9303             if (cps->twoMachinesColor[0] == 'w')
9304               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9305             else
9306               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9307             break;
9308           default:
9309             /* can't happen */
9310             break;
9311         }
9312         return;
9313     } else if (strncmp(message, "opponent mates", 14) == 0) {
9314         switch (gameMode) {
9315           case MachinePlaysBlack:
9316           case IcsPlayingBlack:
9317             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9318             break;
9319           case MachinePlaysWhite:
9320           case IcsPlayingWhite:
9321             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9322             break;
9323           case TwoMachinesPlay:
9324             if (cps->twoMachinesColor[0] == 'w')
9325               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9326             else
9327               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9328             break;
9329           default:
9330             /* can't happen */
9331             break;
9332         }
9333         return;
9334     } else if (strncmp(message, "computer mates", 14) == 0) {
9335         switch (gameMode) {
9336           case MachinePlaysBlack:
9337           case IcsPlayingBlack:
9338             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9339             break;
9340           case MachinePlaysWhite:
9341           case IcsPlayingWhite:
9342             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9343             break;
9344           case TwoMachinesPlay:
9345             if (cps->twoMachinesColor[0] == 'w')
9346               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9347             else
9348               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9349             break;
9350           default:
9351             /* can't happen */
9352             break;
9353         }
9354         return;
9355     } else if (strncmp(message, "checkmate", 9) == 0) {
9356         if (WhiteOnMove(forwardMostMove)) {
9357             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9358         } else {
9359             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9360         }
9361         return;
9362     } else if (strstr(message, "Draw") != NULL ||
9363                strstr(message, "game is a draw") != NULL) {
9364         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9365         return;
9366     } else if (strstr(message, "offer") != NULL &&
9367                strstr(message, "draw") != NULL) {
9368 #if ZIPPY
9369         if (appData.zippyPlay && first.initDone) {
9370             /* Relay offer to ICS */
9371             SendToICS(ics_prefix);
9372             SendToICS("draw\n");
9373         }
9374 #endif
9375         cps->offeredDraw = 2; /* valid until this engine moves twice */
9376         if (gameMode == TwoMachinesPlay) {
9377             if (cps->other->offeredDraw) {
9378                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9379             /* [HGM] in two-machine mode we delay relaying draw offer      */
9380             /* until after we also have move, to see if it is really claim */
9381             }
9382         } else if (gameMode == MachinePlaysWhite ||
9383                    gameMode == MachinePlaysBlack) {
9384           if (userOfferedDraw) {
9385             DisplayInformation(_("Machine accepts your draw offer"));
9386             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9387           } else {
9388             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9389           }
9390         }
9391     }
9392
9393
9394     /*
9395      * Look for thinking output
9396      */
9397     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9398           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9399                                 ) {
9400         int plylev, mvleft, mvtot, curscore, time;
9401         char mvname[MOVE_LEN];
9402         u64 nodes; // [DM]
9403         char plyext;
9404         int ignore = FALSE;
9405         int prefixHint = FALSE;
9406         mvname[0] = NULLCHAR;
9407
9408         switch (gameMode) {
9409           case MachinePlaysBlack:
9410           case IcsPlayingBlack:
9411             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9412             break;
9413           case MachinePlaysWhite:
9414           case IcsPlayingWhite:
9415             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9416             break;
9417           case AnalyzeMode:
9418           case AnalyzeFile:
9419             break;
9420           case IcsObserving: /* [DM] icsEngineAnalyze */
9421             if (!appData.icsEngineAnalyze) ignore = TRUE;
9422             break;
9423           case TwoMachinesPlay:
9424             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9425                 ignore = TRUE;
9426             }
9427             break;
9428           default:
9429             ignore = TRUE;
9430             break;
9431         }
9432
9433         if (!ignore) {
9434             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9435             buf1[0] = NULLCHAR;
9436             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9437                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9438
9439                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9440                     nodes += u64Const(0x100000000);
9441
9442                 if (plyext != ' ' && plyext != '\t') {
9443                     time *= 100;
9444                 }
9445
9446                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9447                 if( cps->scoreIsAbsolute &&
9448                     ( gameMode == MachinePlaysBlack ||
9449                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9450                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9451                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9452                      !WhiteOnMove(currentMove)
9453                     ) )
9454                 {
9455                     curscore = -curscore;
9456                 }
9457
9458                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9459
9460                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9461                         char buf[MSG_SIZ];
9462                         FILE *f;
9463                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9464                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9465                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9466                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9467                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9468                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9469                                 fclose(f);
9470                         }
9471                         else
9472                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9473                           DisplayError(_("failed writing PV"), 0);
9474                 }
9475
9476                 tempStats.depth = plylev;
9477                 tempStats.nodes = nodes;
9478                 tempStats.time = time;
9479                 tempStats.score = curscore;
9480                 tempStats.got_only_move = 0;
9481
9482                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9483                         int ticklen;
9484
9485                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9486                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9487                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9488                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9489                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9490                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9491                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9492                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9493                 }
9494
9495                 /* Buffer overflow protection */
9496                 if (pv[0] != NULLCHAR) {
9497                     if (strlen(pv) >= sizeof(tempStats.movelist)
9498                         && appData.debugMode) {
9499                         fprintf(debugFP,
9500                                 "PV is too long; using the first %u bytes.\n",
9501                                 (unsigned) sizeof(tempStats.movelist) - 1);
9502                     }
9503
9504                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9505                 } else {
9506                     sprintf(tempStats.movelist, " no PV\n");
9507                 }
9508
9509                 if (tempStats.seen_stat) {
9510                     tempStats.ok_to_send = 1;
9511                 }
9512
9513                 if (strchr(tempStats.movelist, '(') != NULL) {
9514                     tempStats.line_is_book = 1;
9515                     tempStats.nr_moves = 0;
9516                     tempStats.moves_left = 0;
9517                 } else {
9518                     tempStats.line_is_book = 0;
9519                 }
9520
9521                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9522                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9523
9524                 SendProgramStatsToFrontend( cps, &tempStats );
9525
9526                 /*
9527                     [AS] Protect the thinkOutput buffer from overflow... this
9528                     is only useful if buf1 hasn't overflowed first!
9529                 */
9530                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9531                          plylev,
9532                          (gameMode == TwoMachinesPlay ?
9533                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9534                          ((double) curscore) / 100.0,
9535                          prefixHint ? lastHint : "",
9536                          prefixHint ? " " : "" );
9537
9538                 if( buf1[0] != NULLCHAR ) {
9539                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9540
9541                     if( strlen(pv) > max_len ) {
9542                         if( appData.debugMode) {
9543                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9544                         }
9545                         pv[max_len+1] = '\0';
9546                     }
9547
9548                     strcat( thinkOutput, pv);
9549                 }
9550
9551                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9552                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9553                     DisplayMove(currentMove - 1);
9554                 }
9555                 return;
9556
9557             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9558                 /* crafty (9.25+) says "(only move) <move>"
9559                  * if there is only 1 legal move
9560                  */
9561                 sscanf(p, "(only move) %s", buf1);
9562                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9563                 sprintf(programStats.movelist, "%s (only move)", buf1);
9564                 programStats.depth = 1;
9565                 programStats.nr_moves = 1;
9566                 programStats.moves_left = 1;
9567                 programStats.nodes = 1;
9568                 programStats.time = 1;
9569                 programStats.got_only_move = 1;
9570
9571                 /* Not really, but we also use this member to
9572                    mean "line isn't going to change" (Crafty
9573                    isn't searching, so stats won't change) */
9574                 programStats.line_is_book = 1;
9575
9576                 SendProgramStatsToFrontend( cps, &programStats );
9577
9578                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9579                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9580                     DisplayMove(currentMove - 1);
9581                 }
9582                 return;
9583             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9584                               &time, &nodes, &plylev, &mvleft,
9585                               &mvtot, mvname) >= 5) {
9586                 /* The stat01: line is from Crafty (9.29+) in response
9587                    to the "." command */
9588                 programStats.seen_stat = 1;
9589                 cps->maybeThinking = TRUE;
9590
9591                 if (programStats.got_only_move || !appData.periodicUpdates)
9592                   return;
9593
9594                 programStats.depth = plylev;
9595                 programStats.time = time;
9596                 programStats.nodes = nodes;
9597                 programStats.moves_left = mvleft;
9598                 programStats.nr_moves = mvtot;
9599                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9600                 programStats.ok_to_send = 1;
9601                 programStats.movelist[0] = '\0';
9602
9603                 SendProgramStatsToFrontend( cps, &programStats );
9604
9605                 return;
9606
9607             } else if (strncmp(message,"++",2) == 0) {
9608                 /* Crafty 9.29+ outputs this */
9609                 programStats.got_fail = 2;
9610                 return;
9611
9612             } else if (strncmp(message,"--",2) == 0) {
9613                 /* Crafty 9.29+ outputs this */
9614                 programStats.got_fail = 1;
9615                 return;
9616
9617             } else if (thinkOutput[0] != NULLCHAR &&
9618                        strncmp(message, "    ", 4) == 0) {
9619                 unsigned message_len;
9620
9621                 p = message;
9622                 while (*p && *p == ' ') p++;
9623
9624                 message_len = strlen( p );
9625
9626                 /* [AS] Avoid buffer overflow */
9627                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9628                     strcat(thinkOutput, " ");
9629                     strcat(thinkOutput, p);
9630                 }
9631
9632                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9633                     strcat(programStats.movelist, " ");
9634                     strcat(programStats.movelist, p);
9635                 }
9636
9637                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9638                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9639                     DisplayMove(currentMove - 1);
9640                 }
9641                 return;
9642             }
9643         }
9644         else {
9645             buf1[0] = NULLCHAR;
9646
9647             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9648                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9649             {
9650                 ChessProgramStats cpstats;
9651
9652                 if (plyext != ' ' && plyext != '\t') {
9653                     time *= 100;
9654                 }
9655
9656                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9657                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9658                     curscore = -curscore;
9659                 }
9660
9661                 cpstats.depth = plylev;
9662                 cpstats.nodes = nodes;
9663                 cpstats.time = time;
9664                 cpstats.score = curscore;
9665                 cpstats.got_only_move = 0;
9666                 cpstats.movelist[0] = '\0';
9667
9668                 if (buf1[0] != NULLCHAR) {
9669                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9670                 }
9671
9672                 cpstats.ok_to_send = 0;
9673                 cpstats.line_is_book = 0;
9674                 cpstats.nr_moves = 0;
9675                 cpstats.moves_left = 0;
9676
9677                 SendProgramStatsToFrontend( cps, &cpstats );
9678             }
9679         }
9680     }
9681 }
9682
9683
9684 /* Parse a game score from the character string "game", and
9685    record it as the history of the current game.  The game
9686    score is NOT assumed to start from the standard position.
9687    The display is not updated in any way.
9688    */
9689 void
9690 ParseGameHistory (char *game)
9691 {
9692     ChessMove moveType;
9693     int fromX, fromY, toX, toY, boardIndex;
9694     char promoChar;
9695     char *p, *q;
9696     char buf[MSG_SIZ];
9697
9698     if (appData.debugMode)
9699       fprintf(debugFP, "Parsing game history: %s\n", game);
9700
9701     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9702     gameInfo.site = StrSave(appData.icsHost);
9703     gameInfo.date = PGNDate();
9704     gameInfo.round = StrSave("-");
9705
9706     /* Parse out names of players */
9707     while (*game == ' ') game++;
9708     p = buf;
9709     while (*game != ' ') *p++ = *game++;
9710     *p = NULLCHAR;
9711     gameInfo.white = StrSave(buf);
9712     while (*game == ' ') game++;
9713     p = buf;
9714     while (*game != ' ' && *game != '\n') *p++ = *game++;
9715     *p = NULLCHAR;
9716     gameInfo.black = StrSave(buf);
9717
9718     /* Parse moves */
9719     boardIndex = blackPlaysFirst ? 1 : 0;
9720     yynewstr(game);
9721     for (;;) {
9722         yyboardindex = boardIndex;
9723         moveType = (ChessMove) Myylex();
9724         switch (moveType) {
9725           case IllegalMove:             /* maybe suicide chess, etc. */
9726   if (appData.debugMode) {
9727     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9728     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9729     setbuf(debugFP, NULL);
9730   }
9731           case WhitePromotion:
9732           case BlackPromotion:
9733           case WhiteNonPromotion:
9734           case BlackNonPromotion:
9735           case NormalMove:
9736           case FirstLeg:
9737           case WhiteCapturesEnPassant:
9738           case BlackCapturesEnPassant:
9739           case WhiteKingSideCastle:
9740           case WhiteQueenSideCastle:
9741           case BlackKingSideCastle:
9742           case BlackQueenSideCastle:
9743           case WhiteKingSideCastleWild:
9744           case WhiteQueenSideCastleWild:
9745           case BlackKingSideCastleWild:
9746           case BlackQueenSideCastleWild:
9747           /* PUSH Fabien */
9748           case WhiteHSideCastleFR:
9749           case WhiteASideCastleFR:
9750           case BlackHSideCastleFR:
9751           case BlackASideCastleFR:
9752           /* POP Fabien */
9753             fromX = currentMoveString[0] - AAA;
9754             fromY = currentMoveString[1] - ONE;
9755             toX = currentMoveString[2] - AAA;
9756             toY = currentMoveString[3] - ONE;
9757             promoChar = currentMoveString[4];
9758             break;
9759           case WhiteDrop:
9760           case BlackDrop:
9761             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9762             fromX = moveType == WhiteDrop ?
9763               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9764             (int) CharToPiece(ToLower(currentMoveString[0]));
9765             fromY = DROP_RANK;
9766             toX = currentMoveString[2] - AAA;
9767             toY = currentMoveString[3] - ONE;
9768             promoChar = NULLCHAR;
9769             break;
9770           case AmbiguousMove:
9771             /* bug? */
9772             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9773   if (appData.debugMode) {
9774     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9775     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9776     setbuf(debugFP, NULL);
9777   }
9778             DisplayError(buf, 0);
9779             return;
9780           case ImpossibleMove:
9781             /* bug? */
9782             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9783   if (appData.debugMode) {
9784     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9785     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9786     setbuf(debugFP, NULL);
9787   }
9788             DisplayError(buf, 0);
9789             return;
9790           case EndOfFile:
9791             if (boardIndex < backwardMostMove) {
9792                 /* Oops, gap.  How did that happen? */
9793                 DisplayError(_("Gap in move list"), 0);
9794                 return;
9795             }
9796             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9797             if (boardIndex > forwardMostMove) {
9798                 forwardMostMove = boardIndex;
9799             }
9800             return;
9801           case ElapsedTime:
9802             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9803                 strcat(parseList[boardIndex-1], " ");
9804                 strcat(parseList[boardIndex-1], yy_text);
9805             }
9806             continue;
9807           case Comment:
9808           case PGNTag:
9809           case NAG:
9810           default:
9811             /* ignore */
9812             continue;
9813           case WhiteWins:
9814           case BlackWins:
9815           case GameIsDrawn:
9816           case GameUnfinished:
9817             if (gameMode == IcsExamining) {
9818                 if (boardIndex < backwardMostMove) {
9819                     /* Oops, gap.  How did that happen? */
9820                     return;
9821                 }
9822                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9823                 return;
9824             }
9825             gameInfo.result = moveType;
9826             p = strchr(yy_text, '{');
9827             if (p == NULL) p = strchr(yy_text, '(');
9828             if (p == NULL) {
9829                 p = yy_text;
9830                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9831             } else {
9832                 q = strchr(p, *p == '{' ? '}' : ')');
9833                 if (q != NULL) *q = NULLCHAR;
9834                 p++;
9835             }
9836             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9837             gameInfo.resultDetails = StrSave(p);
9838             continue;
9839         }
9840         if (boardIndex >= forwardMostMove &&
9841             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9842             backwardMostMove = blackPlaysFirst ? 1 : 0;
9843             return;
9844         }
9845         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9846                                  fromY, fromX, toY, toX, promoChar,
9847                                  parseList[boardIndex]);
9848         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9849         /* currentMoveString is set as a side-effect of yylex */
9850         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9851         strcat(moveList[boardIndex], "\n");
9852         boardIndex++;
9853         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9854         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9855           case MT_NONE:
9856           case MT_STALEMATE:
9857           default:
9858             break;
9859           case MT_CHECK:
9860             if(!IS_SHOGI(gameInfo.variant))
9861                 strcat(parseList[boardIndex - 1], "+");
9862             break;
9863           case MT_CHECKMATE:
9864           case MT_STAINMATE:
9865             strcat(parseList[boardIndex - 1], "#");
9866             break;
9867         }
9868     }
9869 }
9870
9871
9872 /* Apply a move to the given board  */
9873 void
9874 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9875 {
9876   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9877   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9878
9879     /* [HGM] compute & store e.p. status and castling rights for new position */
9880     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9881
9882       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9883       oldEP = (signed char)board[EP_STATUS];
9884       board[EP_STATUS] = EP_NONE;
9885       board[EP_FILE] = board[EP_RANK] = 100;
9886
9887   if (fromY == DROP_RANK) {
9888         /* must be first */
9889         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9890             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9891             return;
9892         }
9893         piece = board[toY][toX] = (ChessSquare) fromX;
9894   } else {
9895 //      ChessSquare victim;
9896       int i;
9897
9898       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9899 //           victim = board[killY][killX],
9900            board[killY][killX] = EmptySquare,
9901            board[EP_STATUS] = EP_CAPTURE;
9902
9903       if( board[toY][toX] != EmptySquare ) {
9904            board[EP_STATUS] = EP_CAPTURE;
9905            if( (fromX != toX || fromY != toY) && // not igui!
9906                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9907                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9908                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9909            }
9910       }
9911
9912       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9913            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9914                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9915       } else
9916       if( board[fromY][fromX] == WhitePawn ) {
9917            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9918                board[EP_STATUS] = EP_PAWN_MOVE;
9919            if( toY-fromY==2) {
9920                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
9921                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9922                         gameInfo.variant != VariantBerolina || toX < fromX)
9923                       board[EP_STATUS] = toX | berolina;
9924                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9925                         gameInfo.variant != VariantBerolina || toX > fromX)
9926                       board[EP_STATUS] = toX;
9927            }
9928       } else
9929       if( board[fromY][fromX] == BlackPawn ) {
9930            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9931                board[EP_STATUS] = EP_PAWN_MOVE;
9932            if( toY-fromY== -2) {
9933                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
9934                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9935                         gameInfo.variant != VariantBerolina || toX < fromX)
9936                       board[EP_STATUS] = toX | berolina;
9937                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9938                         gameInfo.variant != VariantBerolina || toX > fromX)
9939                       board[EP_STATUS] = toX;
9940            }
9941        }
9942
9943        for(i=0; i<nrCastlingRights; i++) {
9944            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9945               board[CASTLING][i] == toX   && castlingRank[i] == toY
9946              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9947        }
9948
9949        if(gameInfo.variant == VariantSChess) { // update virginity
9950            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9951            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9952            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9953            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9954        }
9955
9956      if (fromX == toX && fromY == toY) return;
9957
9958      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9959      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9960      if(gameInfo.variant == VariantKnightmate)
9961          king += (int) WhiteUnicorn - (int) WhiteKing;
9962
9963     /* Code added by Tord: */
9964     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9965     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9966         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9967       board[fromY][fromX] = EmptySquare;
9968       board[toY][toX] = EmptySquare;
9969       if((toX > fromX) != (piece == WhiteRook)) {
9970         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9971       } else {
9972         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9973       }
9974     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9975                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9976       board[fromY][fromX] = EmptySquare;
9977       board[toY][toX] = EmptySquare;
9978       if((toX > fromX) != (piece == BlackRook)) {
9979         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9980       } else {
9981         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9982       }
9983     /* End of code added by Tord */
9984
9985     } else if (board[fromY][fromX] == king
9986         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9987         && toY == fromY && toX > fromX+1) {
9988         board[fromY][fromX] = EmptySquare;
9989         board[toY][toX] = king;
9990         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9991         board[fromY][BOARD_RGHT-1] = EmptySquare;
9992     } else if (board[fromY][fromX] == king
9993         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9994                && toY == fromY && toX < fromX-1) {
9995         board[fromY][fromX] = EmptySquare;
9996         board[toY][toX] = king;
9997         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9998         board[fromY][BOARD_LEFT] = EmptySquare;
9999     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10000                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10001                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10002                ) {
10003         /* white pawn promotion */
10004         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10005         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10006             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10007         board[fromY][fromX] = EmptySquare;
10008     } else if ((fromY >= BOARD_HEIGHT>>1)
10009                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10010                && (toX != fromX)
10011                && gameInfo.variant != VariantXiangqi
10012                && gameInfo.variant != VariantBerolina
10013                && (board[fromY][fromX] == WhitePawn)
10014                && (board[toY][toX] == EmptySquare)) {
10015         board[fromY][fromX] = EmptySquare;
10016         board[toY][toX] = WhitePawn;
10017         captured = board[toY - 1][toX];
10018         board[toY - 1][toX] = EmptySquare;
10019     } else if ((fromY == BOARD_HEIGHT-4)
10020                && (toX == fromX)
10021                && gameInfo.variant == VariantBerolina
10022                && (board[fromY][fromX] == WhitePawn)
10023                && (board[toY][toX] == EmptySquare)) {
10024         board[fromY][fromX] = EmptySquare;
10025         board[toY][toX] = WhitePawn;
10026         if(oldEP & EP_BEROLIN_A) {
10027                 captured = board[fromY][fromX-1];
10028                 board[fromY][fromX-1] = EmptySquare;
10029         }else{  captured = board[fromY][fromX+1];
10030                 board[fromY][fromX+1] = EmptySquare;
10031         }
10032     } else if (board[fromY][fromX] == king
10033         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10034                && toY == fromY && toX > fromX+1) {
10035         board[fromY][fromX] = EmptySquare;
10036         board[toY][toX] = king;
10037         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10038         board[fromY][BOARD_RGHT-1] = EmptySquare;
10039     } else if (board[fromY][fromX] == king
10040         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10041                && toY == fromY && toX < fromX-1) {
10042         board[fromY][fromX] = EmptySquare;
10043         board[toY][toX] = king;
10044         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10045         board[fromY][BOARD_LEFT] = EmptySquare;
10046     } else if (fromY == 7 && fromX == 3
10047                && board[fromY][fromX] == BlackKing
10048                && toY == 7 && toX == 5) {
10049         board[fromY][fromX] = EmptySquare;
10050         board[toY][toX] = BlackKing;
10051         board[fromY][7] = EmptySquare;
10052         board[toY][4] = BlackRook;
10053     } else if (fromY == 7 && fromX == 3
10054                && board[fromY][fromX] == BlackKing
10055                && toY == 7 && toX == 1) {
10056         board[fromY][fromX] = EmptySquare;
10057         board[toY][toX] = BlackKing;
10058         board[fromY][0] = EmptySquare;
10059         board[toY][2] = BlackRook;
10060     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10061                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10062                && toY < promoRank && promoChar
10063                ) {
10064         /* black pawn promotion */
10065         board[toY][toX] = CharToPiece(ToLower(promoChar));
10066         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10067             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10068         board[fromY][fromX] = EmptySquare;
10069     } else if ((fromY < BOARD_HEIGHT>>1)
10070                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10071                && (toX != fromX)
10072                && gameInfo.variant != VariantXiangqi
10073                && gameInfo.variant != VariantBerolina
10074                && (board[fromY][fromX] == BlackPawn)
10075                && (board[toY][toX] == EmptySquare)) {
10076         board[fromY][fromX] = EmptySquare;
10077         board[toY][toX] = BlackPawn;
10078         captured = board[toY + 1][toX];
10079         board[toY + 1][toX] = EmptySquare;
10080     } else if ((fromY == 3)
10081                && (toX == fromX)
10082                && gameInfo.variant == VariantBerolina
10083                && (board[fromY][fromX] == BlackPawn)
10084                && (board[toY][toX] == EmptySquare)) {
10085         board[fromY][fromX] = EmptySquare;
10086         board[toY][toX] = BlackPawn;
10087         if(oldEP & EP_BEROLIN_A) {
10088                 captured = board[fromY][fromX-1];
10089                 board[fromY][fromX-1] = EmptySquare;
10090         }else{  captured = board[fromY][fromX+1];
10091                 board[fromY][fromX+1] = EmptySquare;
10092         }
10093     } else {
10094         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10095         board[fromY][fromX] = EmptySquare;
10096         board[toY][toX] = piece;
10097     }
10098   }
10099
10100     if (gameInfo.holdingsWidth != 0) {
10101
10102       /* !!A lot more code needs to be written to support holdings  */
10103       /* [HGM] OK, so I have written it. Holdings are stored in the */
10104       /* penultimate board files, so they are automaticlly stored   */
10105       /* in the game history.                                       */
10106       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10107                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10108         /* Delete from holdings, by decreasing count */
10109         /* and erasing image if necessary            */
10110         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10111         if(p < (int) BlackPawn) { /* white drop */
10112              p -= (int)WhitePawn;
10113                  p = PieceToNumber((ChessSquare)p);
10114              if(p >= gameInfo.holdingsSize) p = 0;
10115              if(--board[p][BOARD_WIDTH-2] <= 0)
10116                   board[p][BOARD_WIDTH-1] = EmptySquare;
10117              if((int)board[p][BOARD_WIDTH-2] < 0)
10118                         board[p][BOARD_WIDTH-2] = 0;
10119         } else {                  /* black drop */
10120              p -= (int)BlackPawn;
10121                  p = PieceToNumber((ChessSquare)p);
10122              if(p >= gameInfo.holdingsSize) p = 0;
10123              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10124                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10125              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10126                         board[BOARD_HEIGHT-1-p][1] = 0;
10127         }
10128       }
10129       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10130           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10131         /* [HGM] holdings: Add to holdings, if holdings exist */
10132         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10133                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10134                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10135         }
10136         p = (int) captured;
10137         if (p >= (int) BlackPawn) {
10138           p -= (int)BlackPawn;
10139           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10140                   /* in Shogi restore piece to its original  first */
10141                   captured = (ChessSquare) (DEMOTED captured);
10142                   p = DEMOTED p;
10143           }
10144           p = PieceToNumber((ChessSquare)p);
10145           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10146           board[p][BOARD_WIDTH-2]++;
10147           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10148         } else {
10149           p -= (int)WhitePawn;
10150           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10151                   captured = (ChessSquare) (DEMOTED captured);
10152                   p = DEMOTED p;
10153           }
10154           p = PieceToNumber((ChessSquare)p);
10155           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10156           board[BOARD_HEIGHT-1-p][1]++;
10157           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10158         }
10159       }
10160     } else if (gameInfo.variant == VariantAtomic) {
10161       if (captured != EmptySquare) {
10162         int y, x;
10163         for (y = toY-1; y <= toY+1; y++) {
10164           for (x = toX-1; x <= toX+1; x++) {
10165             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10166                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10167               board[y][x] = EmptySquare;
10168             }
10169           }
10170         }
10171         board[toY][toX] = EmptySquare;
10172       }
10173     }
10174
10175     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10176         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10177     } else
10178     if(promoChar == '+') {
10179         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10180         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10181         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10182           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10183     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10184         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10185         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10186            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10187         board[toY][toX] = newPiece;
10188     }
10189     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10190                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10191         // [HGM] superchess: take promotion piece out of holdings
10192         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10193         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10194             if(!--board[k][BOARD_WIDTH-2])
10195                 board[k][BOARD_WIDTH-1] = EmptySquare;
10196         } else {
10197             if(!--board[BOARD_HEIGHT-1-k][1])
10198                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10199         }
10200     }
10201 }
10202
10203 /* Updates forwardMostMove */
10204 void
10205 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10206 {
10207     int x = toX, y = toY;
10208     char *s = parseList[forwardMostMove];
10209     ChessSquare p = boards[forwardMostMove][toY][toX];
10210 //    forwardMostMove++; // [HGM] bare: moved downstream
10211
10212     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10213     (void) CoordsToAlgebraic(boards[forwardMostMove],
10214                              PosFlags(forwardMostMove),
10215                              fromY, fromX, y, x, promoChar,
10216                              s);
10217     if(killX >= 0 && killY >= 0)
10218         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10219
10220     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10221         int timeLeft; static int lastLoadFlag=0; int king, piece;
10222         piece = boards[forwardMostMove][fromY][fromX];
10223         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10224         if(gameInfo.variant == VariantKnightmate)
10225             king += (int) WhiteUnicorn - (int) WhiteKing;
10226         if(forwardMostMove == 0) {
10227             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10228                 fprintf(serverMoves, "%s;", UserName());
10229             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10230                 fprintf(serverMoves, "%s;", second.tidy);
10231             fprintf(serverMoves, "%s;", first.tidy);
10232             if(gameMode == MachinePlaysWhite)
10233                 fprintf(serverMoves, "%s;", UserName());
10234             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10235                 fprintf(serverMoves, "%s;", second.tidy);
10236         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10237         lastLoadFlag = loadFlag;
10238         // print base move
10239         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10240         // print castling suffix
10241         if( toY == fromY && piece == king ) {
10242             if(toX-fromX > 1)
10243                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10244             if(fromX-toX >1)
10245                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10246         }
10247         // e.p. suffix
10248         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10249              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10250              boards[forwardMostMove][toY][toX] == EmptySquare
10251              && fromX != toX && fromY != toY)
10252                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10253         // promotion suffix
10254         if(promoChar != NULLCHAR) {
10255             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10256                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10257                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10258             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10259         }
10260         if(!loadFlag) {
10261                 char buf[MOVE_LEN*2], *p; int len;
10262             fprintf(serverMoves, "/%d/%d",
10263                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10264             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10265             else                      timeLeft = blackTimeRemaining/1000;
10266             fprintf(serverMoves, "/%d", timeLeft);
10267                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10268                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10269                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10270                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10271             fprintf(serverMoves, "/%s", buf);
10272         }
10273         fflush(serverMoves);
10274     }
10275
10276     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10277         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10278       return;
10279     }
10280     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10281     if (commentList[forwardMostMove+1] != NULL) {
10282         free(commentList[forwardMostMove+1]);
10283         commentList[forwardMostMove+1] = NULL;
10284     }
10285     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10286     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10287     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10288     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10289     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10290     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10291     adjustedClock = FALSE;
10292     gameInfo.result = GameUnfinished;
10293     if (gameInfo.resultDetails != NULL) {
10294         free(gameInfo.resultDetails);
10295         gameInfo.resultDetails = NULL;
10296     }
10297     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10298                               moveList[forwardMostMove - 1]);
10299     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10300       case MT_NONE:
10301       case MT_STALEMATE:
10302       default:
10303         break;
10304       case MT_CHECK:
10305         if(!IS_SHOGI(gameInfo.variant))
10306             strcat(parseList[forwardMostMove - 1], "+");
10307         break;
10308       case MT_CHECKMATE:
10309       case MT_STAINMATE:
10310         strcat(parseList[forwardMostMove - 1], "#");
10311         break;
10312     }
10313 }
10314
10315 /* Updates currentMove if not pausing */
10316 void
10317 ShowMove (int fromX, int fromY, int toX, int toY)
10318 {
10319     int instant = (gameMode == PlayFromGameFile) ?
10320         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10321     if(appData.noGUI) return;
10322     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10323         if (!instant) {
10324             if (forwardMostMove == currentMove + 1) {
10325                 AnimateMove(boards[forwardMostMove - 1],
10326                             fromX, fromY, toX, toY);
10327             }
10328         }
10329         currentMove = forwardMostMove;
10330     }
10331
10332     killX = killY = -1; // [HGM] lion: used up
10333
10334     if (instant) return;
10335
10336     DisplayMove(currentMove - 1);
10337     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10338             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10339                 SetHighlights(fromX, fromY, toX, toY);
10340             }
10341     }
10342     DrawPosition(FALSE, boards[currentMove]);
10343     DisplayBothClocks();
10344     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10345 }
10346
10347 void
10348 SendEgtPath (ChessProgramState *cps)
10349 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10350         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10351
10352         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10353
10354         while(*p) {
10355             char c, *q = name+1, *r, *s;
10356
10357             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10358             while(*p && *p != ',') *q++ = *p++;
10359             *q++ = ':'; *q = 0;
10360             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10361                 strcmp(name, ",nalimov:") == 0 ) {
10362                 // take nalimov path from the menu-changeable option first, if it is defined
10363               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10364                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10365             } else
10366             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10367                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10368                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10369                 s = r = StrStr(s, ":") + 1; // beginning of path info
10370                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10371                 c = *r; *r = 0;             // temporarily null-terminate path info
10372                     *--q = 0;               // strip of trailig ':' from name
10373                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10374                 *r = c;
10375                 SendToProgram(buf,cps);     // send egtbpath command for this format
10376             }
10377             if(*p == ',') p++; // read away comma to position for next format name
10378         }
10379 }
10380
10381 static int
10382 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10383 {
10384       int width = 8, height = 8, holdings = 0;             // most common sizes
10385       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10386       // correct the deviations default for each variant
10387       if( v == VariantXiangqi ) width = 9,  height = 10;
10388       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10389       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10390       if( v == VariantCapablanca || v == VariantCapaRandom ||
10391           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10392                                 width = 10;
10393       if( v == VariantCourier ) width = 12;
10394       if( v == VariantSuper )                            holdings = 8;
10395       if( v == VariantGreat )   width = 10,              holdings = 8;
10396       if( v == VariantSChess )                           holdings = 7;
10397       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10398       if( v == VariantChuChess) width = 10, height = 10;
10399       if( v == VariantChu )     width = 12, height = 12;
10400       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10401              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10402              holdingsSize >= 0 && holdingsSize != holdings;
10403 }
10404
10405 char variantError[MSG_SIZ];
10406
10407 char *
10408 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10409 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10410       char *p, *variant = VariantName(v);
10411       static char b[MSG_SIZ];
10412       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10413            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10414                                                holdingsSize, variant); // cook up sized variant name
10415            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10416            if(StrStr(list, b) == NULL) {
10417                // specific sized variant not known, check if general sizing allowed
10418                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10419                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10420                             boardWidth, boardHeight, holdingsSize, engine);
10421                    return NULL;
10422                }
10423                /* [HGM] here we really should compare with the maximum supported board size */
10424            }
10425       } else snprintf(b, MSG_SIZ,"%s", variant);
10426       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10427       p = StrStr(list, b);
10428       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10429       if(p == NULL) {
10430           // occurs not at all in list, or only as sub-string
10431           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10432           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10433               int l = strlen(variantError);
10434               char *q;
10435               while(p != list && p[-1] != ',') p--;
10436               q = strchr(p, ',');
10437               if(q) *q = NULLCHAR;
10438               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10439               if(q) *q= ',';
10440           }
10441           return NULL;
10442       }
10443       return b;
10444 }
10445
10446 void
10447 InitChessProgram (ChessProgramState *cps, int setup)
10448 /* setup needed to setup FRC opening position */
10449 {
10450     char buf[MSG_SIZ], *b;
10451     if (appData.noChessProgram) return;
10452     hintRequested = FALSE;
10453     bookRequested = FALSE;
10454
10455     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10456     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10457     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10458     if(cps->memSize) { /* [HGM] memory */
10459       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10460         SendToProgram(buf, cps);
10461     }
10462     SendEgtPath(cps); /* [HGM] EGT */
10463     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10464       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10465         SendToProgram(buf, cps);
10466     }
10467
10468     setboardSpoiledMachineBlack = FALSE;
10469     SendToProgram(cps->initString, cps);
10470     if (gameInfo.variant != VariantNormal &&
10471         gameInfo.variant != VariantLoadable
10472         /* [HGM] also send variant if board size non-standard */
10473         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10474
10475       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10476                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10477       if (b == NULL) {
10478         DisplayFatalError(variantError, 0, 1);
10479         return;
10480       }
10481
10482       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10483       SendToProgram(buf, cps);
10484     }
10485     currentlyInitializedVariant = gameInfo.variant;
10486
10487     /* [HGM] send opening position in FRC to first engine */
10488     if(setup) {
10489           SendToProgram("force\n", cps);
10490           SendBoard(cps, 0);
10491           /* engine is now in force mode! Set flag to wake it up after first move. */
10492           setboardSpoiledMachineBlack = 1;
10493     }
10494
10495     if (cps->sendICS) {
10496       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10497       SendToProgram(buf, cps);
10498     }
10499     cps->maybeThinking = FALSE;
10500     cps->offeredDraw = 0;
10501     if (!appData.icsActive) {
10502         SendTimeControl(cps, movesPerSession, timeControl,
10503                         timeIncrement, appData.searchDepth,
10504                         searchTime);
10505     }
10506     if (appData.showThinking
10507         // [HGM] thinking: four options require thinking output to be sent
10508         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10509                                 ) {
10510         SendToProgram("post\n", cps);
10511     }
10512     SendToProgram("hard\n", cps);
10513     if (!appData.ponderNextMove) {
10514         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10515            it without being sure what state we are in first.  "hard"
10516            is not a toggle, so that one is OK.
10517          */
10518         SendToProgram("easy\n", cps);
10519     }
10520     if (cps->usePing) {
10521       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10522       SendToProgram(buf, cps);
10523     }
10524     cps->initDone = TRUE;
10525     ClearEngineOutputPane(cps == &second);
10526 }
10527
10528
10529 void
10530 ResendOptions (ChessProgramState *cps)
10531 { // send the stored value of the options
10532   int i;
10533   char buf[MSG_SIZ];
10534   Option *opt = cps->option;
10535   for(i=0; i<cps->nrOptions; i++, opt++) {
10536       switch(opt->type) {
10537         case Spin:
10538         case Slider:
10539         case CheckBox:
10540             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10541           break;
10542         case ComboBox:
10543           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10544           break;
10545         default:
10546             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10547           break;
10548         case Button:
10549         case SaveButton:
10550           continue;
10551       }
10552       SendToProgram(buf, cps);
10553   }
10554 }
10555
10556 void
10557 StartChessProgram (ChessProgramState *cps)
10558 {
10559     char buf[MSG_SIZ];
10560     int err;
10561
10562     if (appData.noChessProgram) return;
10563     cps->initDone = FALSE;
10564
10565     if (strcmp(cps->host, "localhost") == 0) {
10566         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10567     } else if (*appData.remoteShell == NULLCHAR) {
10568         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10569     } else {
10570         if (*appData.remoteUser == NULLCHAR) {
10571           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10572                     cps->program);
10573         } else {
10574           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10575                     cps->host, appData.remoteUser, cps->program);
10576         }
10577         err = StartChildProcess(buf, "", &cps->pr);
10578     }
10579
10580     if (err != 0) {
10581       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10582         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10583         if(cps != &first) return;
10584         appData.noChessProgram = TRUE;
10585         ThawUI();
10586         SetNCPMode();
10587 //      DisplayFatalError(buf, err, 1);
10588 //      cps->pr = NoProc;
10589 //      cps->isr = NULL;
10590         return;
10591     }
10592
10593     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10594     if (cps->protocolVersion > 1) {
10595       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10596       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10597         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10598         cps->comboCnt = 0;  //                and values of combo boxes
10599       }
10600       SendToProgram(buf, cps);
10601       if(cps->reload) ResendOptions(cps);
10602     } else {
10603       SendToProgram("xboard\n", cps);
10604     }
10605 }
10606
10607 void
10608 TwoMachinesEventIfReady P((void))
10609 {
10610   static int curMess = 0;
10611   if (first.lastPing != first.lastPong) {
10612     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10613     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10614     return;
10615   }
10616   if (second.lastPing != second.lastPong) {
10617     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10618     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10619     return;
10620   }
10621   DisplayMessage("", ""); curMess = 0;
10622   TwoMachinesEvent();
10623 }
10624
10625 char *
10626 MakeName (char *template)
10627 {
10628     time_t clock;
10629     struct tm *tm;
10630     static char buf[MSG_SIZ];
10631     char *p = buf;
10632     int i;
10633
10634     clock = time((time_t *)NULL);
10635     tm = localtime(&clock);
10636
10637     while(*p++ = *template++) if(p[-1] == '%') {
10638         switch(*template++) {
10639           case 0:   *p = 0; return buf;
10640           case 'Y': i = tm->tm_year+1900; break;
10641           case 'y': i = tm->tm_year-100; break;
10642           case 'M': i = tm->tm_mon+1; break;
10643           case 'd': i = tm->tm_mday; break;
10644           case 'h': i = tm->tm_hour; break;
10645           case 'm': i = tm->tm_min; break;
10646           case 's': i = tm->tm_sec; break;
10647           default:  i = 0;
10648         }
10649         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10650     }
10651     return buf;
10652 }
10653
10654 int
10655 CountPlayers (char *p)
10656 {
10657     int n = 0;
10658     while(p = strchr(p, '\n')) p++, n++; // count participants
10659     return n;
10660 }
10661
10662 FILE *
10663 WriteTourneyFile (char *results, FILE *f)
10664 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10665     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10666     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10667         // create a file with tournament description
10668         fprintf(f, "-participants {%s}\n", appData.participants);
10669         fprintf(f, "-seedBase %d\n", appData.seedBase);
10670         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10671         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10672         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10673         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10674         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10675         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10676         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10677         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10678         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10679         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10680         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10681         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10682         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10683         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10684         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10685         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10686         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10687         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10688         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10689         fprintf(f, "-smpCores %d\n", appData.smpCores);
10690         if(searchTime > 0)
10691                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10692         else {
10693                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10694                 fprintf(f, "-tc %s\n", appData.timeControl);
10695                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10696         }
10697         fprintf(f, "-results \"%s\"\n", results);
10698     }
10699     return f;
10700 }
10701
10702 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10703
10704 void
10705 Substitute (char *participants, int expunge)
10706 {
10707     int i, changed, changes=0, nPlayers=0;
10708     char *p, *q, *r, buf[MSG_SIZ];
10709     if(participants == NULL) return;
10710     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10711     r = p = participants; q = appData.participants;
10712     while(*p && *p == *q) {
10713         if(*p == '\n') r = p+1, nPlayers++;
10714         p++; q++;
10715     }
10716     if(*p) { // difference
10717         while(*p && *p++ != '\n');
10718         while(*q && *q++ != '\n');
10719       changed = nPlayers;
10720         changes = 1 + (strcmp(p, q) != 0);
10721     }
10722     if(changes == 1) { // a single engine mnemonic was changed
10723         q = r; while(*q) nPlayers += (*q++ == '\n');
10724         p = buf; while(*r && (*p = *r++) != '\n') p++;
10725         *p = NULLCHAR;
10726         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10727         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10728         if(mnemonic[i]) { // The substitute is valid
10729             FILE *f;
10730             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10731                 flock(fileno(f), LOCK_EX);
10732                 ParseArgsFromFile(f);
10733                 fseek(f, 0, SEEK_SET);
10734                 FREE(appData.participants); appData.participants = participants;
10735                 if(expunge) { // erase results of replaced engine
10736                     int len = strlen(appData.results), w, b, dummy;
10737                     for(i=0; i<len; i++) {
10738                         Pairing(i, nPlayers, &w, &b, &dummy);
10739                         if((w == changed || b == changed) && appData.results[i] == '*') {
10740                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10741                             fclose(f);
10742                             return;
10743                         }
10744                     }
10745                     for(i=0; i<len; i++) {
10746                         Pairing(i, nPlayers, &w, &b, &dummy);
10747                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10748                     }
10749                 }
10750                 WriteTourneyFile(appData.results, f);
10751                 fclose(f); // release lock
10752                 return;
10753             }
10754         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10755     }
10756     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10757     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10758     free(participants);
10759     return;
10760 }
10761
10762 int
10763 CheckPlayers (char *participants)
10764 {
10765         int i;
10766         char buf[MSG_SIZ], *p;
10767         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10768         while(p = strchr(participants, '\n')) {
10769             *p = NULLCHAR;
10770             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10771             if(!mnemonic[i]) {
10772                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10773                 *p = '\n';
10774                 DisplayError(buf, 0);
10775                 return 1;
10776             }
10777             *p = '\n';
10778             participants = p + 1;
10779         }
10780         return 0;
10781 }
10782
10783 int
10784 CreateTourney (char *name)
10785 {
10786         FILE *f;
10787         if(matchMode && strcmp(name, appData.tourneyFile)) {
10788              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10789         }
10790         if(name[0] == NULLCHAR) {
10791             if(appData.participants[0])
10792                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10793             return 0;
10794         }
10795         f = fopen(name, "r");
10796         if(f) { // file exists
10797             ASSIGN(appData.tourneyFile, name);
10798             ParseArgsFromFile(f); // parse it
10799         } else {
10800             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10801             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10802                 DisplayError(_("Not enough participants"), 0);
10803                 return 0;
10804             }
10805             if(CheckPlayers(appData.participants)) return 0;
10806             ASSIGN(appData.tourneyFile, name);
10807             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10808             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10809         }
10810         fclose(f);
10811         appData.noChessProgram = FALSE;
10812         appData.clockMode = TRUE;
10813         SetGNUMode();
10814         return 1;
10815 }
10816
10817 int
10818 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10819 {
10820     char buf[MSG_SIZ], *p, *q;
10821     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10822     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10823     skip = !all && group[0]; // if group requested, we start in skip mode
10824     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10825         p = names; q = buf; header = 0;
10826         while(*p && *p != '\n') *q++ = *p++;
10827         *q = 0;
10828         if(*p == '\n') p++;
10829         if(buf[0] == '#') {
10830             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10831             depth++; // we must be entering a new group
10832             if(all) continue; // suppress printing group headers when complete list requested
10833             header = 1;
10834             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10835         }
10836         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10837         if(engineList[i]) free(engineList[i]);
10838         engineList[i] = strdup(buf);
10839         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10840         if(engineMnemonic[i]) free(engineMnemonic[i]);
10841         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10842             strcat(buf, " (");
10843             sscanf(q + 8, "%s", buf + strlen(buf));
10844             strcat(buf, ")");
10845         }
10846         engineMnemonic[i] = strdup(buf);
10847         i++;
10848     }
10849     engineList[i] = engineMnemonic[i] = NULL;
10850     return i;
10851 }
10852
10853 // following implemented as macro to avoid type limitations
10854 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10855
10856 void
10857 SwapEngines (int n)
10858 {   // swap settings for first engine and other engine (so far only some selected options)
10859     int h;
10860     char *p;
10861     if(n == 0) return;
10862     SWAP(directory, p)
10863     SWAP(chessProgram, p)
10864     SWAP(isUCI, h)
10865     SWAP(hasOwnBookUCI, h)
10866     SWAP(protocolVersion, h)
10867     SWAP(reuse, h)
10868     SWAP(scoreIsAbsolute, h)
10869     SWAP(timeOdds, h)
10870     SWAP(logo, p)
10871     SWAP(pgnName, p)
10872     SWAP(pvSAN, h)
10873     SWAP(engOptions, p)
10874     SWAP(engInitString, p)
10875     SWAP(computerString, p)
10876     SWAP(features, p)
10877     SWAP(fenOverride, p)
10878     SWAP(NPS, h)
10879     SWAP(accumulateTC, h)
10880     SWAP(drawDepth, h)
10881     SWAP(host, p)
10882     SWAP(pseudo, h)
10883 }
10884
10885 int
10886 GetEngineLine (char *s, int n)
10887 {
10888     int i;
10889     char buf[MSG_SIZ];
10890     extern char *icsNames;
10891     if(!s || !*s) return 0;
10892     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10893     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10894     if(!mnemonic[i]) return 0;
10895     if(n == 11) return 1; // just testing if there was a match
10896     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10897     if(n == 1) SwapEngines(n);
10898     ParseArgsFromString(buf);
10899     if(n == 1) SwapEngines(n);
10900     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10901         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10902         ParseArgsFromString(buf);
10903     }
10904     return 1;
10905 }
10906
10907 int
10908 SetPlayer (int player, char *p)
10909 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10910     int i;
10911     char buf[MSG_SIZ], *engineName;
10912     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10913     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10914     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10915     if(mnemonic[i]) {
10916         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10917         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10918         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10919         ParseArgsFromString(buf);
10920     } else { // no engine with this nickname is installed!
10921         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10922         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10923         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10924         ModeHighlight();
10925         DisplayError(buf, 0);
10926         return 0;
10927     }
10928     free(engineName);
10929     return i;
10930 }
10931
10932 char *recentEngines;
10933
10934 void
10935 RecentEngineEvent (int nr)
10936 {
10937     int n;
10938 //    SwapEngines(1); // bump first to second
10939 //    ReplaceEngine(&second, 1); // and load it there
10940     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10941     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10942     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10943         ReplaceEngine(&first, 0);
10944         FloatToFront(&appData.recentEngineList, command[n]);
10945     }
10946 }
10947
10948 int
10949 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10950 {   // determine players from game number
10951     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10952
10953     if(appData.tourneyType == 0) {
10954         roundsPerCycle = (nPlayers - 1) | 1;
10955         pairingsPerRound = nPlayers / 2;
10956     } else if(appData.tourneyType > 0) {
10957         roundsPerCycle = nPlayers - appData.tourneyType;
10958         pairingsPerRound = appData.tourneyType;
10959     }
10960     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10961     gamesPerCycle = gamesPerRound * roundsPerCycle;
10962     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10963     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10964     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10965     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10966     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10967     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10968
10969     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10970     if(appData.roundSync) *syncInterval = gamesPerRound;
10971
10972     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10973
10974     if(appData.tourneyType == 0) {
10975         if(curPairing == (nPlayers-1)/2 ) {
10976             *whitePlayer = curRound;
10977             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10978         } else {
10979             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10980             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10981             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10982             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10983         }
10984     } else if(appData.tourneyType > 1) {
10985         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10986         *whitePlayer = curRound + appData.tourneyType;
10987     } else if(appData.tourneyType > 0) {
10988         *whitePlayer = curPairing;
10989         *blackPlayer = curRound + appData.tourneyType;
10990     }
10991
10992     // take care of white/black alternation per round.
10993     // For cycles and games this is already taken care of by default, derived from matchGame!
10994     return curRound & 1;
10995 }
10996
10997 int
10998 NextTourneyGame (int nr, int *swapColors)
10999 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11000     char *p, *q;
11001     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11002     FILE *tf;
11003     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11004     tf = fopen(appData.tourneyFile, "r");
11005     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11006     ParseArgsFromFile(tf); fclose(tf);
11007     InitTimeControls(); // TC might be altered from tourney file
11008
11009     nPlayers = CountPlayers(appData.participants); // count participants
11010     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11011     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11012
11013     if(syncInterval) {
11014         p = q = appData.results;
11015         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11016         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11017             DisplayMessage(_("Waiting for other game(s)"),"");
11018             waitingForGame = TRUE;
11019             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11020             return 0;
11021         }
11022         waitingForGame = FALSE;
11023     }
11024
11025     if(appData.tourneyType < 0) {
11026         if(nr>=0 && !pairingReceived) {
11027             char buf[1<<16];
11028             if(pairing.pr == NoProc) {
11029                 if(!appData.pairingEngine[0]) {
11030                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11031                     return 0;
11032                 }
11033                 StartChessProgram(&pairing); // starts the pairing engine
11034             }
11035             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11036             SendToProgram(buf, &pairing);
11037             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11038             SendToProgram(buf, &pairing);
11039             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11040         }
11041         pairingReceived = 0;                              // ... so we continue here
11042         *swapColors = 0;
11043         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11044         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11045         matchGame = 1; roundNr = nr / syncInterval + 1;
11046     }
11047
11048     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11049
11050     // redefine engines, engine dir, etc.
11051     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11052     if(first.pr == NoProc) {
11053       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11054       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11055     }
11056     if(second.pr == NoProc) {
11057       SwapEngines(1);
11058       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11059       SwapEngines(1);         // and make that valid for second engine by swapping
11060       InitEngine(&second, 1);
11061     }
11062     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11063     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11064     return OK;
11065 }
11066
11067 void
11068 NextMatchGame ()
11069 {   // performs game initialization that does not invoke engines, and then tries to start the game
11070     int res, firstWhite, swapColors = 0;
11071     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11072     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
11073         char buf[MSG_SIZ];
11074         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11075         if(strcmp(buf, currentDebugFile)) { // name has changed
11076             FILE *f = fopen(buf, "w");
11077             if(f) { // if opening the new file failed, just keep using the old one
11078                 ASSIGN(currentDebugFile, buf);
11079                 fclose(debugFP);
11080                 debugFP = f;
11081             }
11082             if(appData.serverFileName) {
11083                 if(serverFP) fclose(serverFP);
11084                 serverFP = fopen(appData.serverFileName, "w");
11085                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11086                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11087             }
11088         }
11089     }
11090     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11091     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11092     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11093     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11094     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11095     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11096     Reset(FALSE, first.pr != NoProc);
11097     res = LoadGameOrPosition(matchGame); // setup game
11098     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11099     if(!res) return; // abort when bad game/pos file
11100     TwoMachinesEvent();
11101 }
11102
11103 void
11104 UserAdjudicationEvent (int result)
11105 {
11106     ChessMove gameResult = GameIsDrawn;
11107
11108     if( result > 0 ) {
11109         gameResult = WhiteWins;
11110     }
11111     else if( result < 0 ) {
11112         gameResult = BlackWins;
11113     }
11114
11115     if( gameMode == TwoMachinesPlay ) {
11116         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11117     }
11118 }
11119
11120
11121 // [HGM] save: calculate checksum of game to make games easily identifiable
11122 int
11123 StringCheckSum (char *s)
11124 {
11125         int i = 0;
11126         if(s==NULL) return 0;
11127         while(*s) i = i*259 + *s++;
11128         return i;
11129 }
11130
11131 int
11132 GameCheckSum ()
11133 {
11134         int i, sum=0;
11135         for(i=backwardMostMove; i<forwardMostMove; i++) {
11136                 sum += pvInfoList[i].depth;
11137                 sum += StringCheckSum(parseList[i]);
11138                 sum += StringCheckSum(commentList[i]);
11139                 sum *= 261;
11140         }
11141         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11142         return sum + StringCheckSum(commentList[i]);
11143 } // end of save patch
11144
11145 void
11146 GameEnds (ChessMove result, char *resultDetails, int whosays)
11147 {
11148     GameMode nextGameMode;
11149     int isIcsGame;
11150     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11151
11152     if(endingGame) return; /* [HGM] crash: forbid recursion */
11153     endingGame = 1;
11154     if(twoBoards) { // [HGM] dual: switch back to one board
11155         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11156         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11157     }
11158     if (appData.debugMode) {
11159       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11160               result, resultDetails ? resultDetails : "(null)", whosays);
11161     }
11162
11163     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11164
11165     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11166
11167     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11168         /* If we are playing on ICS, the server decides when the
11169            game is over, but the engine can offer to draw, claim
11170            a draw, or resign.
11171          */
11172 #if ZIPPY
11173         if (appData.zippyPlay && first.initDone) {
11174             if (result == GameIsDrawn) {
11175                 /* In case draw still needs to be claimed */
11176                 SendToICS(ics_prefix);
11177                 SendToICS("draw\n");
11178             } else if (StrCaseStr(resultDetails, "resign")) {
11179                 SendToICS(ics_prefix);
11180                 SendToICS("resign\n");
11181             }
11182         }
11183 #endif
11184         endingGame = 0; /* [HGM] crash */
11185         return;
11186     }
11187
11188     /* If we're loading the game from a file, stop */
11189     if (whosays == GE_FILE) {
11190       (void) StopLoadGameTimer();
11191       gameFileFP = NULL;
11192     }
11193
11194     /* Cancel draw offers */
11195     first.offeredDraw = second.offeredDraw = 0;
11196
11197     /* If this is an ICS game, only ICS can really say it's done;
11198        if not, anyone can. */
11199     isIcsGame = (gameMode == IcsPlayingWhite ||
11200                  gameMode == IcsPlayingBlack ||
11201                  gameMode == IcsObserving    ||
11202                  gameMode == IcsExamining);
11203
11204     if (!isIcsGame || whosays == GE_ICS) {
11205         /* OK -- not an ICS game, or ICS said it was done */
11206         StopClocks();
11207         if (!isIcsGame && !appData.noChessProgram)
11208           SetUserThinkingEnables();
11209
11210         /* [HGM] if a machine claims the game end we verify this claim */
11211         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11212             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11213                 char claimer;
11214                 ChessMove trueResult = (ChessMove) -1;
11215
11216                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11217                                             first.twoMachinesColor[0] :
11218                                             second.twoMachinesColor[0] ;
11219
11220                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11221                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11222                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11223                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11224                 } else
11225                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11226                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11227                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11228                 } else
11229                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11230                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11231                 }
11232
11233                 // now verify win claims, but not in drop games, as we don't understand those yet
11234                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11235                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11236                     (result == WhiteWins && claimer == 'w' ||
11237                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11238                       if (appData.debugMode) {
11239                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11240                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11241                       }
11242                       if(result != trueResult) {
11243                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11244                               result = claimer == 'w' ? BlackWins : WhiteWins;
11245                               resultDetails = buf;
11246                       }
11247                 } else
11248                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11249                     && (forwardMostMove <= backwardMostMove ||
11250                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11251                         (claimer=='b')==(forwardMostMove&1))
11252                                                                                   ) {
11253                       /* [HGM] verify: draws that were not flagged are false claims */
11254                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11255                       result = claimer == 'w' ? BlackWins : WhiteWins;
11256                       resultDetails = buf;
11257                 }
11258                 /* (Claiming a loss is accepted no questions asked!) */
11259             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11260                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11261                 result = GameUnfinished;
11262                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11263             }
11264             /* [HGM] bare: don't allow bare King to win */
11265             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11266                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11267                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11268                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11269                && result != GameIsDrawn)
11270             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11271                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11272                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11273                         if(p >= 0 && p <= (int)WhiteKing) k++;
11274                 }
11275                 if (appData.debugMode) {
11276                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11277                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11278                 }
11279                 if(k <= 1) {
11280                         result = GameIsDrawn;
11281                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11282                         resultDetails = buf;
11283                 }
11284             }
11285         }
11286
11287
11288         if(serverMoves != NULL && !loadFlag) { char c = '=';
11289             if(result==WhiteWins) c = '+';
11290             if(result==BlackWins) c = '-';
11291             if(resultDetails != NULL)
11292                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11293         }
11294         if (resultDetails != NULL) {
11295             gameInfo.result = result;
11296             gameInfo.resultDetails = StrSave(resultDetails);
11297
11298             /* display last move only if game was not loaded from file */
11299             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11300                 DisplayMove(currentMove - 1);
11301
11302             if (forwardMostMove != 0) {
11303                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11304                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11305                                                                 ) {
11306                     if (*appData.saveGameFile != NULLCHAR) {
11307                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11308                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11309                         else
11310                         SaveGameToFile(appData.saveGameFile, TRUE);
11311                     } else if (appData.autoSaveGames) {
11312                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11313                     }
11314                     if (*appData.savePositionFile != NULLCHAR) {
11315                         SavePositionToFile(appData.savePositionFile);
11316                     }
11317                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11318                 }
11319             }
11320
11321             /* Tell program how game ended in case it is learning */
11322             /* [HGM] Moved this to after saving the PGN, just in case */
11323             /* engine died and we got here through time loss. In that */
11324             /* case we will get a fatal error writing the pipe, which */
11325             /* would otherwise lose us the PGN.                       */
11326             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11327             /* output during GameEnds should never be fatal anymore   */
11328             if (gameMode == MachinePlaysWhite ||
11329                 gameMode == MachinePlaysBlack ||
11330                 gameMode == TwoMachinesPlay ||
11331                 gameMode == IcsPlayingWhite ||
11332                 gameMode == IcsPlayingBlack ||
11333                 gameMode == BeginningOfGame) {
11334                 char buf[MSG_SIZ];
11335                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11336                         resultDetails);
11337                 if (first.pr != NoProc) {
11338                     SendToProgram(buf, &first);
11339                 }
11340                 if (second.pr != NoProc &&
11341                     gameMode == TwoMachinesPlay) {
11342                     SendToProgram(buf, &second);
11343                 }
11344             }
11345         }
11346
11347         if (appData.icsActive) {
11348             if (appData.quietPlay &&
11349                 (gameMode == IcsPlayingWhite ||
11350                  gameMode == IcsPlayingBlack)) {
11351                 SendToICS(ics_prefix);
11352                 SendToICS("set shout 1\n");
11353             }
11354             nextGameMode = IcsIdle;
11355             ics_user_moved = FALSE;
11356             /* clean up premove.  It's ugly when the game has ended and the
11357              * premove highlights are still on the board.
11358              */
11359             if (gotPremove) {
11360               gotPremove = FALSE;
11361               ClearPremoveHighlights();
11362               DrawPosition(FALSE, boards[currentMove]);
11363             }
11364             if (whosays == GE_ICS) {
11365                 switch (result) {
11366                 case WhiteWins:
11367                     if (gameMode == IcsPlayingWhite)
11368                         PlayIcsWinSound();
11369                     else if(gameMode == IcsPlayingBlack)
11370                         PlayIcsLossSound();
11371                     break;
11372                 case BlackWins:
11373                     if (gameMode == IcsPlayingBlack)
11374                         PlayIcsWinSound();
11375                     else if(gameMode == IcsPlayingWhite)
11376                         PlayIcsLossSound();
11377                     break;
11378                 case GameIsDrawn:
11379                     PlayIcsDrawSound();
11380                     break;
11381                 default:
11382                     PlayIcsUnfinishedSound();
11383                 }
11384             }
11385             if(appData.quitNext) { ExitEvent(0); return; }
11386         } else if (gameMode == EditGame ||
11387                    gameMode == PlayFromGameFile ||
11388                    gameMode == AnalyzeMode ||
11389                    gameMode == AnalyzeFile) {
11390             nextGameMode = gameMode;
11391         } else {
11392             nextGameMode = EndOfGame;
11393         }
11394         pausing = FALSE;
11395         ModeHighlight();
11396     } else {
11397         nextGameMode = gameMode;
11398     }
11399
11400     if (appData.noChessProgram) {
11401         gameMode = nextGameMode;
11402         ModeHighlight();
11403         endingGame = 0; /* [HGM] crash */
11404         return;
11405     }
11406
11407     if (first.reuse) {
11408         /* Put first chess program into idle state */
11409         if (first.pr != NoProc &&
11410             (gameMode == MachinePlaysWhite ||
11411              gameMode == MachinePlaysBlack ||
11412              gameMode == TwoMachinesPlay ||
11413              gameMode == IcsPlayingWhite ||
11414              gameMode == IcsPlayingBlack ||
11415              gameMode == BeginningOfGame)) {
11416             SendToProgram("force\n", &first);
11417             if (first.usePing) {
11418               char buf[MSG_SIZ];
11419               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11420               SendToProgram(buf, &first);
11421             }
11422         }
11423     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11424         /* Kill off first chess program */
11425         if (first.isr != NULL)
11426           RemoveInputSource(first.isr);
11427         first.isr = NULL;
11428
11429         if (first.pr != NoProc) {
11430             ExitAnalyzeMode();
11431             DoSleep( appData.delayBeforeQuit );
11432             SendToProgram("quit\n", &first);
11433             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11434             first.reload = TRUE;
11435         }
11436         first.pr = NoProc;
11437     }
11438     if (second.reuse) {
11439         /* Put second chess program into idle state */
11440         if (second.pr != NoProc &&
11441             gameMode == TwoMachinesPlay) {
11442             SendToProgram("force\n", &second);
11443             if (second.usePing) {
11444               char buf[MSG_SIZ];
11445               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11446               SendToProgram(buf, &second);
11447             }
11448         }
11449     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11450         /* Kill off second chess program */
11451         if (second.isr != NULL)
11452           RemoveInputSource(second.isr);
11453         second.isr = NULL;
11454
11455         if (second.pr != NoProc) {
11456             DoSleep( appData.delayBeforeQuit );
11457             SendToProgram("quit\n", &second);
11458             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11459             second.reload = TRUE;
11460         }
11461         second.pr = NoProc;
11462     }
11463
11464     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11465         char resChar = '=';
11466         switch (result) {
11467         case WhiteWins:
11468           resChar = '+';
11469           if (first.twoMachinesColor[0] == 'w') {
11470             first.matchWins++;
11471           } else {
11472             second.matchWins++;
11473           }
11474           break;
11475         case BlackWins:
11476           resChar = '-';
11477           if (first.twoMachinesColor[0] == 'b') {
11478             first.matchWins++;
11479           } else {
11480             second.matchWins++;
11481           }
11482           break;
11483         case GameUnfinished:
11484           resChar = ' ';
11485         default:
11486           break;
11487         }
11488
11489         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11490         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11491             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11492             ReserveGame(nextGame, resChar); // sets nextGame
11493             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11494             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11495         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11496
11497         if (nextGame <= appData.matchGames && !abortMatch) {
11498             gameMode = nextGameMode;
11499             matchGame = nextGame; // this will be overruled in tourney mode!
11500             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11501             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11502             endingGame = 0; /* [HGM] crash */
11503             return;
11504         } else {
11505             gameMode = nextGameMode;
11506             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11507                      first.tidy, second.tidy,
11508                      first.matchWins, second.matchWins,
11509                      appData.matchGames - (first.matchWins + second.matchWins));
11510             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11511             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11512             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11513             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11514                 first.twoMachinesColor = "black\n";
11515                 second.twoMachinesColor = "white\n";
11516             } else {
11517                 first.twoMachinesColor = "white\n";
11518                 second.twoMachinesColor = "black\n";
11519             }
11520         }
11521     }
11522     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11523         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11524       ExitAnalyzeMode();
11525     gameMode = nextGameMode;
11526     ModeHighlight();
11527     endingGame = 0;  /* [HGM] crash */
11528     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11529         if(matchMode == TRUE) { // match through command line: exit with or without popup
11530             if(ranking) {
11531                 ToNrEvent(forwardMostMove);
11532                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11533                 else ExitEvent(0);
11534             } else DisplayFatalError(buf, 0, 0);
11535         } else { // match through menu; just stop, with or without popup
11536             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11537             ModeHighlight();
11538             if(ranking){
11539                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11540             } else DisplayNote(buf);
11541       }
11542       if(ranking) free(ranking);
11543     }
11544 }
11545
11546 /* Assumes program was just initialized (initString sent).
11547    Leaves program in force mode. */
11548 void
11549 FeedMovesToProgram (ChessProgramState *cps, int upto)
11550 {
11551     int i;
11552
11553     if (appData.debugMode)
11554       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11555               startedFromSetupPosition ? "position and " : "",
11556               backwardMostMove, upto, cps->which);
11557     if(currentlyInitializedVariant != gameInfo.variant) {
11558       char buf[MSG_SIZ];
11559         // [HGM] variantswitch: make engine aware of new variant
11560         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11561                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11562                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11563         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11564         SendToProgram(buf, cps);
11565         currentlyInitializedVariant = gameInfo.variant;
11566     }
11567     SendToProgram("force\n", cps);
11568     if (startedFromSetupPosition) {
11569         SendBoard(cps, backwardMostMove);
11570     if (appData.debugMode) {
11571         fprintf(debugFP, "feedMoves\n");
11572     }
11573     }
11574     for (i = backwardMostMove; i < upto; i++) {
11575         SendMoveToProgram(i, cps);
11576     }
11577 }
11578
11579
11580 int
11581 ResurrectChessProgram ()
11582 {
11583      /* The chess program may have exited.
11584         If so, restart it and feed it all the moves made so far. */
11585     static int doInit = 0;
11586
11587     if (appData.noChessProgram) return 1;
11588
11589     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11590         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11591         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11592         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11593     } else {
11594         if (first.pr != NoProc) return 1;
11595         StartChessProgram(&first);
11596     }
11597     InitChessProgram(&first, FALSE);
11598     FeedMovesToProgram(&first, currentMove);
11599
11600     if (!first.sendTime) {
11601         /* can't tell gnuchess what its clock should read,
11602            so we bow to its notion. */
11603         ResetClocks();
11604         timeRemaining[0][currentMove] = whiteTimeRemaining;
11605         timeRemaining[1][currentMove] = blackTimeRemaining;
11606     }
11607
11608     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11609                 appData.icsEngineAnalyze) && first.analysisSupport) {
11610       SendToProgram("analyze\n", &first);
11611       first.analyzing = TRUE;
11612     }
11613     return 1;
11614 }
11615
11616 /*
11617  * Button procedures
11618  */
11619 void
11620 Reset (int redraw, int init)
11621 {
11622     int i;
11623
11624     if (appData.debugMode) {
11625         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11626                 redraw, init, gameMode);
11627     }
11628     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11629     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11630     CleanupTail(); // [HGM] vari: delete any stored variations
11631     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11632     pausing = pauseExamInvalid = FALSE;
11633     startedFromSetupPosition = blackPlaysFirst = FALSE;
11634     firstMove = TRUE;
11635     whiteFlag = blackFlag = FALSE;
11636     userOfferedDraw = FALSE;
11637     hintRequested = bookRequested = FALSE;
11638     first.maybeThinking = FALSE;
11639     second.maybeThinking = FALSE;
11640     first.bookSuspend = FALSE; // [HGM] book
11641     second.bookSuspend = FALSE;
11642     thinkOutput[0] = NULLCHAR;
11643     lastHint[0] = NULLCHAR;
11644     ClearGameInfo(&gameInfo);
11645     gameInfo.variant = StringToVariant(appData.variant);
11646     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11647     ics_user_moved = ics_clock_paused = FALSE;
11648     ics_getting_history = H_FALSE;
11649     ics_gamenum = -1;
11650     white_holding[0] = black_holding[0] = NULLCHAR;
11651     ClearProgramStats();
11652     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11653
11654     ResetFrontEnd();
11655     ClearHighlights();
11656     flipView = appData.flipView;
11657     ClearPremoveHighlights();
11658     gotPremove = FALSE;
11659     alarmSounded = FALSE;
11660     killX = killY = -1; // [HGM] lion
11661
11662     GameEnds(EndOfFile, NULL, GE_PLAYER);
11663     if(appData.serverMovesName != NULL) {
11664         /* [HGM] prepare to make moves file for broadcasting */
11665         clock_t t = clock();
11666         if(serverMoves != NULL) fclose(serverMoves);
11667         serverMoves = fopen(appData.serverMovesName, "r");
11668         if(serverMoves != NULL) {
11669             fclose(serverMoves);
11670             /* delay 15 sec before overwriting, so all clients can see end */
11671             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11672         }
11673         serverMoves = fopen(appData.serverMovesName, "w");
11674     }
11675
11676     ExitAnalyzeMode();
11677     gameMode = BeginningOfGame;
11678     ModeHighlight();
11679     if(appData.icsActive) gameInfo.variant = VariantNormal;
11680     currentMove = forwardMostMove = backwardMostMove = 0;
11681     MarkTargetSquares(1);
11682     InitPosition(redraw);
11683     for (i = 0; i < MAX_MOVES; i++) {
11684         if (commentList[i] != NULL) {
11685             free(commentList[i]);
11686             commentList[i] = NULL;
11687         }
11688     }
11689     ResetClocks();
11690     timeRemaining[0][0] = whiteTimeRemaining;
11691     timeRemaining[1][0] = blackTimeRemaining;
11692
11693     if (first.pr == NoProc) {
11694         StartChessProgram(&first);
11695     }
11696     if (init) {
11697             InitChessProgram(&first, startedFromSetupPosition);
11698     }
11699     DisplayTitle("");
11700     DisplayMessage("", "");
11701     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11702     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11703     ClearMap();        // [HGM] exclude: invalidate map
11704 }
11705
11706 void
11707 AutoPlayGameLoop ()
11708 {
11709     for (;;) {
11710         if (!AutoPlayOneMove())
11711           return;
11712         if (matchMode || appData.timeDelay == 0)
11713           continue;
11714         if (appData.timeDelay < 0)
11715           return;
11716         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11717         break;
11718     }
11719 }
11720
11721 void
11722 AnalyzeNextGame()
11723 {
11724     ReloadGame(1); // next game
11725 }
11726
11727 int
11728 AutoPlayOneMove ()
11729 {
11730     int fromX, fromY, toX, toY;
11731
11732     if (appData.debugMode) {
11733       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11734     }
11735
11736     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11737       return FALSE;
11738
11739     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11740       pvInfoList[currentMove].depth = programStats.depth;
11741       pvInfoList[currentMove].score = programStats.score;
11742       pvInfoList[currentMove].time  = 0;
11743       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11744       else { // append analysis of final position as comment
11745         char buf[MSG_SIZ];
11746         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11747         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11748       }
11749       programStats.depth = 0;
11750     }
11751
11752     if (currentMove >= forwardMostMove) {
11753       if(gameMode == AnalyzeFile) {
11754           if(appData.loadGameIndex == -1) {
11755             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11756           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11757           } else {
11758           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11759         }
11760       }
11761 //      gameMode = EndOfGame;
11762 //      ModeHighlight();
11763
11764       /* [AS] Clear current move marker at the end of a game */
11765       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11766
11767       return FALSE;
11768     }
11769
11770     toX = moveList[currentMove][2] - AAA;
11771     toY = moveList[currentMove][3] - ONE;
11772
11773     if (moveList[currentMove][1] == '@') {
11774         if (appData.highlightLastMove) {
11775             SetHighlights(-1, -1, toX, toY);
11776         }
11777     } else {
11778         int viaX = moveList[currentMove][5] - AAA;
11779         int viaY = moveList[currentMove][6] - ONE;
11780         fromX = moveList[currentMove][0] - AAA;
11781         fromY = moveList[currentMove][1] - ONE;
11782
11783         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11784
11785         if(moveList[currentMove][4] == ';') { // multi-leg
11786             ChessSquare piece = boards[currentMove][viaY][viaX];
11787             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11788             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11789             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11790             boards[currentMove][viaY][viaX] = piece;
11791         } else
11792         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11793
11794         if (appData.highlightLastMove) {
11795             SetHighlights(fromX, fromY, toX, toY);
11796         }
11797     }
11798     DisplayMove(currentMove);
11799     SendMoveToProgram(currentMove++, &first);
11800     DisplayBothClocks();
11801     DrawPosition(FALSE, boards[currentMove]);
11802     // [HGM] PV info: always display, routine tests if empty
11803     DisplayComment(currentMove - 1, commentList[currentMove]);
11804     return TRUE;
11805 }
11806
11807
11808 int
11809 LoadGameOneMove (ChessMove readAhead)
11810 {
11811     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11812     char promoChar = NULLCHAR;
11813     ChessMove moveType;
11814     char move[MSG_SIZ];
11815     char *p, *q;
11816
11817     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11818         gameMode != AnalyzeMode && gameMode != Training) {
11819         gameFileFP = NULL;
11820         return FALSE;
11821     }
11822
11823     yyboardindex = forwardMostMove;
11824     if (readAhead != EndOfFile) {
11825       moveType = readAhead;
11826     } else {
11827       if (gameFileFP == NULL)
11828           return FALSE;
11829       moveType = (ChessMove) Myylex();
11830     }
11831
11832     done = FALSE;
11833     switch (moveType) {
11834       case Comment:
11835         if (appData.debugMode)
11836           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11837         p = yy_text;
11838
11839         /* append the comment but don't display it */
11840         AppendComment(currentMove, p, FALSE);
11841         return TRUE;
11842
11843       case WhiteCapturesEnPassant:
11844       case BlackCapturesEnPassant:
11845       case WhitePromotion:
11846       case BlackPromotion:
11847       case WhiteNonPromotion:
11848       case BlackNonPromotion:
11849       case NormalMove:
11850       case FirstLeg:
11851       case WhiteKingSideCastle:
11852       case WhiteQueenSideCastle:
11853       case BlackKingSideCastle:
11854       case BlackQueenSideCastle:
11855       case WhiteKingSideCastleWild:
11856       case WhiteQueenSideCastleWild:
11857       case BlackKingSideCastleWild:
11858       case BlackQueenSideCastleWild:
11859       /* PUSH Fabien */
11860       case WhiteHSideCastleFR:
11861       case WhiteASideCastleFR:
11862       case BlackHSideCastleFR:
11863       case BlackASideCastleFR:
11864       /* POP Fabien */
11865         if (appData.debugMode)
11866           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11867         fromX = currentMoveString[0] - AAA;
11868         fromY = currentMoveString[1] - ONE;
11869         toX = currentMoveString[2] - AAA;
11870         toY = currentMoveString[3] - ONE;
11871         promoChar = currentMoveString[4];
11872         if(promoChar == ';') promoChar = NULLCHAR;
11873         break;
11874
11875       case WhiteDrop:
11876       case BlackDrop:
11877         if (appData.debugMode)
11878           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11879         fromX = moveType == WhiteDrop ?
11880           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11881         (int) CharToPiece(ToLower(currentMoveString[0]));
11882         fromY = DROP_RANK;
11883         toX = currentMoveString[2] - AAA;
11884         toY = currentMoveString[3] - ONE;
11885         break;
11886
11887       case WhiteWins:
11888       case BlackWins:
11889       case GameIsDrawn:
11890       case GameUnfinished:
11891         if (appData.debugMode)
11892           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11893         p = strchr(yy_text, '{');
11894         if (p == NULL) p = strchr(yy_text, '(');
11895         if (p == NULL) {
11896             p = yy_text;
11897             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11898         } else {
11899             q = strchr(p, *p == '{' ? '}' : ')');
11900             if (q != NULL) *q = NULLCHAR;
11901             p++;
11902         }
11903         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11904         GameEnds(moveType, p, GE_FILE);
11905         done = TRUE;
11906         if (cmailMsgLoaded) {
11907             ClearHighlights();
11908             flipView = WhiteOnMove(currentMove);
11909             if (moveType == GameUnfinished) flipView = !flipView;
11910             if (appData.debugMode)
11911               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11912         }
11913         break;
11914
11915       case EndOfFile:
11916         if (appData.debugMode)
11917           fprintf(debugFP, "Parser hit end of file\n");
11918         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11919           case MT_NONE:
11920           case MT_CHECK:
11921             break;
11922           case MT_CHECKMATE:
11923           case MT_STAINMATE:
11924             if (WhiteOnMove(currentMove)) {
11925                 GameEnds(BlackWins, "Black mates", GE_FILE);
11926             } else {
11927                 GameEnds(WhiteWins, "White mates", GE_FILE);
11928             }
11929             break;
11930           case MT_STALEMATE:
11931             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11932             break;
11933         }
11934         done = TRUE;
11935         break;
11936
11937       case MoveNumberOne:
11938         if (lastLoadGameStart == GNUChessGame) {
11939             /* GNUChessGames have numbers, but they aren't move numbers */
11940             if (appData.debugMode)
11941               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11942                       yy_text, (int) moveType);
11943             return LoadGameOneMove(EndOfFile); /* tail recursion */
11944         }
11945         /* else fall thru */
11946
11947       case XBoardGame:
11948       case GNUChessGame:
11949       case PGNTag:
11950         /* Reached start of next game in file */
11951         if (appData.debugMode)
11952           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11953         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11954           case MT_NONE:
11955           case MT_CHECK:
11956             break;
11957           case MT_CHECKMATE:
11958           case MT_STAINMATE:
11959             if (WhiteOnMove(currentMove)) {
11960                 GameEnds(BlackWins, "Black mates", GE_FILE);
11961             } else {
11962                 GameEnds(WhiteWins, "White mates", GE_FILE);
11963             }
11964             break;
11965           case MT_STALEMATE:
11966             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11967             break;
11968         }
11969         done = TRUE;
11970         break;
11971
11972       case PositionDiagram:     /* should not happen; ignore */
11973       case ElapsedTime:         /* ignore */
11974       case NAG:                 /* ignore */
11975         if (appData.debugMode)
11976           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11977                   yy_text, (int) moveType);
11978         return LoadGameOneMove(EndOfFile); /* tail recursion */
11979
11980       case IllegalMove:
11981         if (appData.testLegality) {
11982             if (appData.debugMode)
11983               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11984             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11985                     (forwardMostMove / 2) + 1,
11986                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11987             DisplayError(move, 0);
11988             done = TRUE;
11989         } else {
11990             if (appData.debugMode)
11991               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11992                       yy_text, currentMoveString);
11993             fromX = currentMoveString[0] - AAA;
11994             fromY = currentMoveString[1] - ONE;
11995             toX = currentMoveString[2] - AAA;
11996             toY = currentMoveString[3] - ONE;
11997             promoChar = currentMoveString[4];
11998         }
11999         break;
12000
12001       case AmbiguousMove:
12002         if (appData.debugMode)
12003           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12004         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12005                 (forwardMostMove / 2) + 1,
12006                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12007         DisplayError(move, 0);
12008         done = TRUE;
12009         break;
12010
12011       default:
12012       case ImpossibleMove:
12013         if (appData.debugMode)
12014           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12015         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12016                 (forwardMostMove / 2) + 1,
12017                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12018         DisplayError(move, 0);
12019         done = TRUE;
12020         break;
12021     }
12022
12023     if (done) {
12024         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12025             DrawPosition(FALSE, boards[currentMove]);
12026             DisplayBothClocks();
12027             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12028               DisplayComment(currentMove - 1, commentList[currentMove]);
12029         }
12030         (void) StopLoadGameTimer();
12031         gameFileFP = NULL;
12032         cmailOldMove = forwardMostMove;
12033         return FALSE;
12034     } else {
12035         /* currentMoveString is set as a side-effect of yylex */
12036
12037         thinkOutput[0] = NULLCHAR;
12038         MakeMove(fromX, fromY, toX, toY, promoChar);
12039         killX = killY = -1; // [HGM] lion: used up
12040         currentMove = forwardMostMove;
12041         return TRUE;
12042     }
12043 }
12044
12045 /* Load the nth game from the given file */
12046 int
12047 LoadGameFromFile (char *filename, int n, char *title, int useList)
12048 {
12049     FILE *f;
12050     char buf[MSG_SIZ];
12051
12052     if (strcmp(filename, "-") == 0) {
12053         f = stdin;
12054         title = "stdin";
12055     } else {
12056         f = fopen(filename, "rb");
12057         if (f == NULL) {
12058           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12059             DisplayError(buf, errno);
12060             return FALSE;
12061         }
12062     }
12063     if (fseek(f, 0, 0) == -1) {
12064         /* f is not seekable; probably a pipe */
12065         useList = FALSE;
12066     }
12067     if (useList && n == 0) {
12068         int error = GameListBuild(f);
12069         if (error) {
12070             DisplayError(_("Cannot build game list"), error);
12071         } else if (!ListEmpty(&gameList) &&
12072                    ((ListGame *) gameList.tailPred)->number > 1) {
12073             GameListPopUp(f, title);
12074             return TRUE;
12075         }
12076         GameListDestroy();
12077         n = 1;
12078     }
12079     if (n == 0) n = 1;
12080     return LoadGame(f, n, title, FALSE);
12081 }
12082
12083
12084 void
12085 MakeRegisteredMove ()
12086 {
12087     int fromX, fromY, toX, toY;
12088     char promoChar;
12089     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12090         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12091           case CMAIL_MOVE:
12092           case CMAIL_DRAW:
12093             if (appData.debugMode)
12094               fprintf(debugFP, "Restoring %s for game %d\n",
12095                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12096
12097             thinkOutput[0] = NULLCHAR;
12098             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12099             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12100             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12101             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12102             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12103             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12104             MakeMove(fromX, fromY, toX, toY, promoChar);
12105             ShowMove(fromX, fromY, toX, toY);
12106
12107             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12108               case MT_NONE:
12109               case MT_CHECK:
12110                 break;
12111
12112               case MT_CHECKMATE:
12113               case MT_STAINMATE:
12114                 if (WhiteOnMove(currentMove)) {
12115                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12116                 } else {
12117                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12118                 }
12119                 break;
12120
12121               case MT_STALEMATE:
12122                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12123                 break;
12124             }
12125
12126             break;
12127
12128           case CMAIL_RESIGN:
12129             if (WhiteOnMove(currentMove)) {
12130                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12131             } else {
12132                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12133             }
12134             break;
12135
12136           case CMAIL_ACCEPT:
12137             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12138             break;
12139
12140           default:
12141             break;
12142         }
12143     }
12144
12145     return;
12146 }
12147
12148 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12149 int
12150 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12151 {
12152     int retVal;
12153
12154     if (gameNumber > nCmailGames) {
12155         DisplayError(_("No more games in this message"), 0);
12156         return FALSE;
12157     }
12158     if (f == lastLoadGameFP) {
12159         int offset = gameNumber - lastLoadGameNumber;
12160         if (offset == 0) {
12161             cmailMsg[0] = NULLCHAR;
12162             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12163                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12164                 nCmailMovesRegistered--;
12165             }
12166             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12167             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12168                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12169             }
12170         } else {
12171             if (! RegisterMove()) return FALSE;
12172         }
12173     }
12174
12175     retVal = LoadGame(f, gameNumber, title, useList);
12176
12177     /* Make move registered during previous look at this game, if any */
12178     MakeRegisteredMove();
12179
12180     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12181         commentList[currentMove]
12182           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12183         DisplayComment(currentMove - 1, commentList[currentMove]);
12184     }
12185
12186     return retVal;
12187 }
12188
12189 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12190 int
12191 ReloadGame (int offset)
12192 {
12193     int gameNumber = lastLoadGameNumber + offset;
12194     if (lastLoadGameFP == NULL) {
12195         DisplayError(_("No game has been loaded yet"), 0);
12196         return FALSE;
12197     }
12198     if (gameNumber <= 0) {
12199         DisplayError(_("Can't back up any further"), 0);
12200         return FALSE;
12201     }
12202     if (cmailMsgLoaded) {
12203         return CmailLoadGame(lastLoadGameFP, gameNumber,
12204                              lastLoadGameTitle, lastLoadGameUseList);
12205     } else {
12206         return LoadGame(lastLoadGameFP, gameNumber,
12207                         lastLoadGameTitle, lastLoadGameUseList);
12208     }
12209 }
12210
12211 int keys[EmptySquare+1];
12212
12213 int
12214 PositionMatches (Board b1, Board b2)
12215 {
12216     int r, f, sum=0;
12217     switch(appData.searchMode) {
12218         case 1: return CompareWithRights(b1, b2);
12219         case 2:
12220             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12221                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12222             }
12223             return TRUE;
12224         case 3:
12225             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12226               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12227                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12228             }
12229             return sum==0;
12230         case 4:
12231             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12232                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12233             }
12234             return sum==0;
12235     }
12236     return TRUE;
12237 }
12238
12239 #define Q_PROMO  4
12240 #define Q_EP     3
12241 #define Q_BCASTL 2
12242 #define Q_WCASTL 1
12243
12244 int pieceList[256], quickBoard[256];
12245 ChessSquare pieceType[256] = { EmptySquare };
12246 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12247 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12248 int soughtTotal, turn;
12249 Boolean epOK, flipSearch;
12250
12251 typedef struct {
12252     unsigned char piece, to;
12253 } Move;
12254
12255 #define DSIZE (250000)
12256
12257 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12258 Move *moveDatabase = initialSpace;
12259 unsigned int movePtr, dataSize = DSIZE;
12260
12261 int
12262 MakePieceList (Board board, int *counts)
12263 {
12264     int r, f, n=Q_PROMO, total=0;
12265     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12266     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12267         int sq = f + (r<<4);
12268         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12269             quickBoard[sq] = ++n;
12270             pieceList[n] = sq;
12271             pieceType[n] = board[r][f];
12272             counts[board[r][f]]++;
12273             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12274             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12275             total++;
12276         }
12277     }
12278     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12279     return total;
12280 }
12281
12282 void
12283 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12284 {
12285     int sq = fromX + (fromY<<4);
12286     int piece = quickBoard[sq], rook;
12287     quickBoard[sq] = 0;
12288     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12289     if(piece == pieceList[1] && fromY == toY) {
12290       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12291         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12292         moveDatabase[movePtr++].piece = Q_WCASTL;
12293         quickBoard[sq] = piece;
12294         piece = quickBoard[from]; quickBoard[from] = 0;
12295         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12296       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12297         quickBoard[sq] = 0; // remove Rook
12298         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12299         moveDatabase[movePtr++].piece = Q_WCASTL;
12300         quickBoard[sq] = pieceList[1]; // put King
12301         piece = rook;
12302         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12303       }
12304     } else
12305     if(piece == pieceList[2] && fromY == toY) {
12306       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12307         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12308         moveDatabase[movePtr++].piece = Q_BCASTL;
12309         quickBoard[sq] = piece;
12310         piece = quickBoard[from]; quickBoard[from] = 0;
12311         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12312       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12313         quickBoard[sq] = 0; // remove Rook
12314         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12315         moveDatabase[movePtr++].piece = Q_BCASTL;
12316         quickBoard[sq] = pieceList[2]; // put King
12317         piece = rook;
12318         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12319       }
12320     } else
12321     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12322         quickBoard[(fromY<<4)+toX] = 0;
12323         moveDatabase[movePtr].piece = Q_EP;
12324         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12325         moveDatabase[movePtr].to = sq;
12326     } else
12327     if(promoPiece != pieceType[piece]) {
12328         moveDatabase[movePtr++].piece = Q_PROMO;
12329         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12330     }
12331     moveDatabase[movePtr].piece = piece;
12332     quickBoard[sq] = piece;
12333     movePtr++;
12334 }
12335
12336 int
12337 PackGame (Board board)
12338 {
12339     Move *newSpace = NULL;
12340     moveDatabase[movePtr].piece = 0; // terminate previous game
12341     if(movePtr > dataSize) {
12342         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12343         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12344         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12345         if(newSpace) {
12346             int i;
12347             Move *p = moveDatabase, *q = newSpace;
12348             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12349             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12350             moveDatabase = newSpace;
12351         } else { // calloc failed, we must be out of memory. Too bad...
12352             dataSize = 0; // prevent calloc events for all subsequent games
12353             return 0;     // and signal this one isn't cached
12354         }
12355     }
12356     movePtr++;
12357     MakePieceList(board, counts);
12358     return movePtr;
12359 }
12360
12361 int
12362 QuickCompare (Board board, int *minCounts, int *maxCounts)
12363 {   // compare according to search mode
12364     int r, f;
12365     switch(appData.searchMode)
12366     {
12367       case 1: // exact position match
12368         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12369         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12370             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12371         }
12372         break;
12373       case 2: // can have extra material on empty squares
12374         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12375             if(board[r][f] == EmptySquare) continue;
12376             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12377         }
12378         break;
12379       case 3: // material with exact Pawn structure
12380         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12381             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12382             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12383         } // fall through to material comparison
12384       case 4: // exact material
12385         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12386         break;
12387       case 6: // material range with given imbalance
12388         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12389         // fall through to range comparison
12390       case 5: // material range
12391         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12392     }
12393     return TRUE;
12394 }
12395
12396 int
12397 QuickScan (Board board, Move *move)
12398 {   // reconstruct game,and compare all positions in it
12399     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12400     do {
12401         int piece = move->piece;
12402         int to = move->to, from = pieceList[piece];
12403         if(found < 0) { // if already found just scan to game end for final piece count
12404           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12405            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12406            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12407                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12408             ) {
12409             static int lastCounts[EmptySquare+1];
12410             int i;
12411             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12412             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12413           } else stretch = 0;
12414           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12415           if(found >= 0 && !appData.minPieces) return found;
12416         }
12417         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12418           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12419           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12420             piece = (++move)->piece;
12421             from = pieceList[piece];
12422             counts[pieceType[piece]]--;
12423             pieceType[piece] = (ChessSquare) move->to;
12424             counts[move->to]++;
12425           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12426             counts[pieceType[quickBoard[to]]]--;
12427             quickBoard[to] = 0; total--;
12428             move++;
12429             continue;
12430           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12431             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12432             from  = pieceList[piece]; // so this must be King
12433             quickBoard[from] = 0;
12434             pieceList[piece] = to;
12435             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12436             quickBoard[from] = 0; // rook
12437             quickBoard[to] = piece;
12438             to = move->to; piece = move->piece;
12439             goto aftercastle;
12440           }
12441         }
12442         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12443         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12444         quickBoard[from] = 0;
12445       aftercastle:
12446         quickBoard[to] = piece;
12447         pieceList[piece] = to;
12448         cnt++; turn ^= 3;
12449         move++;
12450     } while(1);
12451 }
12452
12453 void
12454 InitSearch ()
12455 {
12456     int r, f;
12457     flipSearch = FALSE;
12458     CopyBoard(soughtBoard, boards[currentMove]);
12459     soughtTotal = MakePieceList(soughtBoard, maxSought);
12460     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12461     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12462     CopyBoard(reverseBoard, boards[currentMove]);
12463     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12464         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12465         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12466         reverseBoard[r][f] = piece;
12467     }
12468     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12469     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12470     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12471                  || (boards[currentMove][CASTLING][2] == NoRights ||
12472                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12473                  && (boards[currentMove][CASTLING][5] == NoRights ||
12474                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12475       ) {
12476         flipSearch = TRUE;
12477         CopyBoard(flipBoard, soughtBoard);
12478         CopyBoard(rotateBoard, reverseBoard);
12479         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12480             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12481             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12482         }
12483     }
12484     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12485     if(appData.searchMode >= 5) {
12486         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12487         MakePieceList(soughtBoard, minSought);
12488         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12489     }
12490     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12491         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12492 }
12493
12494 GameInfo dummyInfo;
12495 static int creatingBook;
12496
12497 int
12498 GameContainsPosition (FILE *f, ListGame *lg)
12499 {
12500     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12501     int fromX, fromY, toX, toY;
12502     char promoChar;
12503     static int initDone=FALSE;
12504
12505     // weed out games based on numerical tag comparison
12506     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12507     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12508     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12509     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12510     if(!initDone) {
12511         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12512         initDone = TRUE;
12513     }
12514     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12515     else CopyBoard(boards[scratch], initialPosition); // default start position
12516     if(lg->moves) {
12517         turn = btm + 1;
12518         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12519         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12520     }
12521     if(btm) plyNr++;
12522     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12523     fseek(f, lg->offset, 0);
12524     yynewfile(f);
12525     while(1) {
12526         yyboardindex = scratch;
12527         quickFlag = plyNr+1;
12528         next = Myylex();
12529         quickFlag = 0;
12530         switch(next) {
12531             case PGNTag:
12532                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12533             default:
12534                 continue;
12535
12536             case XBoardGame:
12537             case GNUChessGame:
12538                 if(plyNr) return -1; // after we have seen moves, this is for new game
12539               continue;
12540
12541             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12542             case ImpossibleMove:
12543             case WhiteWins: // game ends here with these four
12544             case BlackWins:
12545             case GameIsDrawn:
12546             case GameUnfinished:
12547                 return -1;
12548
12549             case IllegalMove:
12550                 if(appData.testLegality) return -1;
12551             case WhiteCapturesEnPassant:
12552             case BlackCapturesEnPassant:
12553             case WhitePromotion:
12554             case BlackPromotion:
12555             case WhiteNonPromotion:
12556             case BlackNonPromotion:
12557             case NormalMove:
12558             case FirstLeg:
12559             case WhiteKingSideCastle:
12560             case WhiteQueenSideCastle:
12561             case BlackKingSideCastle:
12562             case BlackQueenSideCastle:
12563             case WhiteKingSideCastleWild:
12564             case WhiteQueenSideCastleWild:
12565             case BlackKingSideCastleWild:
12566             case BlackQueenSideCastleWild:
12567             case WhiteHSideCastleFR:
12568             case WhiteASideCastleFR:
12569             case BlackHSideCastleFR:
12570             case BlackASideCastleFR:
12571                 fromX = currentMoveString[0] - AAA;
12572                 fromY = currentMoveString[1] - ONE;
12573                 toX = currentMoveString[2] - AAA;
12574                 toY = currentMoveString[3] - ONE;
12575                 promoChar = currentMoveString[4];
12576                 break;
12577             case WhiteDrop:
12578             case BlackDrop:
12579                 fromX = next == WhiteDrop ?
12580                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12581                   (int) CharToPiece(ToLower(currentMoveString[0]));
12582                 fromY = DROP_RANK;
12583                 toX = currentMoveString[2] - AAA;
12584                 toY = currentMoveString[3] - ONE;
12585                 promoChar = 0;
12586                 break;
12587         }
12588         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12589         plyNr++;
12590         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12591         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12592         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12593         if(appData.findMirror) {
12594             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12595             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12596         }
12597     }
12598 }
12599
12600 /* Load the nth game from open file f */
12601 int
12602 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12603 {
12604     ChessMove cm;
12605     char buf[MSG_SIZ];
12606     int gn = gameNumber;
12607     ListGame *lg = NULL;
12608     int numPGNTags = 0;
12609     int err, pos = -1;
12610     GameMode oldGameMode;
12611     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12612
12613     if (appData.debugMode)
12614         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12615
12616     if (gameMode == Training )
12617         SetTrainingModeOff();
12618
12619     oldGameMode = gameMode;
12620     if (gameMode != BeginningOfGame) {
12621       Reset(FALSE, TRUE);
12622     }
12623     killX = killY = -1; // [HGM] lion: in case we did not Reset
12624
12625     gameFileFP = f;
12626     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12627         fclose(lastLoadGameFP);
12628     }
12629
12630     if (useList) {
12631         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12632
12633         if (lg) {
12634             fseek(f, lg->offset, 0);
12635             GameListHighlight(gameNumber);
12636             pos = lg->position;
12637             gn = 1;
12638         }
12639         else {
12640             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12641               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12642             else
12643             DisplayError(_("Game number out of range"), 0);
12644             return FALSE;
12645         }
12646     } else {
12647         GameListDestroy();
12648         if (fseek(f, 0, 0) == -1) {
12649             if (f == lastLoadGameFP ?
12650                 gameNumber == lastLoadGameNumber + 1 :
12651                 gameNumber == 1) {
12652                 gn = 1;
12653             } else {
12654                 DisplayError(_("Can't seek on game file"), 0);
12655                 return FALSE;
12656             }
12657         }
12658     }
12659     lastLoadGameFP = f;
12660     lastLoadGameNumber = gameNumber;
12661     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12662     lastLoadGameUseList = useList;
12663
12664     yynewfile(f);
12665
12666     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12667       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12668                 lg->gameInfo.black);
12669             DisplayTitle(buf);
12670     } else if (*title != NULLCHAR) {
12671         if (gameNumber > 1) {
12672           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12673             DisplayTitle(buf);
12674         } else {
12675             DisplayTitle(title);
12676         }
12677     }
12678
12679     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12680         gameMode = PlayFromGameFile;
12681         ModeHighlight();
12682     }
12683
12684     currentMove = forwardMostMove = backwardMostMove = 0;
12685     CopyBoard(boards[0], initialPosition);
12686     StopClocks();
12687
12688     /*
12689      * Skip the first gn-1 games in the file.
12690      * Also skip over anything that precedes an identifiable
12691      * start of game marker, to avoid being confused by
12692      * garbage at the start of the file.  Currently
12693      * recognized start of game markers are the move number "1",
12694      * the pattern "gnuchess .* game", the pattern
12695      * "^[#;%] [^ ]* game file", and a PGN tag block.
12696      * A game that starts with one of the latter two patterns
12697      * will also have a move number 1, possibly
12698      * following a position diagram.
12699      * 5-4-02: Let's try being more lenient and allowing a game to
12700      * start with an unnumbered move.  Does that break anything?
12701      */
12702     cm = lastLoadGameStart = EndOfFile;
12703     while (gn > 0) {
12704         yyboardindex = forwardMostMove;
12705         cm = (ChessMove) Myylex();
12706         switch (cm) {
12707           case EndOfFile:
12708             if (cmailMsgLoaded) {
12709                 nCmailGames = CMAIL_MAX_GAMES - gn;
12710             } else {
12711                 Reset(TRUE, TRUE);
12712                 DisplayError(_("Game not found in file"), 0);
12713             }
12714             return FALSE;
12715
12716           case GNUChessGame:
12717           case XBoardGame:
12718             gn--;
12719             lastLoadGameStart = cm;
12720             break;
12721
12722           case MoveNumberOne:
12723             switch (lastLoadGameStart) {
12724               case GNUChessGame:
12725               case XBoardGame:
12726               case PGNTag:
12727                 break;
12728               case MoveNumberOne:
12729               case EndOfFile:
12730                 gn--;           /* count this game */
12731                 lastLoadGameStart = cm;
12732                 break;
12733               default:
12734                 /* impossible */
12735                 break;
12736             }
12737             break;
12738
12739           case PGNTag:
12740             switch (lastLoadGameStart) {
12741               case GNUChessGame:
12742               case PGNTag:
12743               case MoveNumberOne:
12744               case EndOfFile:
12745                 gn--;           /* count this game */
12746                 lastLoadGameStart = cm;
12747                 break;
12748               case XBoardGame:
12749                 lastLoadGameStart = cm; /* game counted already */
12750                 break;
12751               default:
12752                 /* impossible */
12753                 break;
12754             }
12755             if (gn > 0) {
12756                 do {
12757                     yyboardindex = forwardMostMove;
12758                     cm = (ChessMove) Myylex();
12759                 } while (cm == PGNTag || cm == Comment);
12760             }
12761             break;
12762
12763           case WhiteWins:
12764           case BlackWins:
12765           case GameIsDrawn:
12766             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12767                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12768                     != CMAIL_OLD_RESULT) {
12769                     nCmailResults ++ ;
12770                     cmailResult[  CMAIL_MAX_GAMES
12771                                 - gn - 1] = CMAIL_OLD_RESULT;
12772                 }
12773             }
12774             break;
12775
12776           case NormalMove:
12777           case FirstLeg:
12778             /* Only a NormalMove can be at the start of a game
12779              * without a position diagram. */
12780             if (lastLoadGameStart == EndOfFile ) {
12781               gn--;
12782               lastLoadGameStart = MoveNumberOne;
12783             }
12784             break;
12785
12786           default:
12787             break;
12788         }
12789     }
12790
12791     if (appData.debugMode)
12792       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12793
12794     if (cm == XBoardGame) {
12795         /* Skip any header junk before position diagram and/or move 1 */
12796         for (;;) {
12797             yyboardindex = forwardMostMove;
12798             cm = (ChessMove) Myylex();
12799
12800             if (cm == EndOfFile ||
12801                 cm == GNUChessGame || cm == XBoardGame) {
12802                 /* Empty game; pretend end-of-file and handle later */
12803                 cm = EndOfFile;
12804                 break;
12805             }
12806
12807             if (cm == MoveNumberOne || cm == PositionDiagram ||
12808                 cm == PGNTag || cm == Comment)
12809               break;
12810         }
12811     } else if (cm == GNUChessGame) {
12812         if (gameInfo.event != NULL) {
12813             free(gameInfo.event);
12814         }
12815         gameInfo.event = StrSave(yy_text);
12816     }
12817
12818     startedFromSetupPosition = FALSE;
12819     while (cm == PGNTag) {
12820         if (appData.debugMode)
12821           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12822         err = ParsePGNTag(yy_text, &gameInfo);
12823         if (!err) numPGNTags++;
12824
12825         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12826         if(gameInfo.variant != oldVariant) {
12827             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12828             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12829             InitPosition(TRUE);
12830             oldVariant = gameInfo.variant;
12831             if (appData.debugMode)
12832               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12833         }
12834
12835
12836         if (gameInfo.fen != NULL) {
12837           Board initial_position;
12838           startedFromSetupPosition = TRUE;
12839           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12840             Reset(TRUE, TRUE);
12841             DisplayError(_("Bad FEN position in file"), 0);
12842             return FALSE;
12843           }
12844           CopyBoard(boards[0], initial_position);
12845           if (blackPlaysFirst) {
12846             currentMove = forwardMostMove = backwardMostMove = 1;
12847             CopyBoard(boards[1], initial_position);
12848             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12849             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12850             timeRemaining[0][1] = whiteTimeRemaining;
12851             timeRemaining[1][1] = blackTimeRemaining;
12852             if (commentList[0] != NULL) {
12853               commentList[1] = commentList[0];
12854               commentList[0] = NULL;
12855             }
12856           } else {
12857             currentMove = forwardMostMove = backwardMostMove = 0;
12858           }
12859           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12860           {   int i;
12861               initialRulePlies = FENrulePlies;
12862               for( i=0; i< nrCastlingRights; i++ )
12863                   initialRights[i] = initial_position[CASTLING][i];
12864           }
12865           yyboardindex = forwardMostMove;
12866           free(gameInfo.fen);
12867           gameInfo.fen = NULL;
12868         }
12869
12870         yyboardindex = forwardMostMove;
12871         cm = (ChessMove) Myylex();
12872
12873         /* Handle comments interspersed among the tags */
12874         while (cm == Comment) {
12875             char *p;
12876             if (appData.debugMode)
12877               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12878             p = yy_text;
12879             AppendComment(currentMove, p, FALSE);
12880             yyboardindex = forwardMostMove;
12881             cm = (ChessMove) Myylex();
12882         }
12883     }
12884
12885     /* don't rely on existence of Event tag since if game was
12886      * pasted from clipboard the Event tag may not exist
12887      */
12888     if (numPGNTags > 0){
12889         char *tags;
12890         if (gameInfo.variant == VariantNormal) {
12891           VariantClass v = StringToVariant(gameInfo.event);
12892           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12893           if(v < VariantShogi) gameInfo.variant = v;
12894         }
12895         if (!matchMode) {
12896           if( appData.autoDisplayTags ) {
12897             tags = PGNTags(&gameInfo);
12898             TagsPopUp(tags, CmailMsg());
12899             free(tags);
12900           }
12901         }
12902     } else {
12903         /* Make something up, but don't display it now */
12904         SetGameInfo();
12905         TagsPopDown();
12906     }
12907
12908     if (cm == PositionDiagram) {
12909         int i, j;
12910         char *p;
12911         Board initial_position;
12912
12913         if (appData.debugMode)
12914           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12915
12916         if (!startedFromSetupPosition) {
12917             p = yy_text;
12918             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12919               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12920                 switch (*p) {
12921                   case '{':
12922                   case '[':
12923                   case '-':
12924                   case ' ':
12925                   case '\t':
12926                   case '\n':
12927                   case '\r':
12928                     break;
12929                   default:
12930                     initial_position[i][j++] = CharToPiece(*p);
12931                     break;
12932                 }
12933             while (*p == ' ' || *p == '\t' ||
12934                    *p == '\n' || *p == '\r') p++;
12935
12936             if (strncmp(p, "black", strlen("black"))==0)
12937               blackPlaysFirst = TRUE;
12938             else
12939               blackPlaysFirst = FALSE;
12940             startedFromSetupPosition = TRUE;
12941
12942             CopyBoard(boards[0], initial_position);
12943             if (blackPlaysFirst) {
12944                 currentMove = forwardMostMove = backwardMostMove = 1;
12945                 CopyBoard(boards[1], initial_position);
12946                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12947                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12948                 timeRemaining[0][1] = whiteTimeRemaining;
12949                 timeRemaining[1][1] = blackTimeRemaining;
12950                 if (commentList[0] != NULL) {
12951                     commentList[1] = commentList[0];
12952                     commentList[0] = NULL;
12953                 }
12954             } else {
12955                 currentMove = forwardMostMove = backwardMostMove = 0;
12956             }
12957         }
12958         yyboardindex = forwardMostMove;
12959         cm = (ChessMove) Myylex();
12960     }
12961
12962   if(!creatingBook) {
12963     if (first.pr == NoProc) {
12964         StartChessProgram(&first);
12965     }
12966     InitChessProgram(&first, FALSE);
12967     SendToProgram("force\n", &first);
12968     if (startedFromSetupPosition) {
12969         SendBoard(&first, forwardMostMove);
12970     if (appData.debugMode) {
12971         fprintf(debugFP, "Load Game\n");
12972     }
12973         DisplayBothClocks();
12974     }
12975   }
12976
12977     /* [HGM] server: flag to write setup moves in broadcast file as one */
12978     loadFlag = appData.suppressLoadMoves;
12979
12980     while (cm == Comment) {
12981         char *p;
12982         if (appData.debugMode)
12983           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12984         p = yy_text;
12985         AppendComment(currentMove, p, FALSE);
12986         yyboardindex = forwardMostMove;
12987         cm = (ChessMove) Myylex();
12988     }
12989
12990     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12991         cm == WhiteWins || cm == BlackWins ||
12992         cm == GameIsDrawn || cm == GameUnfinished) {
12993         DisplayMessage("", _("No moves in game"));
12994         if (cmailMsgLoaded) {
12995             if (appData.debugMode)
12996               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12997             ClearHighlights();
12998             flipView = FALSE;
12999         }
13000         DrawPosition(FALSE, boards[currentMove]);
13001         DisplayBothClocks();
13002         gameMode = EditGame;
13003         ModeHighlight();
13004         gameFileFP = NULL;
13005         cmailOldMove = 0;
13006         return TRUE;
13007     }
13008
13009     // [HGM] PV info: routine tests if comment empty
13010     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13011         DisplayComment(currentMove - 1, commentList[currentMove]);
13012     }
13013     if (!matchMode && appData.timeDelay != 0)
13014       DrawPosition(FALSE, boards[currentMove]);
13015
13016     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13017       programStats.ok_to_send = 1;
13018     }
13019
13020     /* if the first token after the PGN tags is a move
13021      * and not move number 1, retrieve it from the parser
13022      */
13023     if (cm != MoveNumberOne)
13024         LoadGameOneMove(cm);
13025
13026     /* load the remaining moves from the file */
13027     while (LoadGameOneMove(EndOfFile)) {
13028       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13029       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13030     }
13031
13032     /* rewind to the start of the game */
13033     currentMove = backwardMostMove;
13034
13035     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13036
13037     if (oldGameMode == AnalyzeFile) {
13038       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13039       AnalyzeFileEvent();
13040     } else
13041     if (oldGameMode == AnalyzeMode) {
13042       AnalyzeFileEvent();
13043     }
13044
13045     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13046         long int w, b; // [HGM] adjourn: restore saved clock times
13047         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13048         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13049             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13050             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13051         }
13052     }
13053
13054     if(creatingBook) return TRUE;
13055     if (!matchMode && pos > 0) {
13056         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13057     } else
13058     if (matchMode || appData.timeDelay == 0) {
13059       ToEndEvent();
13060     } else if (appData.timeDelay > 0) {
13061       AutoPlayGameLoop();
13062     }
13063
13064     if (appData.debugMode)
13065         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13066
13067     loadFlag = 0; /* [HGM] true game starts */
13068     return TRUE;
13069 }
13070
13071 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13072 int
13073 ReloadPosition (int offset)
13074 {
13075     int positionNumber = lastLoadPositionNumber + offset;
13076     if (lastLoadPositionFP == NULL) {
13077         DisplayError(_("No position has been loaded yet"), 0);
13078         return FALSE;
13079     }
13080     if (positionNumber <= 0) {
13081         DisplayError(_("Can't back up any further"), 0);
13082         return FALSE;
13083     }
13084     return LoadPosition(lastLoadPositionFP, positionNumber,
13085                         lastLoadPositionTitle);
13086 }
13087
13088 /* Load the nth position from the given file */
13089 int
13090 LoadPositionFromFile (char *filename, int n, char *title)
13091 {
13092     FILE *f;
13093     char buf[MSG_SIZ];
13094
13095     if (strcmp(filename, "-") == 0) {
13096         return LoadPosition(stdin, n, "stdin");
13097     } else {
13098         f = fopen(filename, "rb");
13099         if (f == NULL) {
13100             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13101             DisplayError(buf, errno);
13102             return FALSE;
13103         } else {
13104             return LoadPosition(f, n, title);
13105         }
13106     }
13107 }
13108
13109 /* Load the nth position from the given open file, and close it */
13110 int
13111 LoadPosition (FILE *f, int positionNumber, char *title)
13112 {
13113     char *p, line[MSG_SIZ];
13114     Board initial_position;
13115     int i, j, fenMode, pn;
13116
13117     if (gameMode == Training )
13118         SetTrainingModeOff();
13119
13120     if (gameMode != BeginningOfGame) {
13121         Reset(FALSE, TRUE);
13122     }
13123     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13124         fclose(lastLoadPositionFP);
13125     }
13126     if (positionNumber == 0) positionNumber = 1;
13127     lastLoadPositionFP = f;
13128     lastLoadPositionNumber = positionNumber;
13129     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13130     if (first.pr == NoProc && !appData.noChessProgram) {
13131       StartChessProgram(&first);
13132       InitChessProgram(&first, FALSE);
13133     }
13134     pn = positionNumber;
13135     if (positionNumber < 0) {
13136         /* Negative position number means to seek to that byte offset */
13137         if (fseek(f, -positionNumber, 0) == -1) {
13138             DisplayError(_("Can't seek on position file"), 0);
13139             return FALSE;
13140         };
13141         pn = 1;
13142     } else {
13143         if (fseek(f, 0, 0) == -1) {
13144             if (f == lastLoadPositionFP ?
13145                 positionNumber == lastLoadPositionNumber + 1 :
13146                 positionNumber == 1) {
13147                 pn = 1;
13148             } else {
13149                 DisplayError(_("Can't seek on position file"), 0);
13150                 return FALSE;
13151             }
13152         }
13153     }
13154     /* See if this file is FEN or old-style xboard */
13155     if (fgets(line, MSG_SIZ, f) == NULL) {
13156         DisplayError(_("Position not found in file"), 0);
13157         return FALSE;
13158     }
13159     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13160     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13161
13162     if (pn >= 2) {
13163         if (fenMode || line[0] == '#') pn--;
13164         while (pn > 0) {
13165             /* skip positions before number pn */
13166             if (fgets(line, MSG_SIZ, f) == NULL) {
13167                 Reset(TRUE, TRUE);
13168                 DisplayError(_("Position not found in file"), 0);
13169                 return FALSE;
13170             }
13171             if (fenMode || line[0] == '#') pn--;
13172         }
13173     }
13174
13175     if (fenMode) {
13176         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13177             DisplayError(_("Bad FEN position in file"), 0);
13178             return FALSE;
13179         }
13180     } else {
13181         (void) fgets(line, MSG_SIZ, f);
13182         (void) fgets(line, MSG_SIZ, f);
13183
13184         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13185             (void) fgets(line, MSG_SIZ, f);
13186             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13187                 if (*p == ' ')
13188                   continue;
13189                 initial_position[i][j++] = CharToPiece(*p);
13190             }
13191         }
13192
13193         blackPlaysFirst = FALSE;
13194         if (!feof(f)) {
13195             (void) fgets(line, MSG_SIZ, f);
13196             if (strncmp(line, "black", strlen("black"))==0)
13197               blackPlaysFirst = TRUE;
13198         }
13199     }
13200     startedFromSetupPosition = TRUE;
13201
13202     CopyBoard(boards[0], initial_position);
13203     if (blackPlaysFirst) {
13204         currentMove = forwardMostMove = backwardMostMove = 1;
13205         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13206         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13207         CopyBoard(boards[1], initial_position);
13208         DisplayMessage("", _("Black to play"));
13209     } else {
13210         currentMove = forwardMostMove = backwardMostMove = 0;
13211         DisplayMessage("", _("White to play"));
13212     }
13213     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13214     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13215         SendToProgram("force\n", &first);
13216         SendBoard(&first, forwardMostMove);
13217     }
13218     if (appData.debugMode) {
13219 int i, j;
13220   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13221   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13222         fprintf(debugFP, "Load Position\n");
13223     }
13224
13225     if (positionNumber > 1) {
13226       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13227         DisplayTitle(line);
13228     } else {
13229         DisplayTitle(title);
13230     }
13231     gameMode = EditGame;
13232     ModeHighlight();
13233     ResetClocks();
13234     timeRemaining[0][1] = whiteTimeRemaining;
13235     timeRemaining[1][1] = blackTimeRemaining;
13236     DrawPosition(FALSE, boards[currentMove]);
13237
13238     return TRUE;
13239 }
13240
13241
13242 void
13243 CopyPlayerNameIntoFileName (char **dest, char *src)
13244 {
13245     while (*src != NULLCHAR && *src != ',') {
13246         if (*src == ' ') {
13247             *(*dest)++ = '_';
13248             src++;
13249         } else {
13250             *(*dest)++ = *src++;
13251         }
13252     }
13253 }
13254
13255 char *
13256 DefaultFileName (char *ext)
13257 {
13258     static char def[MSG_SIZ];
13259     char *p;
13260
13261     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13262         p = def;
13263         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13264         *p++ = '-';
13265         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13266         *p++ = '.';
13267         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13268     } else {
13269         def[0] = NULLCHAR;
13270     }
13271     return def;
13272 }
13273
13274 /* Save the current game to the given file */
13275 int
13276 SaveGameToFile (char *filename, int append)
13277 {
13278     FILE *f;
13279     char buf[MSG_SIZ];
13280     int result, i, t,tot=0;
13281
13282     if (strcmp(filename, "-") == 0) {
13283         return SaveGame(stdout, 0, NULL);
13284     } else {
13285         for(i=0; i<10; i++) { // upto 10 tries
13286              f = fopen(filename, append ? "a" : "w");
13287              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13288              if(f || errno != 13) break;
13289              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13290              tot += t;
13291         }
13292         if (f == NULL) {
13293             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13294             DisplayError(buf, errno);
13295             return FALSE;
13296         } else {
13297             safeStrCpy(buf, lastMsg, MSG_SIZ);
13298             DisplayMessage(_("Waiting for access to save file"), "");
13299             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13300             DisplayMessage(_("Saving game"), "");
13301             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13302             result = SaveGame(f, 0, NULL);
13303             DisplayMessage(buf, "");
13304             return result;
13305         }
13306     }
13307 }
13308
13309 char *
13310 SavePart (char *str)
13311 {
13312     static char buf[MSG_SIZ];
13313     char *p;
13314
13315     p = strchr(str, ' ');
13316     if (p == NULL) return str;
13317     strncpy(buf, str, p - str);
13318     buf[p - str] = NULLCHAR;
13319     return buf;
13320 }
13321
13322 #define PGN_MAX_LINE 75
13323
13324 #define PGN_SIDE_WHITE  0
13325 #define PGN_SIDE_BLACK  1
13326
13327 static int
13328 FindFirstMoveOutOfBook (int side)
13329 {
13330     int result = -1;
13331
13332     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13333         int index = backwardMostMove;
13334         int has_book_hit = 0;
13335
13336         if( (index % 2) != side ) {
13337             index++;
13338         }
13339
13340         while( index < forwardMostMove ) {
13341             /* Check to see if engine is in book */
13342             int depth = pvInfoList[index].depth;
13343             int score = pvInfoList[index].score;
13344             int in_book = 0;
13345
13346             if( depth <= 2 ) {
13347                 in_book = 1;
13348             }
13349             else if( score == 0 && depth == 63 ) {
13350                 in_book = 1; /* Zappa */
13351             }
13352             else if( score == 2 && depth == 99 ) {
13353                 in_book = 1; /* Abrok */
13354             }
13355
13356             has_book_hit += in_book;
13357
13358             if( ! in_book ) {
13359                 result = index;
13360
13361                 break;
13362             }
13363
13364             index += 2;
13365         }
13366     }
13367
13368     return result;
13369 }
13370
13371 void
13372 GetOutOfBookInfo (char * buf)
13373 {
13374     int oob[2];
13375     int i;
13376     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13377
13378     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13379     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13380
13381     *buf = '\0';
13382
13383     if( oob[0] >= 0 || oob[1] >= 0 ) {
13384         for( i=0; i<2; i++ ) {
13385             int idx = oob[i];
13386
13387             if( idx >= 0 ) {
13388                 if( i > 0 && oob[0] >= 0 ) {
13389                     strcat( buf, "   " );
13390                 }
13391
13392                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13393                 sprintf( buf+strlen(buf), "%s%.2f",
13394                     pvInfoList[idx].score >= 0 ? "+" : "",
13395                     pvInfoList[idx].score / 100.0 );
13396             }
13397         }
13398     }
13399 }
13400
13401 /* Save game in PGN style */
13402 static void
13403 SaveGamePGN2 (FILE *f)
13404 {
13405     int i, offset, linelen, newblock;
13406 //    char *movetext;
13407     char numtext[32];
13408     int movelen, numlen, blank;
13409     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13410
13411     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13412
13413     PrintPGNTags(f, &gameInfo);
13414
13415     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13416
13417     if (backwardMostMove > 0 || startedFromSetupPosition) {
13418         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13419         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13420         fprintf(f, "\n{--------------\n");
13421         PrintPosition(f, backwardMostMove);
13422         fprintf(f, "--------------}\n");
13423         free(fen);
13424     }
13425     else {
13426         /* [AS] Out of book annotation */
13427         if( appData.saveOutOfBookInfo ) {
13428             char buf[64];
13429
13430             GetOutOfBookInfo( buf );
13431
13432             if( buf[0] != '\0' ) {
13433                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13434             }
13435         }
13436
13437         fprintf(f, "\n");
13438     }
13439
13440     i = backwardMostMove;
13441     linelen = 0;
13442     newblock = TRUE;
13443
13444     while (i < forwardMostMove) {
13445         /* Print comments preceding this move */
13446         if (commentList[i] != NULL) {
13447             if (linelen > 0) fprintf(f, "\n");
13448             fprintf(f, "%s", commentList[i]);
13449             linelen = 0;
13450             newblock = TRUE;
13451         }
13452
13453         /* Format move number */
13454         if ((i % 2) == 0)
13455           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13456         else
13457           if (newblock)
13458             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13459           else
13460             numtext[0] = NULLCHAR;
13461
13462         numlen = strlen(numtext);
13463         newblock = FALSE;
13464
13465         /* Print move number */
13466         blank = linelen > 0 && numlen > 0;
13467         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13468             fprintf(f, "\n");
13469             linelen = 0;
13470             blank = 0;
13471         }
13472         if (blank) {
13473             fprintf(f, " ");
13474             linelen++;
13475         }
13476         fprintf(f, "%s", numtext);
13477         linelen += numlen;
13478
13479         /* Get move */
13480         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13481         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13482
13483         /* Print move */
13484         blank = linelen > 0 && movelen > 0;
13485         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13486             fprintf(f, "\n");
13487             linelen = 0;
13488             blank = 0;
13489         }
13490         if (blank) {
13491             fprintf(f, " ");
13492             linelen++;
13493         }
13494         fprintf(f, "%s", move_buffer);
13495         linelen += movelen;
13496
13497         /* [AS] Add PV info if present */
13498         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13499             /* [HGM] add time */
13500             char buf[MSG_SIZ]; int seconds;
13501
13502             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13503
13504             if( seconds <= 0)
13505               buf[0] = 0;
13506             else
13507               if( seconds < 30 )
13508                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13509               else
13510                 {
13511                   seconds = (seconds + 4)/10; // round to full seconds
13512                   if( seconds < 60 )
13513                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13514                   else
13515                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13516                 }
13517
13518             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13519                       pvInfoList[i].score >= 0 ? "+" : "",
13520                       pvInfoList[i].score / 100.0,
13521                       pvInfoList[i].depth,
13522                       buf );
13523
13524             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13525
13526             /* Print score/depth */
13527             blank = linelen > 0 && movelen > 0;
13528             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13529                 fprintf(f, "\n");
13530                 linelen = 0;
13531                 blank = 0;
13532             }
13533             if (blank) {
13534                 fprintf(f, " ");
13535                 linelen++;
13536             }
13537             fprintf(f, "%s", move_buffer);
13538             linelen += movelen;
13539         }
13540
13541         i++;
13542     }
13543
13544     /* Start a new line */
13545     if (linelen > 0) fprintf(f, "\n");
13546
13547     /* Print comments after last move */
13548     if (commentList[i] != NULL) {
13549         fprintf(f, "%s\n", commentList[i]);
13550     }
13551
13552     /* Print result */
13553     if (gameInfo.resultDetails != NULL &&
13554         gameInfo.resultDetails[0] != NULLCHAR) {
13555         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13556         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13557            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13558             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13559         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13560     } else {
13561         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13562     }
13563 }
13564
13565 /* Save game in PGN style and close the file */
13566 int
13567 SaveGamePGN (FILE *f)
13568 {
13569     SaveGamePGN2(f);
13570     fclose(f);
13571     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13572     return TRUE;
13573 }
13574
13575 /* Save game in old style and close the file */
13576 int
13577 SaveGameOldStyle (FILE *f)
13578 {
13579     int i, offset;
13580     time_t tm;
13581
13582     tm = time((time_t *) NULL);
13583
13584     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13585     PrintOpponents(f);
13586
13587     if (backwardMostMove > 0 || startedFromSetupPosition) {
13588         fprintf(f, "\n[--------------\n");
13589         PrintPosition(f, backwardMostMove);
13590         fprintf(f, "--------------]\n");
13591     } else {
13592         fprintf(f, "\n");
13593     }
13594
13595     i = backwardMostMove;
13596     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13597
13598     while (i < forwardMostMove) {
13599         if (commentList[i] != NULL) {
13600             fprintf(f, "[%s]\n", commentList[i]);
13601         }
13602
13603         if ((i % 2) == 1) {
13604             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13605             i++;
13606         } else {
13607             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13608             i++;
13609             if (commentList[i] != NULL) {
13610                 fprintf(f, "\n");
13611                 continue;
13612             }
13613             if (i >= forwardMostMove) {
13614                 fprintf(f, "\n");
13615                 break;
13616             }
13617             fprintf(f, "%s\n", parseList[i]);
13618             i++;
13619         }
13620     }
13621
13622     if (commentList[i] != NULL) {
13623         fprintf(f, "[%s]\n", commentList[i]);
13624     }
13625
13626     /* This isn't really the old style, but it's close enough */
13627     if (gameInfo.resultDetails != NULL &&
13628         gameInfo.resultDetails[0] != NULLCHAR) {
13629         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13630                 gameInfo.resultDetails);
13631     } else {
13632         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13633     }
13634
13635     fclose(f);
13636     return TRUE;
13637 }
13638
13639 /* Save the current game to open file f and close the file */
13640 int
13641 SaveGame (FILE *f, int dummy, char *dummy2)
13642 {
13643     if (gameMode == EditPosition) EditPositionDone(TRUE);
13644     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13645     if (appData.oldSaveStyle)
13646       return SaveGameOldStyle(f);
13647     else
13648       return SaveGamePGN(f);
13649 }
13650
13651 /* Save the current position to the given file */
13652 int
13653 SavePositionToFile (char *filename)
13654 {
13655     FILE *f;
13656     char buf[MSG_SIZ];
13657
13658     if (strcmp(filename, "-") == 0) {
13659         return SavePosition(stdout, 0, NULL);
13660     } else {
13661         f = fopen(filename, "a");
13662         if (f == NULL) {
13663             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13664             DisplayError(buf, errno);
13665             return FALSE;
13666         } else {
13667             safeStrCpy(buf, lastMsg, MSG_SIZ);
13668             DisplayMessage(_("Waiting for access to save file"), "");
13669             flock(fileno(f), LOCK_EX); // [HGM] lock
13670             DisplayMessage(_("Saving position"), "");
13671             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13672             SavePosition(f, 0, NULL);
13673             DisplayMessage(buf, "");
13674             return TRUE;
13675         }
13676     }
13677 }
13678
13679 /* Save the current position to the given open file and close the file */
13680 int
13681 SavePosition (FILE *f, int dummy, char *dummy2)
13682 {
13683     time_t tm;
13684     char *fen;
13685
13686     if (gameMode == EditPosition) EditPositionDone(TRUE);
13687     if (appData.oldSaveStyle) {
13688         tm = time((time_t *) NULL);
13689
13690         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13691         PrintOpponents(f);
13692         fprintf(f, "[--------------\n");
13693         PrintPosition(f, currentMove);
13694         fprintf(f, "--------------]\n");
13695     } else {
13696         fen = PositionToFEN(currentMove, NULL, 1);
13697         fprintf(f, "%s\n", fen);
13698         free(fen);
13699     }
13700     fclose(f);
13701     return TRUE;
13702 }
13703
13704 void
13705 ReloadCmailMsgEvent (int unregister)
13706 {
13707 #if !WIN32
13708     static char *inFilename = NULL;
13709     static char *outFilename;
13710     int i;
13711     struct stat inbuf, outbuf;
13712     int status;
13713
13714     /* Any registered moves are unregistered if unregister is set, */
13715     /* i.e. invoked by the signal handler */
13716     if (unregister) {
13717         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13718             cmailMoveRegistered[i] = FALSE;
13719             if (cmailCommentList[i] != NULL) {
13720                 free(cmailCommentList[i]);
13721                 cmailCommentList[i] = NULL;
13722             }
13723         }
13724         nCmailMovesRegistered = 0;
13725     }
13726
13727     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13728         cmailResult[i] = CMAIL_NOT_RESULT;
13729     }
13730     nCmailResults = 0;
13731
13732     if (inFilename == NULL) {
13733         /* Because the filenames are static they only get malloced once  */
13734         /* and they never get freed                                      */
13735         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13736         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13737
13738         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13739         sprintf(outFilename, "%s.out", appData.cmailGameName);
13740     }
13741
13742     status = stat(outFilename, &outbuf);
13743     if (status < 0) {
13744         cmailMailedMove = FALSE;
13745     } else {
13746         status = stat(inFilename, &inbuf);
13747         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13748     }
13749
13750     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13751        counts the games, notes how each one terminated, etc.
13752
13753        It would be nice to remove this kludge and instead gather all
13754        the information while building the game list.  (And to keep it
13755        in the game list nodes instead of having a bunch of fixed-size
13756        parallel arrays.)  Note this will require getting each game's
13757        termination from the PGN tags, as the game list builder does
13758        not process the game moves.  --mann
13759        */
13760     cmailMsgLoaded = TRUE;
13761     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13762
13763     /* Load first game in the file or popup game menu */
13764     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13765
13766 #endif /* !WIN32 */
13767     return;
13768 }
13769
13770 int
13771 RegisterMove ()
13772 {
13773     FILE *f;
13774     char string[MSG_SIZ];
13775
13776     if (   cmailMailedMove
13777         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13778         return TRUE;            /* Allow free viewing  */
13779     }
13780
13781     /* Unregister move to ensure that we don't leave RegisterMove        */
13782     /* with the move registered when the conditions for registering no   */
13783     /* longer hold                                                       */
13784     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13785         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13786         nCmailMovesRegistered --;
13787
13788         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13789           {
13790               free(cmailCommentList[lastLoadGameNumber - 1]);
13791               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13792           }
13793     }
13794
13795     if (cmailOldMove == -1) {
13796         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13797         return FALSE;
13798     }
13799
13800     if (currentMove > cmailOldMove + 1) {
13801         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13802         return FALSE;
13803     }
13804
13805     if (currentMove < cmailOldMove) {
13806         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13807         return FALSE;
13808     }
13809
13810     if (forwardMostMove > currentMove) {
13811         /* Silently truncate extra moves */
13812         TruncateGame();
13813     }
13814
13815     if (   (currentMove == cmailOldMove + 1)
13816         || (   (currentMove == cmailOldMove)
13817             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13818                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13819         if (gameInfo.result != GameUnfinished) {
13820             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13821         }
13822
13823         if (commentList[currentMove] != NULL) {
13824             cmailCommentList[lastLoadGameNumber - 1]
13825               = StrSave(commentList[currentMove]);
13826         }
13827         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13828
13829         if (appData.debugMode)
13830           fprintf(debugFP, "Saving %s for game %d\n",
13831                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13832
13833         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13834
13835         f = fopen(string, "w");
13836         if (appData.oldSaveStyle) {
13837             SaveGameOldStyle(f); /* also closes the file */
13838
13839             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13840             f = fopen(string, "w");
13841             SavePosition(f, 0, NULL); /* also closes the file */
13842         } else {
13843             fprintf(f, "{--------------\n");
13844             PrintPosition(f, currentMove);
13845             fprintf(f, "--------------}\n\n");
13846
13847             SaveGame(f, 0, NULL); /* also closes the file*/
13848         }
13849
13850         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13851         nCmailMovesRegistered ++;
13852     } else if (nCmailGames == 1) {
13853         DisplayError(_("You have not made a move yet"), 0);
13854         return FALSE;
13855     }
13856
13857     return TRUE;
13858 }
13859
13860 void
13861 MailMoveEvent ()
13862 {
13863 #if !WIN32
13864     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13865     FILE *commandOutput;
13866     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13867     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13868     int nBuffers;
13869     int i;
13870     int archived;
13871     char *arcDir;
13872
13873     if (! cmailMsgLoaded) {
13874         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13875         return;
13876     }
13877
13878     if (nCmailGames == nCmailResults) {
13879         DisplayError(_("No unfinished games"), 0);
13880         return;
13881     }
13882
13883 #if CMAIL_PROHIBIT_REMAIL
13884     if (cmailMailedMove) {
13885       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);
13886         DisplayError(msg, 0);
13887         return;
13888     }
13889 #endif
13890
13891     if (! (cmailMailedMove || RegisterMove())) return;
13892
13893     if (   cmailMailedMove
13894         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13895       snprintf(string, MSG_SIZ, partCommandString,
13896                appData.debugMode ? " -v" : "", appData.cmailGameName);
13897         commandOutput = popen(string, "r");
13898
13899         if (commandOutput == NULL) {
13900             DisplayError(_("Failed to invoke cmail"), 0);
13901         } else {
13902             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13903                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13904             }
13905             if (nBuffers > 1) {
13906                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13907                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13908                 nBytes = MSG_SIZ - 1;
13909             } else {
13910                 (void) memcpy(msg, buffer, nBytes);
13911             }
13912             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13913
13914             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13915                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13916
13917                 archived = TRUE;
13918                 for (i = 0; i < nCmailGames; i ++) {
13919                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13920                         archived = FALSE;
13921                     }
13922                 }
13923                 if (   archived
13924                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13925                         != NULL)) {
13926                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13927                            arcDir,
13928                            appData.cmailGameName,
13929                            gameInfo.date);
13930                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13931                     cmailMsgLoaded = FALSE;
13932                 }
13933             }
13934
13935             DisplayInformation(msg);
13936             pclose(commandOutput);
13937         }
13938     } else {
13939         if ((*cmailMsg) != '\0') {
13940             DisplayInformation(cmailMsg);
13941         }
13942     }
13943
13944     return;
13945 #endif /* !WIN32 */
13946 }
13947
13948 char *
13949 CmailMsg ()
13950 {
13951 #if WIN32
13952     return NULL;
13953 #else
13954     int  prependComma = 0;
13955     char number[5];
13956     char string[MSG_SIZ];       /* Space for game-list */
13957     int  i;
13958
13959     if (!cmailMsgLoaded) return "";
13960
13961     if (cmailMailedMove) {
13962       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13963     } else {
13964         /* Create a list of games left */
13965       snprintf(string, MSG_SIZ, "[");
13966         for (i = 0; i < nCmailGames; i ++) {
13967             if (! (   cmailMoveRegistered[i]
13968                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13969                 if (prependComma) {
13970                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13971                 } else {
13972                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13973                     prependComma = 1;
13974                 }
13975
13976                 strcat(string, number);
13977             }
13978         }
13979         strcat(string, "]");
13980
13981         if (nCmailMovesRegistered + nCmailResults == 0) {
13982             switch (nCmailGames) {
13983               case 1:
13984                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13985                 break;
13986
13987               case 2:
13988                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13989                 break;
13990
13991               default:
13992                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13993                          nCmailGames);
13994                 break;
13995             }
13996         } else {
13997             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13998               case 1:
13999                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14000                          string);
14001                 break;
14002
14003               case 0:
14004                 if (nCmailResults == nCmailGames) {
14005                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14006                 } else {
14007                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14008                 }
14009                 break;
14010
14011               default:
14012                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14013                          string);
14014             }
14015         }
14016     }
14017     return cmailMsg;
14018 #endif /* WIN32 */
14019 }
14020
14021 void
14022 ResetGameEvent ()
14023 {
14024     if (gameMode == Training)
14025       SetTrainingModeOff();
14026
14027     Reset(TRUE, TRUE);
14028     cmailMsgLoaded = FALSE;
14029     if (appData.icsActive) {
14030       SendToICS(ics_prefix);
14031       SendToICS("refresh\n");
14032     }
14033 }
14034
14035 void
14036 ExitEvent (int status)
14037 {
14038     exiting++;
14039     if (exiting > 2) {
14040       /* Give up on clean exit */
14041       exit(status);
14042     }
14043     if (exiting > 1) {
14044       /* Keep trying for clean exit */
14045       return;
14046     }
14047
14048     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14049     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14050
14051     if (telnetISR != NULL) {
14052       RemoveInputSource(telnetISR);
14053     }
14054     if (icsPR != NoProc) {
14055       DestroyChildProcess(icsPR, TRUE);
14056     }
14057
14058     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14059     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14060
14061     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14062     /* make sure this other one finishes before killing it!                  */
14063     if(endingGame) { int count = 0;
14064         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14065         while(endingGame && count++ < 10) DoSleep(1);
14066         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14067     }
14068
14069     /* Kill off chess programs */
14070     if (first.pr != NoProc) {
14071         ExitAnalyzeMode();
14072
14073         DoSleep( appData.delayBeforeQuit );
14074         SendToProgram("quit\n", &first);
14075         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14076     }
14077     if (second.pr != NoProc) {
14078         DoSleep( appData.delayBeforeQuit );
14079         SendToProgram("quit\n", &second);
14080         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14081     }
14082     if (first.isr != NULL) {
14083         RemoveInputSource(first.isr);
14084     }
14085     if (second.isr != NULL) {
14086         RemoveInputSource(second.isr);
14087     }
14088
14089     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14090     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14091
14092     ShutDownFrontEnd();
14093     exit(status);
14094 }
14095
14096 void
14097 PauseEngine (ChessProgramState *cps)
14098 {
14099     SendToProgram("pause\n", cps);
14100     cps->pause = 2;
14101 }
14102
14103 void
14104 UnPauseEngine (ChessProgramState *cps)
14105 {
14106     SendToProgram("resume\n", cps);
14107     cps->pause = 1;
14108 }
14109
14110 void
14111 PauseEvent ()
14112 {
14113     if (appData.debugMode)
14114         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14115     if (pausing) {
14116         pausing = FALSE;
14117         ModeHighlight();
14118         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14119             StartClocks();
14120             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14121                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14122                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14123             }
14124             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14125             HandleMachineMove(stashedInputMove, stalledEngine);
14126             stalledEngine = NULL;
14127             return;
14128         }
14129         if (gameMode == MachinePlaysWhite ||
14130             gameMode == TwoMachinesPlay   ||
14131             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14132             if(first.pause)  UnPauseEngine(&first);
14133             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14134             if(second.pause) UnPauseEngine(&second);
14135             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14136             StartClocks();
14137         } else {
14138             DisplayBothClocks();
14139         }
14140         if (gameMode == PlayFromGameFile) {
14141             if (appData.timeDelay >= 0)
14142                 AutoPlayGameLoop();
14143         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14144             Reset(FALSE, TRUE);
14145             SendToICS(ics_prefix);
14146             SendToICS("refresh\n");
14147         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14148             ForwardInner(forwardMostMove);
14149         }
14150         pauseExamInvalid = FALSE;
14151     } else {
14152         switch (gameMode) {
14153           default:
14154             return;
14155           case IcsExamining:
14156             pauseExamForwardMostMove = forwardMostMove;
14157             pauseExamInvalid = FALSE;
14158             /* fall through */
14159           case IcsObserving:
14160           case IcsPlayingWhite:
14161           case IcsPlayingBlack:
14162             pausing = TRUE;
14163             ModeHighlight();
14164             return;
14165           case PlayFromGameFile:
14166             (void) StopLoadGameTimer();
14167             pausing = TRUE;
14168             ModeHighlight();
14169             break;
14170           case BeginningOfGame:
14171             if (appData.icsActive) return;
14172             /* else fall through */
14173           case MachinePlaysWhite:
14174           case MachinePlaysBlack:
14175           case TwoMachinesPlay:
14176             if (forwardMostMove == 0)
14177               return;           /* don't pause if no one has moved */
14178             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14179                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14180                 if(onMove->pause) {           // thinking engine can be paused
14181                     PauseEngine(onMove);      // do it
14182                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14183                         PauseEngine(onMove->other);
14184                     else
14185                         SendToProgram("easy\n", onMove->other);
14186                     StopClocks();
14187                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14188             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14189                 if(first.pause) {
14190                     PauseEngine(&first);
14191                     StopClocks();
14192                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14193             } else { // human on move, pause pondering by either method
14194                 if(first.pause)
14195                     PauseEngine(&first);
14196                 else if(appData.ponderNextMove)
14197                     SendToProgram("easy\n", &first);
14198                 StopClocks();
14199             }
14200             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14201           case AnalyzeMode:
14202             pausing = TRUE;
14203             ModeHighlight();
14204             break;
14205         }
14206     }
14207 }
14208
14209 void
14210 EditCommentEvent ()
14211 {
14212     char title[MSG_SIZ];
14213
14214     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14215       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14216     } else {
14217       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14218                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14219                parseList[currentMove - 1]);
14220     }
14221
14222     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14223 }
14224
14225
14226 void
14227 EditTagsEvent ()
14228 {
14229     char *tags = PGNTags(&gameInfo);
14230     bookUp = FALSE;
14231     EditTagsPopUp(tags, NULL);
14232     free(tags);
14233 }
14234
14235 void
14236 ToggleSecond ()
14237 {
14238   if(second.analyzing) {
14239     SendToProgram("exit\n", &second);
14240     second.analyzing = FALSE;
14241   } else {
14242     if (second.pr == NoProc) StartChessProgram(&second);
14243     InitChessProgram(&second, FALSE);
14244     FeedMovesToProgram(&second, currentMove);
14245
14246     SendToProgram("analyze\n", &second);
14247     second.analyzing = TRUE;
14248   }
14249 }
14250
14251 /* Toggle ShowThinking */
14252 void
14253 ToggleShowThinking()
14254 {
14255   appData.showThinking = !appData.showThinking;
14256   ShowThinkingEvent();
14257 }
14258
14259 int
14260 AnalyzeModeEvent ()
14261 {
14262     char buf[MSG_SIZ];
14263
14264     if (!first.analysisSupport) {
14265       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14266       DisplayError(buf, 0);
14267       return 0;
14268     }
14269     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14270     if (appData.icsActive) {
14271         if (gameMode != IcsObserving) {
14272           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14273             DisplayError(buf, 0);
14274             /* secure check */
14275             if (appData.icsEngineAnalyze) {
14276                 if (appData.debugMode)
14277                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14278                 ExitAnalyzeMode();
14279                 ModeHighlight();
14280             }
14281             return 0;
14282         }
14283         /* if enable, user wants to disable icsEngineAnalyze */
14284         if (appData.icsEngineAnalyze) {
14285                 ExitAnalyzeMode();
14286                 ModeHighlight();
14287                 return 0;
14288         }
14289         appData.icsEngineAnalyze = TRUE;
14290         if (appData.debugMode)
14291             fprintf(debugFP, "ICS engine analyze starting... \n");
14292     }
14293
14294     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14295     if (appData.noChessProgram || gameMode == AnalyzeMode)
14296       return 0;
14297
14298     if (gameMode != AnalyzeFile) {
14299         if (!appData.icsEngineAnalyze) {
14300                EditGameEvent();
14301                if (gameMode != EditGame) return 0;
14302         }
14303         if (!appData.showThinking) ToggleShowThinking();
14304         ResurrectChessProgram();
14305         SendToProgram("analyze\n", &first);
14306         first.analyzing = TRUE;
14307         /*first.maybeThinking = TRUE;*/
14308         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14309         EngineOutputPopUp();
14310     }
14311     if (!appData.icsEngineAnalyze) {
14312         gameMode = AnalyzeMode;
14313         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14314     }
14315     pausing = FALSE;
14316     ModeHighlight();
14317     SetGameInfo();
14318
14319     StartAnalysisClock();
14320     GetTimeMark(&lastNodeCountTime);
14321     lastNodeCount = 0;
14322     return 1;
14323 }
14324
14325 void
14326 AnalyzeFileEvent ()
14327 {
14328     if (appData.noChessProgram || gameMode == AnalyzeFile)
14329       return;
14330
14331     if (!first.analysisSupport) {
14332       char buf[MSG_SIZ];
14333       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14334       DisplayError(buf, 0);
14335       return;
14336     }
14337
14338     if (gameMode != AnalyzeMode) {
14339         keepInfo = 1; // mere annotating should not alter PGN tags
14340         EditGameEvent();
14341         keepInfo = 0;
14342         if (gameMode != EditGame) return;
14343         if (!appData.showThinking) ToggleShowThinking();
14344         ResurrectChessProgram();
14345         SendToProgram("analyze\n", &first);
14346         first.analyzing = TRUE;
14347         /*first.maybeThinking = TRUE;*/
14348         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14349         EngineOutputPopUp();
14350     }
14351     gameMode = AnalyzeFile;
14352     pausing = FALSE;
14353     ModeHighlight();
14354
14355     StartAnalysisClock();
14356     GetTimeMark(&lastNodeCountTime);
14357     lastNodeCount = 0;
14358     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14359     AnalysisPeriodicEvent(1);
14360 }
14361
14362 void
14363 MachineWhiteEvent ()
14364 {
14365     char buf[MSG_SIZ];
14366     char *bookHit = NULL;
14367
14368     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14369       return;
14370
14371
14372     if (gameMode == PlayFromGameFile ||
14373         gameMode == TwoMachinesPlay  ||
14374         gameMode == Training         ||
14375         gameMode == AnalyzeMode      ||
14376         gameMode == EndOfGame)
14377         EditGameEvent();
14378
14379     if (gameMode == EditPosition)
14380         EditPositionDone(TRUE);
14381
14382     if (!WhiteOnMove(currentMove)) {
14383         DisplayError(_("It is not White's turn"), 0);
14384         return;
14385     }
14386
14387     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14388       ExitAnalyzeMode();
14389
14390     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14391         gameMode == AnalyzeFile)
14392         TruncateGame();
14393
14394     ResurrectChessProgram();    /* in case it isn't running */
14395     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14396         gameMode = MachinePlaysWhite;
14397         ResetClocks();
14398     } else
14399     gameMode = MachinePlaysWhite;
14400     pausing = FALSE;
14401     ModeHighlight();
14402     SetGameInfo();
14403     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14404     DisplayTitle(buf);
14405     if (first.sendName) {
14406       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14407       SendToProgram(buf, &first);
14408     }
14409     if (first.sendTime) {
14410       if (first.useColors) {
14411         SendToProgram("black\n", &first); /*gnu kludge*/
14412       }
14413       SendTimeRemaining(&first, TRUE);
14414     }
14415     if (first.useColors) {
14416       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14417     }
14418     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14419     SetMachineThinkingEnables();
14420     first.maybeThinking = TRUE;
14421     StartClocks();
14422     firstMove = FALSE;
14423
14424     if (appData.autoFlipView && !flipView) {
14425       flipView = !flipView;
14426       DrawPosition(FALSE, NULL);
14427       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14428     }
14429
14430     if(bookHit) { // [HGM] book: simulate book reply
14431         static char bookMove[MSG_SIZ]; // a bit generous?
14432
14433         programStats.nodes = programStats.depth = programStats.time =
14434         programStats.score = programStats.got_only_move = 0;
14435         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14436
14437         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14438         strcat(bookMove, bookHit);
14439         HandleMachineMove(bookMove, &first);
14440     }
14441 }
14442
14443 void
14444 MachineBlackEvent ()
14445 {
14446   char buf[MSG_SIZ];
14447   char *bookHit = NULL;
14448
14449     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14450         return;
14451
14452
14453     if (gameMode == PlayFromGameFile ||
14454         gameMode == TwoMachinesPlay  ||
14455         gameMode == Training         ||
14456         gameMode == AnalyzeMode      ||
14457         gameMode == EndOfGame)
14458         EditGameEvent();
14459
14460     if (gameMode == EditPosition)
14461         EditPositionDone(TRUE);
14462
14463     if (WhiteOnMove(currentMove)) {
14464         DisplayError(_("It is not Black's turn"), 0);
14465         return;
14466     }
14467
14468     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14469       ExitAnalyzeMode();
14470
14471     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14472         gameMode == AnalyzeFile)
14473         TruncateGame();
14474
14475     ResurrectChessProgram();    /* in case it isn't running */
14476     gameMode = MachinePlaysBlack;
14477     pausing = FALSE;
14478     ModeHighlight();
14479     SetGameInfo();
14480     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14481     DisplayTitle(buf);
14482     if (first.sendName) {
14483       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14484       SendToProgram(buf, &first);
14485     }
14486     if (first.sendTime) {
14487       if (first.useColors) {
14488         SendToProgram("white\n", &first); /*gnu kludge*/
14489       }
14490       SendTimeRemaining(&first, FALSE);
14491     }
14492     if (first.useColors) {
14493       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14494     }
14495     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14496     SetMachineThinkingEnables();
14497     first.maybeThinking = TRUE;
14498     StartClocks();
14499
14500     if (appData.autoFlipView && flipView) {
14501       flipView = !flipView;
14502       DrawPosition(FALSE, NULL);
14503       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14504     }
14505     if(bookHit) { // [HGM] book: simulate book reply
14506         static char bookMove[MSG_SIZ]; // a bit generous?
14507
14508         programStats.nodes = programStats.depth = programStats.time =
14509         programStats.score = programStats.got_only_move = 0;
14510         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14511
14512         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14513         strcat(bookMove, bookHit);
14514         HandleMachineMove(bookMove, &first);
14515     }
14516 }
14517
14518
14519 void
14520 DisplayTwoMachinesTitle ()
14521 {
14522     char buf[MSG_SIZ];
14523     if (appData.matchGames > 0) {
14524         if(appData.tourneyFile[0]) {
14525           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14526                    gameInfo.white, _("vs."), gameInfo.black,
14527                    nextGame+1, appData.matchGames+1,
14528                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14529         } else
14530         if (first.twoMachinesColor[0] == 'w') {
14531           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14532                    gameInfo.white, _("vs."),  gameInfo.black,
14533                    first.matchWins, second.matchWins,
14534                    matchGame - 1 - (first.matchWins + second.matchWins));
14535         } else {
14536           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14537                    gameInfo.white, _("vs."), gameInfo.black,
14538                    second.matchWins, first.matchWins,
14539                    matchGame - 1 - (first.matchWins + second.matchWins));
14540         }
14541     } else {
14542       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14543     }
14544     DisplayTitle(buf);
14545 }
14546
14547 void
14548 SettingsMenuIfReady ()
14549 {
14550   if (second.lastPing != second.lastPong) {
14551     DisplayMessage("", _("Waiting for second chess program"));
14552     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14553     return;
14554   }
14555   ThawUI();
14556   DisplayMessage("", "");
14557   SettingsPopUp(&second);
14558 }
14559
14560 int
14561 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14562 {
14563     char buf[MSG_SIZ];
14564     if (cps->pr == NoProc) {
14565         StartChessProgram(cps);
14566         if (cps->protocolVersion == 1) {
14567           retry();
14568           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14569         } else {
14570           /* kludge: allow timeout for initial "feature" command */
14571           if(retry != TwoMachinesEventIfReady) FreezeUI();
14572           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14573           DisplayMessage("", buf);
14574           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14575         }
14576         return 1;
14577     }
14578     return 0;
14579 }
14580
14581 void
14582 TwoMachinesEvent P((void))
14583 {
14584     int i;
14585     char buf[MSG_SIZ];
14586     ChessProgramState *onmove;
14587     char *bookHit = NULL;
14588     static int stalling = 0;
14589     TimeMark now;
14590     long wait;
14591
14592     if (appData.noChessProgram) return;
14593
14594     switch (gameMode) {
14595       case TwoMachinesPlay:
14596         return;
14597       case MachinePlaysWhite:
14598       case MachinePlaysBlack:
14599         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14600             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14601             return;
14602         }
14603         /* fall through */
14604       case BeginningOfGame:
14605       case PlayFromGameFile:
14606       case EndOfGame:
14607         EditGameEvent();
14608         if (gameMode != EditGame) return;
14609         break;
14610       case EditPosition:
14611         EditPositionDone(TRUE);
14612         break;
14613       case AnalyzeMode:
14614       case AnalyzeFile:
14615         ExitAnalyzeMode();
14616         break;
14617       case EditGame:
14618       default:
14619         break;
14620     }
14621
14622 //    forwardMostMove = currentMove;
14623     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14624     startingEngine = TRUE;
14625
14626     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14627
14628     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14629     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14630       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14631       return;
14632     }
14633     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14634
14635     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14636                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14637         startingEngine = FALSE;
14638         DisplayError("second engine does not play this", 0);
14639         return;
14640     }
14641
14642     if(!stalling) {
14643       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14644       SendToProgram("force\n", &second);
14645       stalling = 1;
14646       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14647       return;
14648     }
14649     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14650     if(appData.matchPause>10000 || appData.matchPause<10)
14651                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14652     wait = SubtractTimeMarks(&now, &pauseStart);
14653     if(wait < appData.matchPause) {
14654         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14655         return;
14656     }
14657     // we are now committed to starting the game
14658     stalling = 0;
14659     DisplayMessage("", "");
14660     if (startedFromSetupPosition) {
14661         SendBoard(&second, backwardMostMove);
14662     if (appData.debugMode) {
14663         fprintf(debugFP, "Two Machines\n");
14664     }
14665     }
14666     for (i = backwardMostMove; i < forwardMostMove; i++) {
14667         SendMoveToProgram(i, &second);
14668     }
14669
14670     gameMode = TwoMachinesPlay;
14671     pausing = startingEngine = FALSE;
14672     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14673     SetGameInfo();
14674     DisplayTwoMachinesTitle();
14675     firstMove = TRUE;
14676     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14677         onmove = &first;
14678     } else {
14679         onmove = &second;
14680     }
14681     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14682     SendToProgram(first.computerString, &first);
14683     if (first.sendName) {
14684       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14685       SendToProgram(buf, &first);
14686     }
14687     SendToProgram(second.computerString, &second);
14688     if (second.sendName) {
14689       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14690       SendToProgram(buf, &second);
14691     }
14692
14693     ResetClocks();
14694     if (!first.sendTime || !second.sendTime) {
14695         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14696         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14697     }
14698     if (onmove->sendTime) {
14699       if (onmove->useColors) {
14700         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14701       }
14702       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14703     }
14704     if (onmove->useColors) {
14705       SendToProgram(onmove->twoMachinesColor, onmove);
14706     }
14707     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14708 //    SendToProgram("go\n", onmove);
14709     onmove->maybeThinking = TRUE;
14710     SetMachineThinkingEnables();
14711
14712     StartClocks();
14713
14714     if(bookHit) { // [HGM] book: simulate book reply
14715         static char bookMove[MSG_SIZ]; // a bit generous?
14716
14717         programStats.nodes = programStats.depth = programStats.time =
14718         programStats.score = programStats.got_only_move = 0;
14719         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14720
14721         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14722         strcat(bookMove, bookHit);
14723         savedMessage = bookMove; // args for deferred call
14724         savedState = onmove;
14725         ScheduleDelayedEvent(DeferredBookMove, 1);
14726     }
14727 }
14728
14729 void
14730 TrainingEvent ()
14731 {
14732     if (gameMode == Training) {
14733       SetTrainingModeOff();
14734       gameMode = PlayFromGameFile;
14735       DisplayMessage("", _("Training mode off"));
14736     } else {
14737       gameMode = Training;
14738       animateTraining = appData.animate;
14739
14740       /* make sure we are not already at the end of the game */
14741       if (currentMove < forwardMostMove) {
14742         SetTrainingModeOn();
14743         DisplayMessage("", _("Training mode on"));
14744       } else {
14745         gameMode = PlayFromGameFile;
14746         DisplayError(_("Already at end of game"), 0);
14747       }
14748     }
14749     ModeHighlight();
14750 }
14751
14752 void
14753 IcsClientEvent ()
14754 {
14755     if (!appData.icsActive) return;
14756     switch (gameMode) {
14757       case IcsPlayingWhite:
14758       case IcsPlayingBlack:
14759       case IcsObserving:
14760       case IcsIdle:
14761       case BeginningOfGame:
14762       case IcsExamining:
14763         return;
14764
14765       case EditGame:
14766         break;
14767
14768       case EditPosition:
14769         EditPositionDone(TRUE);
14770         break;
14771
14772       case AnalyzeMode:
14773       case AnalyzeFile:
14774         ExitAnalyzeMode();
14775         break;
14776
14777       default:
14778         EditGameEvent();
14779         break;
14780     }
14781
14782     gameMode = IcsIdle;
14783     ModeHighlight();
14784     return;
14785 }
14786
14787 void
14788 EditGameEvent ()
14789 {
14790     int i;
14791
14792     switch (gameMode) {
14793       case Training:
14794         SetTrainingModeOff();
14795         break;
14796       case MachinePlaysWhite:
14797       case MachinePlaysBlack:
14798       case BeginningOfGame:
14799         SendToProgram("force\n", &first);
14800         SetUserThinkingEnables();
14801         break;
14802       case PlayFromGameFile:
14803         (void) StopLoadGameTimer();
14804         if (gameFileFP != NULL) {
14805             gameFileFP = NULL;
14806         }
14807         break;
14808       case EditPosition:
14809         EditPositionDone(TRUE);
14810         break;
14811       case AnalyzeMode:
14812       case AnalyzeFile:
14813         ExitAnalyzeMode();
14814         SendToProgram("force\n", &first);
14815         break;
14816       case TwoMachinesPlay:
14817         GameEnds(EndOfFile, NULL, GE_PLAYER);
14818         ResurrectChessProgram();
14819         SetUserThinkingEnables();
14820         break;
14821       case EndOfGame:
14822         ResurrectChessProgram();
14823         break;
14824       case IcsPlayingBlack:
14825       case IcsPlayingWhite:
14826         DisplayError(_("Warning: You are still playing a game"), 0);
14827         break;
14828       case IcsObserving:
14829         DisplayError(_("Warning: You are still observing a game"), 0);
14830         break;
14831       case IcsExamining:
14832         DisplayError(_("Warning: You are still examining a game"), 0);
14833         break;
14834       case IcsIdle:
14835         break;
14836       case EditGame:
14837       default:
14838         return;
14839     }
14840
14841     pausing = FALSE;
14842     StopClocks();
14843     first.offeredDraw = second.offeredDraw = 0;
14844
14845     if (gameMode == PlayFromGameFile) {
14846         whiteTimeRemaining = timeRemaining[0][currentMove];
14847         blackTimeRemaining = timeRemaining[1][currentMove];
14848         DisplayTitle("");
14849     }
14850
14851     if (gameMode == MachinePlaysWhite ||
14852         gameMode == MachinePlaysBlack ||
14853         gameMode == TwoMachinesPlay ||
14854         gameMode == EndOfGame) {
14855         i = forwardMostMove;
14856         while (i > currentMove) {
14857             SendToProgram("undo\n", &first);
14858             i--;
14859         }
14860         if(!adjustedClock) {
14861         whiteTimeRemaining = timeRemaining[0][currentMove];
14862         blackTimeRemaining = timeRemaining[1][currentMove];
14863         DisplayBothClocks();
14864         }
14865         if (whiteFlag || blackFlag) {
14866             whiteFlag = blackFlag = 0;
14867         }
14868         DisplayTitle("");
14869     }
14870
14871     gameMode = EditGame;
14872     ModeHighlight();
14873     SetGameInfo();
14874 }
14875
14876
14877 void
14878 EditPositionEvent ()
14879 {
14880     if (gameMode == EditPosition) {
14881         EditGameEvent();
14882         return;
14883     }
14884
14885     EditGameEvent();
14886     if (gameMode != EditGame) return;
14887
14888     gameMode = EditPosition;
14889     ModeHighlight();
14890     SetGameInfo();
14891     if (currentMove > 0)
14892       CopyBoard(boards[0], boards[currentMove]);
14893
14894     blackPlaysFirst = !WhiteOnMove(currentMove);
14895     ResetClocks();
14896     currentMove = forwardMostMove = backwardMostMove = 0;
14897     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14898     DisplayMove(-1);
14899     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14900 }
14901
14902 void
14903 ExitAnalyzeMode ()
14904 {
14905     /* [DM] icsEngineAnalyze - possible call from other functions */
14906     if (appData.icsEngineAnalyze) {
14907         appData.icsEngineAnalyze = FALSE;
14908
14909         DisplayMessage("",_("Close ICS engine analyze..."));
14910     }
14911     if (first.analysisSupport && first.analyzing) {
14912       SendToBoth("exit\n");
14913       first.analyzing = second.analyzing = FALSE;
14914     }
14915     thinkOutput[0] = NULLCHAR;
14916 }
14917
14918 void
14919 EditPositionDone (Boolean fakeRights)
14920 {
14921     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14922
14923     startedFromSetupPosition = TRUE;
14924     InitChessProgram(&first, FALSE);
14925     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14926       boards[0][EP_STATUS] = EP_NONE;
14927       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14928       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14929         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14930         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14931       } else boards[0][CASTLING][2] = NoRights;
14932       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14933         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14934         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14935       } else boards[0][CASTLING][5] = NoRights;
14936       if(gameInfo.variant == VariantSChess) {
14937         int i;
14938         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14939           boards[0][VIRGIN][i] = 0;
14940           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14941           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14942         }
14943       }
14944     }
14945     SendToProgram("force\n", &first);
14946     if (blackPlaysFirst) {
14947         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14948         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14949         currentMove = forwardMostMove = backwardMostMove = 1;
14950         CopyBoard(boards[1], boards[0]);
14951     } else {
14952         currentMove = forwardMostMove = backwardMostMove = 0;
14953     }
14954     SendBoard(&first, forwardMostMove);
14955     if (appData.debugMode) {
14956         fprintf(debugFP, "EditPosDone\n");
14957     }
14958     DisplayTitle("");
14959     DisplayMessage("", "");
14960     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14961     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14962     gameMode = EditGame;
14963     ModeHighlight();
14964     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14965     ClearHighlights(); /* [AS] */
14966 }
14967
14968 /* Pause for `ms' milliseconds */
14969 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14970 void
14971 TimeDelay (long ms)
14972 {
14973     TimeMark m1, m2;
14974
14975     GetTimeMark(&m1);
14976     do {
14977         GetTimeMark(&m2);
14978     } while (SubtractTimeMarks(&m2, &m1) < ms);
14979 }
14980
14981 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14982 void
14983 SendMultiLineToICS (char *buf)
14984 {
14985     char temp[MSG_SIZ+1], *p;
14986     int len;
14987
14988     len = strlen(buf);
14989     if (len > MSG_SIZ)
14990       len = MSG_SIZ;
14991
14992     strncpy(temp, buf, len);
14993     temp[len] = 0;
14994
14995     p = temp;
14996     while (*p) {
14997         if (*p == '\n' || *p == '\r')
14998           *p = ' ';
14999         ++p;
15000     }
15001
15002     strcat(temp, "\n");
15003     SendToICS(temp);
15004     SendToPlayer(temp, strlen(temp));
15005 }
15006
15007 void
15008 SetWhiteToPlayEvent ()
15009 {
15010     if (gameMode == EditPosition) {
15011         blackPlaysFirst = FALSE;
15012         DisplayBothClocks();    /* works because currentMove is 0 */
15013     } else if (gameMode == IcsExamining) {
15014         SendToICS(ics_prefix);
15015         SendToICS("tomove white\n");
15016     }
15017 }
15018
15019 void
15020 SetBlackToPlayEvent ()
15021 {
15022     if (gameMode == EditPosition) {
15023         blackPlaysFirst = TRUE;
15024         currentMove = 1;        /* kludge */
15025         DisplayBothClocks();
15026         currentMove = 0;
15027     } else if (gameMode == IcsExamining) {
15028         SendToICS(ics_prefix);
15029         SendToICS("tomove black\n");
15030     }
15031 }
15032
15033 void
15034 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15035 {
15036     char buf[MSG_SIZ];
15037     ChessSquare piece = boards[0][y][x];
15038     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15039     static int lastVariant;
15040
15041     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15042
15043     switch (selection) {
15044       case ClearBoard:
15045         CopyBoard(currentBoard, boards[0]);
15046         CopyBoard(menuBoard, initialPosition);
15047         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15048             SendToICS(ics_prefix);
15049             SendToICS("bsetup clear\n");
15050         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15051             SendToICS(ics_prefix);
15052             SendToICS("clearboard\n");
15053         } else {
15054             int nonEmpty = 0;
15055             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15056                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15057                 for (y = 0; y < BOARD_HEIGHT; y++) {
15058                     if (gameMode == IcsExamining) {
15059                         if (boards[currentMove][y][x] != EmptySquare) {
15060                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15061                                     AAA + x, ONE + y);
15062                             SendToICS(buf);
15063                         }
15064                     } else {
15065                         if(boards[0][y][x] != p) nonEmpty++;
15066                         boards[0][y][x] = p;
15067                     }
15068                 }
15069             }
15070             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15071                 int r;
15072                 for(r = 0; r < BOARD_HEIGHT; r++) {
15073                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15074                     ChessSquare p = menuBoard[r][x];
15075                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15076                   }
15077                 }
15078                 DisplayMessage("Clicking clock again restores position", "");
15079                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15080                 if(!nonEmpty) { // asked to clear an empty board
15081                     CopyBoard(boards[0], menuBoard);
15082                 } else
15083                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15084                     CopyBoard(boards[0], initialPosition);
15085                 } else
15086                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15087                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15088                     CopyBoard(boards[0], erasedBoard);
15089                 } else
15090                     CopyBoard(erasedBoard, currentBoard);
15091
15092             }
15093         }
15094         if (gameMode == EditPosition) {
15095             DrawPosition(FALSE, boards[0]);
15096         }
15097         break;
15098
15099       case WhitePlay:
15100         SetWhiteToPlayEvent();
15101         break;
15102
15103       case BlackPlay:
15104         SetBlackToPlayEvent();
15105         break;
15106
15107       case EmptySquare:
15108         if (gameMode == IcsExamining) {
15109             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15110             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15111             SendToICS(buf);
15112         } else {
15113             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15114                 if(x == BOARD_LEFT-2) {
15115                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15116                     boards[0][y][1] = 0;
15117                 } else
15118                 if(x == BOARD_RGHT+1) {
15119                     if(y >= gameInfo.holdingsSize) break;
15120                     boards[0][y][BOARD_WIDTH-2] = 0;
15121                 } else break;
15122             }
15123             boards[0][y][x] = EmptySquare;
15124             DrawPosition(FALSE, boards[0]);
15125         }
15126         break;
15127
15128       case PromotePiece:
15129         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15130            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15131             selection = (ChessSquare) (PROMOTED piece);
15132         } else if(piece == EmptySquare) selection = WhiteSilver;
15133         else selection = (ChessSquare)((int)piece - 1);
15134         goto defaultlabel;
15135
15136       case DemotePiece:
15137         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15138            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15139             selection = (ChessSquare) (DEMOTED piece);
15140         } else if(piece == EmptySquare) selection = BlackSilver;
15141         else selection = (ChessSquare)((int)piece + 1);
15142         goto defaultlabel;
15143
15144       case WhiteQueen:
15145       case BlackQueen:
15146         if(gameInfo.variant == VariantShatranj ||
15147            gameInfo.variant == VariantXiangqi  ||
15148            gameInfo.variant == VariantCourier  ||
15149            gameInfo.variant == VariantASEAN    ||
15150            gameInfo.variant == VariantMakruk     )
15151             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15152         goto defaultlabel;
15153
15154       case WhiteKing:
15155       case BlackKing:
15156         if(gameInfo.variant == VariantXiangqi)
15157             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15158         if(gameInfo.variant == VariantKnightmate)
15159             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15160       default:
15161         defaultlabel:
15162         if (gameMode == IcsExamining) {
15163             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15164             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15165                      PieceToChar(selection), AAA + x, ONE + y);
15166             SendToICS(buf);
15167         } else {
15168             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15169                 int n;
15170                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15171                     n = PieceToNumber(selection - BlackPawn);
15172                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15173                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15174                     boards[0][BOARD_HEIGHT-1-n][1]++;
15175                 } else
15176                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15177                     n = PieceToNumber(selection);
15178                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15179                     boards[0][n][BOARD_WIDTH-1] = selection;
15180                     boards[0][n][BOARD_WIDTH-2]++;
15181                 }
15182             } else
15183             boards[0][y][x] = selection;
15184             DrawPosition(TRUE, boards[0]);
15185             ClearHighlights();
15186             fromX = fromY = -1;
15187         }
15188         break;
15189     }
15190 }
15191
15192
15193 void
15194 DropMenuEvent (ChessSquare selection, int x, int y)
15195 {
15196     ChessMove moveType;
15197
15198     switch (gameMode) {
15199       case IcsPlayingWhite:
15200       case MachinePlaysBlack:
15201         if (!WhiteOnMove(currentMove)) {
15202             DisplayMoveError(_("It is Black's turn"));
15203             return;
15204         }
15205         moveType = WhiteDrop;
15206         break;
15207       case IcsPlayingBlack:
15208       case MachinePlaysWhite:
15209         if (WhiteOnMove(currentMove)) {
15210             DisplayMoveError(_("It is White's turn"));
15211             return;
15212         }
15213         moveType = BlackDrop;
15214         break;
15215       case EditGame:
15216         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15217         break;
15218       default:
15219         return;
15220     }
15221
15222     if (moveType == BlackDrop && selection < BlackPawn) {
15223       selection = (ChessSquare) ((int) selection
15224                                  + (int) BlackPawn - (int) WhitePawn);
15225     }
15226     if (boards[currentMove][y][x] != EmptySquare) {
15227         DisplayMoveError(_("That square is occupied"));
15228         return;
15229     }
15230
15231     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15232 }
15233
15234 void
15235 AcceptEvent ()
15236 {
15237     /* Accept a pending offer of any kind from opponent */
15238
15239     if (appData.icsActive) {
15240         SendToICS(ics_prefix);
15241         SendToICS("accept\n");
15242     } else if (cmailMsgLoaded) {
15243         if (currentMove == cmailOldMove &&
15244             commentList[cmailOldMove] != NULL &&
15245             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15246                    "Black offers a draw" : "White offers a draw")) {
15247             TruncateGame();
15248             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15249             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15250         } else {
15251             DisplayError(_("There is no pending offer on this move"), 0);
15252             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15253         }
15254     } else {
15255         /* Not used for offers from chess program */
15256     }
15257 }
15258
15259 void
15260 DeclineEvent ()
15261 {
15262     /* Decline a pending offer of any kind from opponent */
15263
15264     if (appData.icsActive) {
15265         SendToICS(ics_prefix);
15266         SendToICS("decline\n");
15267     } else if (cmailMsgLoaded) {
15268         if (currentMove == cmailOldMove &&
15269             commentList[cmailOldMove] != NULL &&
15270             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15271                    "Black offers a draw" : "White offers a draw")) {
15272 #ifdef NOTDEF
15273             AppendComment(cmailOldMove, "Draw declined", TRUE);
15274             DisplayComment(cmailOldMove - 1, "Draw declined");
15275 #endif /*NOTDEF*/
15276         } else {
15277             DisplayError(_("There is no pending offer on this move"), 0);
15278         }
15279     } else {
15280         /* Not used for offers from chess program */
15281     }
15282 }
15283
15284 void
15285 RematchEvent ()
15286 {
15287     /* Issue ICS rematch command */
15288     if (appData.icsActive) {
15289         SendToICS(ics_prefix);
15290         SendToICS("rematch\n");
15291     }
15292 }
15293
15294 void
15295 CallFlagEvent ()
15296 {
15297     /* Call your opponent's flag (claim a win on time) */
15298     if (appData.icsActive) {
15299         SendToICS(ics_prefix);
15300         SendToICS("flag\n");
15301     } else {
15302         switch (gameMode) {
15303           default:
15304             return;
15305           case MachinePlaysWhite:
15306             if (whiteFlag) {
15307                 if (blackFlag)
15308                   GameEnds(GameIsDrawn, "Both players ran out of time",
15309                            GE_PLAYER);
15310                 else
15311                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15312             } else {
15313                 DisplayError(_("Your opponent is not out of time"), 0);
15314             }
15315             break;
15316           case MachinePlaysBlack:
15317             if (blackFlag) {
15318                 if (whiteFlag)
15319                   GameEnds(GameIsDrawn, "Both players ran out of time",
15320                            GE_PLAYER);
15321                 else
15322                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15323             } else {
15324                 DisplayError(_("Your opponent is not out of time"), 0);
15325             }
15326             break;
15327         }
15328     }
15329 }
15330
15331 void
15332 ClockClick (int which)
15333 {       // [HGM] code moved to back-end from winboard.c
15334         if(which) { // black clock
15335           if (gameMode == EditPosition || gameMode == IcsExamining) {
15336             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15337             SetBlackToPlayEvent();
15338           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15339                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15340           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15341           } else if (shiftKey) {
15342             AdjustClock(which, -1);
15343           } else if (gameMode == IcsPlayingWhite ||
15344                      gameMode == MachinePlaysBlack) {
15345             CallFlagEvent();
15346           }
15347         } else { // white clock
15348           if (gameMode == EditPosition || gameMode == IcsExamining) {
15349             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15350             SetWhiteToPlayEvent();
15351           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15352                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15353           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15354           } else if (shiftKey) {
15355             AdjustClock(which, -1);
15356           } else if (gameMode == IcsPlayingBlack ||
15357                    gameMode == MachinePlaysWhite) {
15358             CallFlagEvent();
15359           }
15360         }
15361 }
15362
15363 void
15364 DrawEvent ()
15365 {
15366     /* Offer draw or accept pending draw offer from opponent */
15367
15368     if (appData.icsActive) {
15369         /* Note: tournament rules require draw offers to be
15370            made after you make your move but before you punch
15371            your clock.  Currently ICS doesn't let you do that;
15372            instead, you immediately punch your clock after making
15373            a move, but you can offer a draw at any time. */
15374
15375         SendToICS(ics_prefix);
15376         SendToICS("draw\n");
15377         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15378     } else if (cmailMsgLoaded) {
15379         if (currentMove == cmailOldMove &&
15380             commentList[cmailOldMove] != NULL &&
15381             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15382                    "Black offers a draw" : "White offers a draw")) {
15383             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15384             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15385         } else if (currentMove == cmailOldMove + 1) {
15386             char *offer = WhiteOnMove(cmailOldMove) ?
15387               "White offers a draw" : "Black offers a draw";
15388             AppendComment(currentMove, offer, TRUE);
15389             DisplayComment(currentMove - 1, offer);
15390             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15391         } else {
15392             DisplayError(_("You must make your move before offering a draw"), 0);
15393             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15394         }
15395     } else if (first.offeredDraw) {
15396         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15397     } else {
15398         if (first.sendDrawOffers) {
15399             SendToProgram("draw\n", &first);
15400             userOfferedDraw = TRUE;
15401         }
15402     }
15403 }
15404
15405 void
15406 AdjournEvent ()
15407 {
15408     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15409
15410     if (appData.icsActive) {
15411         SendToICS(ics_prefix);
15412         SendToICS("adjourn\n");
15413     } else {
15414         /* Currently GNU Chess doesn't offer or accept Adjourns */
15415     }
15416 }
15417
15418
15419 void
15420 AbortEvent ()
15421 {
15422     /* Offer Abort or accept pending Abort offer from opponent */
15423
15424     if (appData.icsActive) {
15425         SendToICS(ics_prefix);
15426         SendToICS("abort\n");
15427     } else {
15428         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15429     }
15430 }
15431
15432 void
15433 ResignEvent ()
15434 {
15435     /* Resign.  You can do this even if it's not your turn. */
15436
15437     if (appData.icsActive) {
15438         SendToICS(ics_prefix);
15439         SendToICS("resign\n");
15440     } else {
15441         switch (gameMode) {
15442           case MachinePlaysWhite:
15443             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15444             break;
15445           case MachinePlaysBlack:
15446             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15447             break;
15448           case EditGame:
15449             if (cmailMsgLoaded) {
15450                 TruncateGame();
15451                 if (WhiteOnMove(cmailOldMove)) {
15452                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15453                 } else {
15454                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15455                 }
15456                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15457             }
15458             break;
15459           default:
15460             break;
15461         }
15462     }
15463 }
15464
15465
15466 void
15467 StopObservingEvent ()
15468 {
15469     /* Stop observing current games */
15470     SendToICS(ics_prefix);
15471     SendToICS("unobserve\n");
15472 }
15473
15474 void
15475 StopExaminingEvent ()
15476 {
15477     /* Stop observing current game */
15478     SendToICS(ics_prefix);
15479     SendToICS("unexamine\n");
15480 }
15481
15482 void
15483 ForwardInner (int target)
15484 {
15485     int limit; int oldSeekGraphUp = seekGraphUp;
15486
15487     if (appData.debugMode)
15488         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15489                 target, currentMove, forwardMostMove);
15490
15491     if (gameMode == EditPosition)
15492       return;
15493
15494     seekGraphUp = FALSE;
15495     MarkTargetSquares(1);
15496
15497     if (gameMode == PlayFromGameFile && !pausing)
15498       PauseEvent();
15499
15500     if (gameMode == IcsExamining && pausing)
15501       limit = pauseExamForwardMostMove;
15502     else
15503       limit = forwardMostMove;
15504
15505     if (target > limit) target = limit;
15506
15507     if (target > 0 && moveList[target - 1][0]) {
15508         int fromX, fromY, toX, toY;
15509         toX = moveList[target - 1][2] - AAA;
15510         toY = moveList[target - 1][3] - ONE;
15511         if (moveList[target - 1][1] == '@') {
15512             if (appData.highlightLastMove) {
15513                 SetHighlights(-1, -1, toX, toY);
15514             }
15515         } else {
15516             int viaX = moveList[target - 1][5] - AAA;
15517             int viaY = moveList[target - 1][6] - ONE;
15518             fromX = moveList[target - 1][0] - AAA;
15519             fromY = moveList[target - 1][1] - ONE;
15520             if (target == currentMove + 1) {
15521                 if(moveList[target - 1][4] == ';') { // multi-leg
15522                     ChessSquare piece = boards[currentMove][viaY][viaX];
15523                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15524                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15525                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15526                     boards[currentMove][viaY][viaX] = piece;
15527                 } else
15528                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15529             }
15530             if (appData.highlightLastMove) {
15531                 SetHighlights(fromX, fromY, toX, toY);
15532             }
15533         }
15534     }
15535     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15536         gameMode == Training || gameMode == PlayFromGameFile ||
15537         gameMode == AnalyzeFile) {
15538         while (currentMove < target) {
15539             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15540             SendMoveToProgram(currentMove++, &first);
15541         }
15542     } else {
15543         currentMove = target;
15544     }
15545
15546     if (gameMode == EditGame || gameMode == EndOfGame) {
15547         whiteTimeRemaining = timeRemaining[0][currentMove];
15548         blackTimeRemaining = timeRemaining[1][currentMove];
15549     }
15550     DisplayBothClocks();
15551     DisplayMove(currentMove - 1);
15552     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15553     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15554     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15555         DisplayComment(currentMove - 1, commentList[currentMove]);
15556     }
15557     ClearMap(); // [HGM] exclude: invalidate map
15558 }
15559
15560
15561 void
15562 ForwardEvent ()
15563 {
15564     if (gameMode == IcsExamining && !pausing) {
15565         SendToICS(ics_prefix);
15566         SendToICS("forward\n");
15567     } else {
15568         ForwardInner(currentMove + 1);
15569     }
15570 }
15571
15572 void
15573 ToEndEvent ()
15574 {
15575     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15576         /* to optimze, we temporarily turn off analysis mode while we feed
15577          * the remaining moves to the engine. Otherwise we get analysis output
15578          * after each move.
15579          */
15580         if (first.analysisSupport) {
15581           SendToProgram("exit\nforce\n", &first);
15582           first.analyzing = FALSE;
15583         }
15584     }
15585
15586     if (gameMode == IcsExamining && !pausing) {
15587         SendToICS(ics_prefix);
15588         SendToICS("forward 999999\n");
15589     } else {
15590         ForwardInner(forwardMostMove);
15591     }
15592
15593     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15594         /* we have fed all the moves, so reactivate analysis mode */
15595         SendToProgram("analyze\n", &first);
15596         first.analyzing = TRUE;
15597         /*first.maybeThinking = TRUE;*/
15598         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15599     }
15600 }
15601
15602 void
15603 BackwardInner (int target)
15604 {
15605     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15606
15607     if (appData.debugMode)
15608         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15609                 target, currentMove, forwardMostMove);
15610
15611     if (gameMode == EditPosition) return;
15612     seekGraphUp = FALSE;
15613     MarkTargetSquares(1);
15614     if (currentMove <= backwardMostMove) {
15615         ClearHighlights();
15616         DrawPosition(full_redraw, boards[currentMove]);
15617         return;
15618     }
15619     if (gameMode == PlayFromGameFile && !pausing)
15620       PauseEvent();
15621
15622     if (moveList[target][0]) {
15623         int fromX, fromY, toX, toY;
15624         toX = moveList[target][2] - AAA;
15625         toY = moveList[target][3] - ONE;
15626         if (moveList[target][1] == '@') {
15627             if (appData.highlightLastMove) {
15628                 SetHighlights(-1, -1, toX, toY);
15629             }
15630         } else {
15631             fromX = moveList[target][0] - AAA;
15632             fromY = moveList[target][1] - ONE;
15633             if (target == currentMove - 1) {
15634                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15635             }
15636             if (appData.highlightLastMove) {
15637                 SetHighlights(fromX, fromY, toX, toY);
15638             }
15639         }
15640     }
15641     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15642         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15643         while (currentMove > target) {
15644             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15645                 // null move cannot be undone. Reload program with move history before it.
15646                 int i;
15647                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15648                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15649                 }
15650                 SendBoard(&first, i);
15651               if(second.analyzing) SendBoard(&second, i);
15652                 for(currentMove=i; currentMove<target; currentMove++) {
15653                     SendMoveToProgram(currentMove, &first);
15654                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15655                 }
15656                 break;
15657             }
15658             SendToBoth("undo\n");
15659             currentMove--;
15660         }
15661     } else {
15662         currentMove = target;
15663     }
15664
15665     if (gameMode == EditGame || gameMode == EndOfGame) {
15666         whiteTimeRemaining = timeRemaining[0][currentMove];
15667         blackTimeRemaining = timeRemaining[1][currentMove];
15668     }
15669     DisplayBothClocks();
15670     DisplayMove(currentMove - 1);
15671     DrawPosition(full_redraw, boards[currentMove]);
15672     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15673     // [HGM] PV info: routine tests if comment empty
15674     DisplayComment(currentMove - 1, commentList[currentMove]);
15675     ClearMap(); // [HGM] exclude: invalidate map
15676 }
15677
15678 void
15679 BackwardEvent ()
15680 {
15681     if (gameMode == IcsExamining && !pausing) {
15682         SendToICS(ics_prefix);
15683         SendToICS("backward\n");
15684     } else {
15685         BackwardInner(currentMove - 1);
15686     }
15687 }
15688
15689 void
15690 ToStartEvent ()
15691 {
15692     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15693         /* to optimize, we temporarily turn off analysis mode while we undo
15694          * all the moves. Otherwise we get analysis output after each undo.
15695          */
15696         if (first.analysisSupport) {
15697           SendToProgram("exit\nforce\n", &first);
15698           first.analyzing = FALSE;
15699         }
15700     }
15701
15702     if (gameMode == IcsExamining && !pausing) {
15703         SendToICS(ics_prefix);
15704         SendToICS("backward 999999\n");
15705     } else {
15706         BackwardInner(backwardMostMove);
15707     }
15708
15709     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15710         /* we have fed all the moves, so reactivate analysis mode */
15711         SendToProgram("analyze\n", &first);
15712         first.analyzing = TRUE;
15713         /*first.maybeThinking = TRUE;*/
15714         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15715     }
15716 }
15717
15718 void
15719 ToNrEvent (int to)
15720 {
15721   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15722   if (to >= forwardMostMove) to = forwardMostMove;
15723   if (to <= backwardMostMove) to = backwardMostMove;
15724   if (to < currentMove) {
15725     BackwardInner(to);
15726   } else {
15727     ForwardInner(to);
15728   }
15729 }
15730
15731 void
15732 RevertEvent (Boolean annotate)
15733 {
15734     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15735         return;
15736     }
15737     if (gameMode != IcsExamining) {
15738         DisplayError(_("You are not examining a game"), 0);
15739         return;
15740     }
15741     if (pausing) {
15742         DisplayError(_("You can't revert while pausing"), 0);
15743         return;
15744     }
15745     SendToICS(ics_prefix);
15746     SendToICS("revert\n");
15747 }
15748
15749 void
15750 RetractMoveEvent ()
15751 {
15752     switch (gameMode) {
15753       case MachinePlaysWhite:
15754       case MachinePlaysBlack:
15755         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15756             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15757             return;
15758         }
15759         if (forwardMostMove < 2) return;
15760         currentMove = forwardMostMove = forwardMostMove - 2;
15761         whiteTimeRemaining = timeRemaining[0][currentMove];
15762         blackTimeRemaining = timeRemaining[1][currentMove];
15763         DisplayBothClocks();
15764         DisplayMove(currentMove - 1);
15765         ClearHighlights();/*!! could figure this out*/
15766         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15767         SendToProgram("remove\n", &first);
15768         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15769         break;
15770
15771       case BeginningOfGame:
15772       default:
15773         break;
15774
15775       case IcsPlayingWhite:
15776       case IcsPlayingBlack:
15777         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15778             SendToICS(ics_prefix);
15779             SendToICS("takeback 2\n");
15780         } else {
15781             SendToICS(ics_prefix);
15782             SendToICS("takeback 1\n");
15783         }
15784         break;
15785     }
15786 }
15787
15788 void
15789 MoveNowEvent ()
15790 {
15791     ChessProgramState *cps;
15792
15793     switch (gameMode) {
15794       case MachinePlaysWhite:
15795         if (!WhiteOnMove(forwardMostMove)) {
15796             DisplayError(_("It is your turn"), 0);
15797             return;
15798         }
15799         cps = &first;
15800         break;
15801       case MachinePlaysBlack:
15802         if (WhiteOnMove(forwardMostMove)) {
15803             DisplayError(_("It is your turn"), 0);
15804             return;
15805         }
15806         cps = &first;
15807         break;
15808       case TwoMachinesPlay:
15809         if (WhiteOnMove(forwardMostMove) ==
15810             (first.twoMachinesColor[0] == 'w')) {
15811             cps = &first;
15812         } else {
15813             cps = &second;
15814         }
15815         break;
15816       case BeginningOfGame:
15817       default:
15818         return;
15819     }
15820     SendToProgram("?\n", cps);
15821 }
15822
15823 void
15824 TruncateGameEvent ()
15825 {
15826     EditGameEvent();
15827     if (gameMode != EditGame) return;
15828     TruncateGame();
15829 }
15830
15831 void
15832 TruncateGame ()
15833 {
15834     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15835     if (forwardMostMove > currentMove) {
15836         if (gameInfo.resultDetails != NULL) {
15837             free(gameInfo.resultDetails);
15838             gameInfo.resultDetails = NULL;
15839             gameInfo.result = GameUnfinished;
15840         }
15841         forwardMostMove = currentMove;
15842         HistorySet(parseList, backwardMostMove, forwardMostMove,
15843                    currentMove-1);
15844     }
15845 }
15846
15847 void
15848 HintEvent ()
15849 {
15850     if (appData.noChessProgram) return;
15851     switch (gameMode) {
15852       case MachinePlaysWhite:
15853         if (WhiteOnMove(forwardMostMove)) {
15854             DisplayError(_("Wait until your turn."), 0);
15855             return;
15856         }
15857         break;
15858       case BeginningOfGame:
15859       case MachinePlaysBlack:
15860         if (!WhiteOnMove(forwardMostMove)) {
15861             DisplayError(_("Wait until your turn."), 0);
15862             return;
15863         }
15864         break;
15865       default:
15866         DisplayError(_("No hint available"), 0);
15867         return;
15868     }
15869     SendToProgram("hint\n", &first);
15870     hintRequested = TRUE;
15871 }
15872
15873 int
15874 SaveSelected (FILE *g, int dummy, char *dummy2)
15875 {
15876     ListGame * lg = (ListGame *) gameList.head;
15877     int nItem, cnt=0;
15878     FILE *f;
15879
15880     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15881         DisplayError(_("Game list not loaded or empty"), 0);
15882         return 0;
15883     }
15884
15885     creatingBook = TRUE; // suppresses stuff during load game
15886
15887     /* Get list size */
15888     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15889         if(lg->position >= 0) { // selected?
15890             LoadGame(f, nItem, "", TRUE);
15891             SaveGamePGN2(g); // leaves g open
15892             cnt++; DoEvents();
15893         }
15894         lg = (ListGame *) lg->node.succ;
15895     }
15896
15897     fclose(g);
15898     creatingBook = FALSE;
15899
15900     return cnt;
15901 }
15902
15903 void
15904 CreateBookEvent ()
15905 {
15906     ListGame * lg = (ListGame *) gameList.head;
15907     FILE *f, *g;
15908     int nItem;
15909     static int secondTime = FALSE;
15910
15911     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15912         DisplayError(_("Game list not loaded or empty"), 0);
15913         return;
15914     }
15915
15916     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15917         fclose(g);
15918         secondTime++;
15919         DisplayNote(_("Book file exists! Try again for overwrite."));
15920         return;
15921     }
15922
15923     creatingBook = TRUE;
15924     secondTime = FALSE;
15925
15926     /* Get list size */
15927     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15928         if(lg->position >= 0) {
15929             LoadGame(f, nItem, "", TRUE);
15930             AddGameToBook(TRUE);
15931             DoEvents();
15932         }
15933         lg = (ListGame *) lg->node.succ;
15934     }
15935
15936     creatingBook = FALSE;
15937     FlushBook();
15938 }
15939
15940 void
15941 BookEvent ()
15942 {
15943     if (appData.noChessProgram) return;
15944     switch (gameMode) {
15945       case MachinePlaysWhite:
15946         if (WhiteOnMove(forwardMostMove)) {
15947             DisplayError(_("Wait until your turn."), 0);
15948             return;
15949         }
15950         break;
15951       case BeginningOfGame:
15952       case MachinePlaysBlack:
15953         if (!WhiteOnMove(forwardMostMove)) {
15954             DisplayError(_("Wait until your turn."), 0);
15955             return;
15956         }
15957         break;
15958       case EditPosition:
15959         EditPositionDone(TRUE);
15960         break;
15961       case TwoMachinesPlay:
15962         return;
15963       default:
15964         break;
15965     }
15966     SendToProgram("bk\n", &first);
15967     bookOutput[0] = NULLCHAR;
15968     bookRequested = TRUE;
15969 }
15970
15971 void
15972 AboutGameEvent ()
15973 {
15974     char *tags = PGNTags(&gameInfo);
15975     TagsPopUp(tags, CmailMsg());
15976     free(tags);
15977 }
15978
15979 /* end button procedures */
15980
15981 void
15982 PrintPosition (FILE *fp, int move)
15983 {
15984     int i, j;
15985
15986     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15987         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15988             char c = PieceToChar(boards[move][i][j]);
15989             fputc(c == 'x' ? '.' : c, fp);
15990             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15991         }
15992     }
15993     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15994       fprintf(fp, "white to play\n");
15995     else
15996       fprintf(fp, "black to play\n");
15997 }
15998
15999 void
16000 PrintOpponents (FILE *fp)
16001 {
16002     if (gameInfo.white != NULL) {
16003         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16004     } else {
16005         fprintf(fp, "\n");
16006     }
16007 }
16008
16009 /* Find last component of program's own name, using some heuristics */
16010 void
16011 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16012 {
16013     char *p, *q, c;
16014     int local = (strcmp(host, "localhost") == 0);
16015     while (!local && (p = strchr(prog, ';')) != NULL) {
16016         p++;
16017         while (*p == ' ') p++;
16018         prog = p;
16019     }
16020     if (*prog == '"' || *prog == '\'') {
16021         q = strchr(prog + 1, *prog);
16022     } else {
16023         q = strchr(prog, ' ');
16024     }
16025     if (q == NULL) q = prog + strlen(prog);
16026     p = q;
16027     while (p >= prog && *p != '/' && *p != '\\') p--;
16028     p++;
16029     if(p == prog && *p == '"') p++;
16030     c = *q; *q = 0;
16031     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16032     memcpy(buf, p, q - p);
16033     buf[q - p] = NULLCHAR;
16034     if (!local) {
16035         strcat(buf, "@");
16036         strcat(buf, host);
16037     }
16038 }
16039
16040 char *
16041 TimeControlTagValue ()
16042 {
16043     char buf[MSG_SIZ];
16044     if (!appData.clockMode) {
16045       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16046     } else if (movesPerSession > 0) {
16047       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16048     } else if (timeIncrement == 0) {
16049       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16050     } else {
16051       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16052     }
16053     return StrSave(buf);
16054 }
16055
16056 void
16057 SetGameInfo ()
16058 {
16059     /* This routine is used only for certain modes */
16060     VariantClass v = gameInfo.variant;
16061     ChessMove r = GameUnfinished;
16062     char *p = NULL;
16063
16064     if(keepInfo) return;
16065
16066     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16067         r = gameInfo.result;
16068         p = gameInfo.resultDetails;
16069         gameInfo.resultDetails = NULL;
16070     }
16071     ClearGameInfo(&gameInfo);
16072     gameInfo.variant = v;
16073
16074     switch (gameMode) {
16075       case MachinePlaysWhite:
16076         gameInfo.event = StrSave( appData.pgnEventHeader );
16077         gameInfo.site = StrSave(HostName());
16078         gameInfo.date = PGNDate();
16079         gameInfo.round = StrSave("-");
16080         gameInfo.white = StrSave(first.tidy);
16081         gameInfo.black = StrSave(UserName());
16082         gameInfo.timeControl = TimeControlTagValue();
16083         break;
16084
16085       case MachinePlaysBlack:
16086         gameInfo.event = StrSave( appData.pgnEventHeader );
16087         gameInfo.site = StrSave(HostName());
16088         gameInfo.date = PGNDate();
16089         gameInfo.round = StrSave("-");
16090         gameInfo.white = StrSave(UserName());
16091         gameInfo.black = StrSave(first.tidy);
16092         gameInfo.timeControl = TimeControlTagValue();
16093         break;
16094
16095       case TwoMachinesPlay:
16096         gameInfo.event = StrSave( appData.pgnEventHeader );
16097         gameInfo.site = StrSave(HostName());
16098         gameInfo.date = PGNDate();
16099         if (roundNr > 0) {
16100             char buf[MSG_SIZ];
16101             snprintf(buf, MSG_SIZ, "%d", roundNr);
16102             gameInfo.round = StrSave(buf);
16103         } else {
16104             gameInfo.round = StrSave("-");
16105         }
16106         if (first.twoMachinesColor[0] == 'w') {
16107             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16108             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16109         } else {
16110             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16111             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16112         }
16113         gameInfo.timeControl = TimeControlTagValue();
16114         break;
16115
16116       case EditGame:
16117         gameInfo.event = StrSave("Edited game");
16118         gameInfo.site = StrSave(HostName());
16119         gameInfo.date = PGNDate();
16120         gameInfo.round = StrSave("-");
16121         gameInfo.white = StrSave("-");
16122         gameInfo.black = StrSave("-");
16123         gameInfo.result = r;
16124         gameInfo.resultDetails = p;
16125         break;
16126
16127       case EditPosition:
16128         gameInfo.event = StrSave("Edited position");
16129         gameInfo.site = StrSave(HostName());
16130         gameInfo.date = PGNDate();
16131         gameInfo.round = StrSave("-");
16132         gameInfo.white = StrSave("-");
16133         gameInfo.black = StrSave("-");
16134         break;
16135
16136       case IcsPlayingWhite:
16137       case IcsPlayingBlack:
16138       case IcsObserving:
16139       case IcsExamining:
16140         break;
16141
16142       case PlayFromGameFile:
16143         gameInfo.event = StrSave("Game from non-PGN file");
16144         gameInfo.site = StrSave(HostName());
16145         gameInfo.date = PGNDate();
16146         gameInfo.round = StrSave("-");
16147         gameInfo.white = StrSave("?");
16148         gameInfo.black = StrSave("?");
16149         break;
16150
16151       default:
16152         break;
16153     }
16154 }
16155
16156 void
16157 ReplaceComment (int index, char *text)
16158 {
16159     int len;
16160     char *p;
16161     float score;
16162
16163     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16164        pvInfoList[index-1].depth == len &&
16165        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16166        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16167     while (*text == '\n') text++;
16168     len = strlen(text);
16169     while (len > 0 && text[len - 1] == '\n') len--;
16170
16171     if (commentList[index] != NULL)
16172       free(commentList[index]);
16173
16174     if (len == 0) {
16175         commentList[index] = NULL;
16176         return;
16177     }
16178   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16179       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16180       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16181     commentList[index] = (char *) malloc(len + 2);
16182     strncpy(commentList[index], text, len);
16183     commentList[index][len] = '\n';
16184     commentList[index][len + 1] = NULLCHAR;
16185   } else {
16186     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16187     char *p;
16188     commentList[index] = (char *) malloc(len + 7);
16189     safeStrCpy(commentList[index], "{\n", 3);
16190     safeStrCpy(commentList[index]+2, text, len+1);
16191     commentList[index][len+2] = NULLCHAR;
16192     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16193     strcat(commentList[index], "\n}\n");
16194   }
16195 }
16196
16197 void
16198 CrushCRs (char *text)
16199 {
16200   char *p = text;
16201   char *q = text;
16202   char ch;
16203
16204   do {
16205     ch = *p++;
16206     if (ch == '\r') continue;
16207     *q++ = ch;
16208   } while (ch != '\0');
16209 }
16210
16211 void
16212 AppendComment (int index, char *text, Boolean addBraces)
16213 /* addBraces  tells if we should add {} */
16214 {
16215     int oldlen, len;
16216     char *old;
16217
16218 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16219     if(addBraces == 3) addBraces = 0; else // force appending literally
16220     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16221
16222     CrushCRs(text);
16223     while (*text == '\n') text++;
16224     len = strlen(text);
16225     while (len > 0 && text[len - 1] == '\n') len--;
16226     text[len] = NULLCHAR;
16227
16228     if (len == 0) return;
16229
16230     if (commentList[index] != NULL) {
16231       Boolean addClosingBrace = addBraces;
16232         old = commentList[index];
16233         oldlen = strlen(old);
16234         while(commentList[index][oldlen-1] ==  '\n')
16235           commentList[index][--oldlen] = NULLCHAR;
16236         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16237         safeStrCpy(commentList[index], old, oldlen + len + 6);
16238         free(old);
16239         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16240         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16241           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16242           while (*text == '\n') { text++; len--; }
16243           commentList[index][--oldlen] = NULLCHAR;
16244       }
16245         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16246         else          strcat(commentList[index], "\n");
16247         strcat(commentList[index], text);
16248         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16249         else          strcat(commentList[index], "\n");
16250     } else {
16251         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16252         if(addBraces)
16253           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16254         else commentList[index][0] = NULLCHAR;
16255         strcat(commentList[index], text);
16256         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16257         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16258     }
16259 }
16260
16261 static char *
16262 FindStr (char * text, char * sub_text)
16263 {
16264     char * result = strstr( text, sub_text );
16265
16266     if( result != NULL ) {
16267         result += strlen( sub_text );
16268     }
16269
16270     return result;
16271 }
16272
16273 /* [AS] Try to extract PV info from PGN comment */
16274 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16275 char *
16276 GetInfoFromComment (int index, char * text)
16277 {
16278     char * sep = text, *p;
16279
16280     if( text != NULL && index > 0 ) {
16281         int score = 0;
16282         int depth = 0;
16283         int time = -1, sec = 0, deci;
16284         char * s_eval = FindStr( text, "[%eval " );
16285         char * s_emt = FindStr( text, "[%emt " );
16286 #if 0
16287         if( s_eval != NULL || s_emt != NULL ) {
16288 #else
16289         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16290 #endif
16291             /* New style */
16292             char delim;
16293
16294             if( s_eval != NULL ) {
16295                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16296                     return text;
16297                 }
16298
16299                 if( delim != ']' ) {
16300                     return text;
16301                 }
16302             }
16303
16304             if( s_emt != NULL ) {
16305             }
16306                 return text;
16307         }
16308         else {
16309             /* We expect something like: [+|-]nnn.nn/dd */
16310             int score_lo = 0;
16311
16312             if(*text != '{') return text; // [HGM] braces: must be normal comment
16313
16314             sep = strchr( text, '/' );
16315             if( sep == NULL || sep < (text+4) ) {
16316                 return text;
16317             }
16318
16319             p = text;
16320             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16321             if(p[1] == '(') { // comment starts with PV
16322                p = strchr(p, ')'); // locate end of PV
16323                if(p == NULL || sep < p+5) return text;
16324                // at this point we have something like "{(.*) +0.23/6 ..."
16325                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16326                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16327                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16328             }
16329             time = -1; sec = -1; deci = -1;
16330             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16331                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16332                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16333                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16334                 return text;
16335             }
16336
16337             if( score_lo < 0 || score_lo >= 100 ) {
16338                 return text;
16339             }
16340
16341             if(sec >= 0) time = 600*time + 10*sec; else
16342             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16343
16344             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16345
16346             /* [HGM] PV time: now locate end of PV info */
16347             while( *++sep >= '0' && *sep <= '9'); // strip depth
16348             if(time >= 0)
16349             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16350             if(sec >= 0)
16351             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16352             if(deci >= 0)
16353             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16354             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16355         }
16356
16357         if( depth <= 0 ) {
16358             return text;
16359         }
16360
16361         if( time < 0 ) {
16362             time = -1;
16363         }
16364
16365         pvInfoList[index-1].depth = depth;
16366         pvInfoList[index-1].score = score;
16367         pvInfoList[index-1].time  = 10*time; // centi-sec
16368         if(*sep == '}') *sep = 0; else *--sep = '{';
16369         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16370     }
16371     return sep;
16372 }
16373
16374 void
16375 SendToProgram (char *message, ChessProgramState *cps)
16376 {
16377     int count, outCount, error;
16378     char buf[MSG_SIZ];
16379
16380     if (cps->pr == NoProc) return;
16381     Attention(cps);
16382
16383     if (appData.debugMode) {
16384         TimeMark now;
16385         GetTimeMark(&now);
16386         fprintf(debugFP, "%ld >%-6s: %s",
16387                 SubtractTimeMarks(&now, &programStartTime),
16388                 cps->which, message);
16389         if(serverFP)
16390             fprintf(serverFP, "%ld >%-6s: %s",
16391                 SubtractTimeMarks(&now, &programStartTime),
16392                 cps->which, message), fflush(serverFP);
16393     }
16394
16395     count = strlen(message);
16396     outCount = OutputToProcess(cps->pr, message, count, &error);
16397     if (outCount < count && !exiting
16398                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16399       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16400       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16401         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16402             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16403                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16404                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16405                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16406             } else {
16407                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16408                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16409                 gameInfo.result = res;
16410             }
16411             gameInfo.resultDetails = StrSave(buf);
16412         }
16413         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16414         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16415     }
16416 }
16417
16418 void
16419 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16420 {
16421     char *end_str;
16422     char buf[MSG_SIZ];
16423     ChessProgramState *cps = (ChessProgramState *)closure;
16424
16425     if (isr != cps->isr) return; /* Killed intentionally */
16426     if (count <= 0) {
16427         if (count == 0) {
16428             RemoveInputSource(cps->isr);
16429             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16430                     _(cps->which), cps->program);
16431             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16432             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16433                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16434                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16435                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16436                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16437                 } else {
16438                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16439                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16440                     gameInfo.result = res;
16441                 }
16442                 gameInfo.resultDetails = StrSave(buf);
16443             }
16444             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16445             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16446         } else {
16447             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16448                     _(cps->which), cps->program);
16449             RemoveInputSource(cps->isr);
16450
16451             /* [AS] Program is misbehaving badly... kill it */
16452             if( count == -2 ) {
16453                 DestroyChildProcess( cps->pr, 9 );
16454                 cps->pr = NoProc;
16455             }
16456
16457             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16458         }
16459         return;
16460     }
16461
16462     if ((end_str = strchr(message, '\r')) != NULL)
16463       *end_str = NULLCHAR;
16464     if ((end_str = strchr(message, '\n')) != NULL)
16465       *end_str = NULLCHAR;
16466
16467     if (appData.debugMode) {
16468         TimeMark now; int print = 1;
16469         char *quote = ""; char c; int i;
16470
16471         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16472                 char start = message[0];
16473                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16474                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16475                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16476                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16477                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16478                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16479                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16480                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16481                    sscanf(message, "hint: %c", &c)!=1 &&
16482                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16483                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16484                     print = (appData.engineComments >= 2);
16485                 }
16486                 message[0] = start; // restore original message
16487         }
16488         if(print) {
16489                 GetTimeMark(&now);
16490                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16491                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16492                         quote,
16493                         message);
16494                 if(serverFP)
16495                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16496                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16497                         quote,
16498                         message), fflush(serverFP);
16499         }
16500     }
16501
16502     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16503     if (appData.icsEngineAnalyze) {
16504         if (strstr(message, "whisper") != NULL ||
16505              strstr(message, "kibitz") != NULL ||
16506             strstr(message, "tellics") != NULL) return;
16507     }
16508
16509     HandleMachineMove(message, cps);
16510 }
16511
16512
16513 void
16514 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16515 {
16516     char buf[MSG_SIZ];
16517     int seconds;
16518
16519     if( timeControl_2 > 0 ) {
16520         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16521             tc = timeControl_2;
16522         }
16523     }
16524     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16525     inc /= cps->timeOdds;
16526     st  /= cps->timeOdds;
16527
16528     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16529
16530     if (st > 0) {
16531       /* Set exact time per move, normally using st command */
16532       if (cps->stKludge) {
16533         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16534         seconds = st % 60;
16535         if (seconds == 0) {
16536           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16537         } else {
16538           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16539         }
16540       } else {
16541         snprintf(buf, MSG_SIZ, "st %d\n", st);
16542       }
16543     } else {
16544       /* Set conventional or incremental time control, using level command */
16545       if (seconds == 0) {
16546         /* Note old gnuchess bug -- minutes:seconds used to not work.
16547            Fixed in later versions, but still avoid :seconds
16548            when seconds is 0. */
16549         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16550       } else {
16551         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16552                  seconds, inc/1000.);
16553       }
16554     }
16555     SendToProgram(buf, cps);
16556
16557     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16558     /* Orthogonally, limit search to given depth */
16559     if (sd > 0) {
16560       if (cps->sdKludge) {
16561         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16562       } else {
16563         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16564       }
16565       SendToProgram(buf, cps);
16566     }
16567
16568     if(cps->nps >= 0) { /* [HGM] nps */
16569         if(cps->supportsNPS == FALSE)
16570           cps->nps = -1; // don't use if engine explicitly says not supported!
16571         else {
16572           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16573           SendToProgram(buf, cps);
16574         }
16575     }
16576 }
16577
16578 ChessProgramState *
16579 WhitePlayer ()
16580 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16581 {
16582     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16583        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16584         return &second;
16585     return &first;
16586 }
16587
16588 void
16589 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16590 {
16591     char message[MSG_SIZ];
16592     long time, otime;
16593
16594     /* Note: this routine must be called when the clocks are stopped
16595        or when they have *just* been set or switched; otherwise
16596        it will be off by the time since the current tick started.
16597     */
16598     if (machineWhite) {
16599         time = whiteTimeRemaining / 10;
16600         otime = blackTimeRemaining / 10;
16601     } else {
16602         time = blackTimeRemaining / 10;
16603         otime = whiteTimeRemaining / 10;
16604     }
16605     /* [HGM] translate opponent's time by time-odds factor */
16606     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16607
16608     if (time <= 0) time = 1;
16609     if (otime <= 0) otime = 1;
16610
16611     snprintf(message, MSG_SIZ, "time %ld\n", time);
16612     SendToProgram(message, cps);
16613
16614     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16615     SendToProgram(message, cps);
16616 }
16617
16618 char *
16619 EngineDefinedVariant (ChessProgramState *cps, int n)
16620 {   // return name of n-th unknown variant that engine supports
16621     static char buf[MSG_SIZ];
16622     char *p, *s = cps->variants;
16623     if(!s) return NULL;
16624     do { // parse string from variants feature
16625       VariantClass v;
16626         p = strchr(s, ',');
16627         if(p) *p = NULLCHAR;
16628       v = StringToVariant(s);
16629       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16630         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16631             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16632                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16633                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16634                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16635             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16636         }
16637         if(p) *p++ = ',';
16638         if(n < 0) return buf;
16639     } while(s = p);
16640     return NULL;
16641 }
16642
16643 int
16644 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16645 {
16646   char buf[MSG_SIZ];
16647   int len = strlen(name);
16648   int val;
16649
16650   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16651     (*p) += len + 1;
16652     sscanf(*p, "%d", &val);
16653     *loc = (val != 0);
16654     while (**p && **p != ' ')
16655       (*p)++;
16656     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16657     SendToProgram(buf, cps);
16658     return TRUE;
16659   }
16660   return FALSE;
16661 }
16662
16663 int
16664 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16665 {
16666   char buf[MSG_SIZ];
16667   int len = strlen(name);
16668   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16669     (*p) += len + 1;
16670     sscanf(*p, "%d", loc);
16671     while (**p && **p != ' ') (*p)++;
16672     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16673     SendToProgram(buf, cps);
16674     return TRUE;
16675   }
16676   return FALSE;
16677 }
16678
16679 int
16680 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16681 {
16682   char buf[MSG_SIZ];
16683   int len = strlen(name);
16684   if (strncmp((*p), name, len) == 0
16685       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16686     (*p) += len + 2;
16687     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16688     sscanf(*p, "%[^\"]", *loc);
16689     while (**p && **p != '\"') (*p)++;
16690     if (**p == '\"') (*p)++;
16691     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16692     SendToProgram(buf, cps);
16693     return TRUE;
16694   }
16695   return FALSE;
16696 }
16697
16698 int
16699 ParseOption (Option *opt, ChessProgramState *cps)
16700 // [HGM] options: process the string that defines an engine option, and determine
16701 // name, type, default value, and allowed value range
16702 {
16703         char *p, *q, buf[MSG_SIZ];
16704         int n, min = (-1)<<31, max = 1<<31, def;
16705
16706         if(p = strstr(opt->name, " -spin ")) {
16707             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16708             if(max < min) max = min; // enforce consistency
16709             if(def < min) def = min;
16710             if(def > max) def = max;
16711             opt->value = def;
16712             opt->min = min;
16713             opt->max = max;
16714             opt->type = Spin;
16715         } else if((p = strstr(opt->name, " -slider "))) {
16716             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16717             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16718             if(max < min) max = min; // enforce consistency
16719             if(def < min) def = min;
16720             if(def > max) def = max;
16721             opt->value = def;
16722             opt->min = min;
16723             opt->max = max;
16724             opt->type = Spin; // Slider;
16725         } else if((p = strstr(opt->name, " -string "))) {
16726             opt->textValue = p+9;
16727             opt->type = TextBox;
16728         } else if((p = strstr(opt->name, " -file "))) {
16729             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16730             opt->textValue = p+7;
16731             opt->type = FileName; // FileName;
16732         } else if((p = strstr(opt->name, " -path "))) {
16733             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16734             opt->textValue = p+7;
16735             opt->type = PathName; // PathName;
16736         } else if(p = strstr(opt->name, " -check ")) {
16737             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16738             opt->value = (def != 0);
16739             opt->type = CheckBox;
16740         } else if(p = strstr(opt->name, " -combo ")) {
16741             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16742             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16743             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16744             opt->value = n = 0;
16745             while(q = StrStr(q, " /// ")) {
16746                 n++; *q = 0;    // count choices, and null-terminate each of them
16747                 q += 5;
16748                 if(*q == '*') { // remember default, which is marked with * prefix
16749                     q++;
16750                     opt->value = n;
16751                 }
16752                 cps->comboList[cps->comboCnt++] = q;
16753             }
16754             cps->comboList[cps->comboCnt++] = NULL;
16755             opt->max = n + 1;
16756             opt->type = ComboBox;
16757         } else if(p = strstr(opt->name, " -button")) {
16758             opt->type = Button;
16759         } else if(p = strstr(opt->name, " -save")) {
16760             opt->type = SaveButton;
16761         } else return FALSE;
16762         *p = 0; // terminate option name
16763         // now look if the command-line options define a setting for this engine option.
16764         if(cps->optionSettings && cps->optionSettings[0])
16765             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16766         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16767           snprintf(buf, MSG_SIZ, "option %s", p);
16768                 if(p = strstr(buf, ",")) *p = 0;
16769                 if(q = strchr(buf, '=')) switch(opt->type) {
16770                     case ComboBox:
16771                         for(n=0; n<opt->max; n++)
16772                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16773                         break;
16774                     case TextBox:
16775                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16776                         break;
16777                     case Spin:
16778                     case CheckBox:
16779                         opt->value = atoi(q+1);
16780                     default:
16781                         break;
16782                 }
16783                 strcat(buf, "\n");
16784                 SendToProgram(buf, cps);
16785         }
16786         return TRUE;
16787 }
16788
16789 void
16790 FeatureDone (ChessProgramState *cps, int val)
16791 {
16792   DelayedEventCallback cb = GetDelayedEvent();
16793   if ((cb == InitBackEnd3 && cps == &first) ||
16794       (cb == SettingsMenuIfReady && cps == &second) ||
16795       (cb == LoadEngine) ||
16796       (cb == TwoMachinesEventIfReady)) {
16797     CancelDelayedEvent();
16798     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16799   }
16800   cps->initDone = val;
16801   if(val) cps->reload = FALSE;
16802 }
16803
16804 /* Parse feature command from engine */
16805 void
16806 ParseFeatures (char *args, ChessProgramState *cps)
16807 {
16808   char *p = args;
16809   char *q = NULL;
16810   int val;
16811   char buf[MSG_SIZ];
16812
16813   for (;;) {
16814     while (*p == ' ') p++;
16815     if (*p == NULLCHAR) return;
16816
16817     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16818     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16819     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16820     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16821     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16822     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16823     if (BoolFeature(&p, "reuse", &val, cps)) {
16824       /* Engine can disable reuse, but can't enable it if user said no */
16825       if (!val) cps->reuse = FALSE;
16826       continue;
16827     }
16828     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16829     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16830       if (gameMode == TwoMachinesPlay) {
16831         DisplayTwoMachinesTitle();
16832       } else {
16833         DisplayTitle("");
16834       }
16835       continue;
16836     }
16837     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16838     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16839     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16840     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16841     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16842     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16843     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16844     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16845     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16846     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16847     if (IntFeature(&p, "done", &val, cps)) {
16848       FeatureDone(cps, val);
16849       continue;
16850     }
16851     /* Added by Tord: */
16852     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16853     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16854     /* End of additions by Tord */
16855
16856     /* [HGM] added features: */
16857     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16858     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16859     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16860     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16861     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16862     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16863     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16864     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16865         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16866         FREE(cps->option[cps->nrOptions].name);
16867         cps->option[cps->nrOptions].name = q; q = NULL;
16868         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16869           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16870             SendToProgram(buf, cps);
16871             continue;
16872         }
16873         if(cps->nrOptions >= MAX_OPTIONS) {
16874             cps->nrOptions--;
16875             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16876             DisplayError(buf, 0);
16877         }
16878         continue;
16879     }
16880     /* End of additions by HGM */
16881
16882     /* unknown feature: complain and skip */
16883     q = p;
16884     while (*q && *q != '=') q++;
16885     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16886     SendToProgram(buf, cps);
16887     p = q;
16888     if (*p == '=') {
16889       p++;
16890       if (*p == '\"') {
16891         p++;
16892         while (*p && *p != '\"') p++;
16893         if (*p == '\"') p++;
16894       } else {
16895         while (*p && *p != ' ') p++;
16896       }
16897     }
16898   }
16899
16900 }
16901
16902 void
16903 PeriodicUpdatesEvent (int newState)
16904 {
16905     if (newState == appData.periodicUpdates)
16906       return;
16907
16908     appData.periodicUpdates=newState;
16909
16910     /* Display type changes, so update it now */
16911 //    DisplayAnalysis();
16912
16913     /* Get the ball rolling again... */
16914     if (newState) {
16915         AnalysisPeriodicEvent(1);
16916         StartAnalysisClock();
16917     }
16918 }
16919
16920 void
16921 PonderNextMoveEvent (int newState)
16922 {
16923     if (newState == appData.ponderNextMove) return;
16924     if (gameMode == EditPosition) EditPositionDone(TRUE);
16925     if (newState) {
16926         SendToProgram("hard\n", &first);
16927         if (gameMode == TwoMachinesPlay) {
16928             SendToProgram("hard\n", &second);
16929         }
16930     } else {
16931         SendToProgram("easy\n", &first);
16932         thinkOutput[0] = NULLCHAR;
16933         if (gameMode == TwoMachinesPlay) {
16934             SendToProgram("easy\n", &second);
16935         }
16936     }
16937     appData.ponderNextMove = newState;
16938 }
16939
16940 void
16941 NewSettingEvent (int option, int *feature, char *command, int value)
16942 {
16943     char buf[MSG_SIZ];
16944
16945     if (gameMode == EditPosition) EditPositionDone(TRUE);
16946     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16947     if(feature == NULL || *feature) SendToProgram(buf, &first);
16948     if (gameMode == TwoMachinesPlay) {
16949         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16950     }
16951 }
16952
16953 void
16954 ShowThinkingEvent ()
16955 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16956 {
16957     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16958     int newState = appData.showThinking
16959         // [HGM] thinking: other features now need thinking output as well
16960         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16961
16962     if (oldState == newState) return;
16963     oldState = newState;
16964     if (gameMode == EditPosition) EditPositionDone(TRUE);
16965     if (oldState) {
16966         SendToProgram("post\n", &first);
16967         if (gameMode == TwoMachinesPlay) {
16968             SendToProgram("post\n", &second);
16969         }
16970     } else {
16971         SendToProgram("nopost\n", &first);
16972         thinkOutput[0] = NULLCHAR;
16973         if (gameMode == TwoMachinesPlay) {
16974             SendToProgram("nopost\n", &second);
16975         }
16976     }
16977 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16978 }
16979
16980 void
16981 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16982 {
16983   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16984   if (pr == NoProc) return;
16985   AskQuestion(title, question, replyPrefix, pr);
16986 }
16987
16988 void
16989 TypeInEvent (char firstChar)
16990 {
16991     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16992         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16993         gameMode == AnalyzeMode || gameMode == EditGame ||
16994         gameMode == EditPosition || gameMode == IcsExamining ||
16995         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16996         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16997                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16998                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16999         gameMode == Training) PopUpMoveDialog(firstChar);
17000 }
17001
17002 void
17003 TypeInDoneEvent (char *move)
17004 {
17005         Board board;
17006         int n, fromX, fromY, toX, toY;
17007         char promoChar;
17008         ChessMove moveType;
17009
17010         // [HGM] FENedit
17011         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17012                 EditPositionPasteFEN(move);
17013                 return;
17014         }
17015         // [HGM] movenum: allow move number to be typed in any mode
17016         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17017           ToNrEvent(2*n-1);
17018           return;
17019         }
17020         // undocumented kludge: allow command-line option to be typed in!
17021         // (potentially fatal, and does not implement the effect of the option.)
17022         // should only be used for options that are values on which future decisions will be made,
17023         // and definitely not on options that would be used during initialization.
17024         if(strstr(move, "!!! -") == move) {
17025             ParseArgsFromString(move+4);
17026             return;
17027         }
17028
17029       if (gameMode != EditGame && currentMove != forwardMostMove &&
17030         gameMode != Training) {
17031         DisplayMoveError(_("Displayed move is not current"));
17032       } else {
17033         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17034           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17035         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17036         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17037           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17038           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17039         } else {
17040           DisplayMoveError(_("Could not parse move"));
17041         }
17042       }
17043 }
17044
17045 void
17046 DisplayMove (int moveNumber)
17047 {
17048     char message[MSG_SIZ];
17049     char res[MSG_SIZ];
17050     char cpThinkOutput[MSG_SIZ];
17051
17052     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17053
17054     if (moveNumber == forwardMostMove - 1 ||
17055         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17056
17057         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17058
17059         if (strchr(cpThinkOutput, '\n')) {
17060             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17061         }
17062     } else {
17063         *cpThinkOutput = NULLCHAR;
17064     }
17065
17066     /* [AS] Hide thinking from human user */
17067     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17068         *cpThinkOutput = NULLCHAR;
17069         if( thinkOutput[0] != NULLCHAR ) {
17070             int i;
17071
17072             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17073                 cpThinkOutput[i] = '.';
17074             }
17075             cpThinkOutput[i] = NULLCHAR;
17076             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17077         }
17078     }
17079
17080     if (moveNumber == forwardMostMove - 1 &&
17081         gameInfo.resultDetails != NULL) {
17082         if (gameInfo.resultDetails[0] == NULLCHAR) {
17083           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17084         } else {
17085           snprintf(res, MSG_SIZ, " {%s} %s",
17086                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17087         }
17088     } else {
17089         res[0] = NULLCHAR;
17090     }
17091
17092     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17093         DisplayMessage(res, cpThinkOutput);
17094     } else {
17095       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17096                 WhiteOnMove(moveNumber) ? " " : ".. ",
17097                 parseList[moveNumber], res);
17098         DisplayMessage(message, cpThinkOutput);
17099     }
17100 }
17101
17102 void
17103 DisplayComment (int moveNumber, char *text)
17104 {
17105     char title[MSG_SIZ];
17106
17107     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17108       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17109     } else {
17110       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17111               WhiteOnMove(moveNumber) ? " " : ".. ",
17112               parseList[moveNumber]);
17113     }
17114     if (text != NULL && (appData.autoDisplayComment || commentUp))
17115         CommentPopUp(title, text);
17116 }
17117
17118 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17119  * might be busy thinking or pondering.  It can be omitted if your
17120  * gnuchess is configured to stop thinking immediately on any user
17121  * input.  However, that gnuchess feature depends on the FIONREAD
17122  * ioctl, which does not work properly on some flavors of Unix.
17123  */
17124 void
17125 Attention (ChessProgramState *cps)
17126 {
17127 #if ATTENTION
17128     if (!cps->useSigint) return;
17129     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17130     switch (gameMode) {
17131       case MachinePlaysWhite:
17132       case MachinePlaysBlack:
17133       case TwoMachinesPlay:
17134       case IcsPlayingWhite:
17135       case IcsPlayingBlack:
17136       case AnalyzeMode:
17137       case AnalyzeFile:
17138         /* Skip if we know it isn't thinking */
17139         if (!cps->maybeThinking) return;
17140         if (appData.debugMode)
17141           fprintf(debugFP, "Interrupting %s\n", cps->which);
17142         InterruptChildProcess(cps->pr);
17143         cps->maybeThinking = FALSE;
17144         break;
17145       default:
17146         break;
17147     }
17148 #endif /*ATTENTION*/
17149 }
17150
17151 int
17152 CheckFlags ()
17153 {
17154     if (whiteTimeRemaining <= 0) {
17155         if (!whiteFlag) {
17156             whiteFlag = TRUE;
17157             if (appData.icsActive) {
17158                 if (appData.autoCallFlag &&
17159                     gameMode == IcsPlayingBlack && !blackFlag) {
17160                   SendToICS(ics_prefix);
17161                   SendToICS("flag\n");
17162                 }
17163             } else {
17164                 if (blackFlag) {
17165                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17166                 } else {
17167                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17168                     if (appData.autoCallFlag) {
17169                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17170                         return TRUE;
17171                     }
17172                 }
17173             }
17174         }
17175     }
17176     if (blackTimeRemaining <= 0) {
17177         if (!blackFlag) {
17178             blackFlag = TRUE;
17179             if (appData.icsActive) {
17180                 if (appData.autoCallFlag &&
17181                     gameMode == IcsPlayingWhite && !whiteFlag) {
17182                   SendToICS(ics_prefix);
17183                   SendToICS("flag\n");
17184                 }
17185             } else {
17186                 if (whiteFlag) {
17187                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17188                 } else {
17189                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17190                     if (appData.autoCallFlag) {
17191                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17192                         return TRUE;
17193                     }
17194                 }
17195             }
17196         }
17197     }
17198     return FALSE;
17199 }
17200
17201 void
17202 CheckTimeControl ()
17203 {
17204     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17205         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17206
17207     /*
17208      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17209      */
17210     if ( !WhiteOnMove(forwardMostMove) ) {
17211         /* White made time control */
17212         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17213         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17214         /* [HGM] time odds: correct new time quota for time odds! */
17215                                             / WhitePlayer()->timeOdds;
17216         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17217     } else {
17218         lastBlack -= blackTimeRemaining;
17219         /* Black made time control */
17220         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17221                                             / WhitePlayer()->other->timeOdds;
17222         lastWhite = whiteTimeRemaining;
17223     }
17224 }
17225
17226 void
17227 DisplayBothClocks ()
17228 {
17229     int wom = gameMode == EditPosition ?
17230       !blackPlaysFirst : WhiteOnMove(currentMove);
17231     DisplayWhiteClock(whiteTimeRemaining, wom);
17232     DisplayBlackClock(blackTimeRemaining, !wom);
17233 }
17234
17235
17236 /* Timekeeping seems to be a portability nightmare.  I think everyone
17237    has ftime(), but I'm really not sure, so I'm including some ifdefs
17238    to use other calls if you don't.  Clocks will be less accurate if
17239    you have neither ftime nor gettimeofday.
17240 */
17241
17242 /* VS 2008 requires the #include outside of the function */
17243 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17244 #include <sys/timeb.h>
17245 #endif
17246
17247 /* Get the current time as a TimeMark */
17248 void
17249 GetTimeMark (TimeMark *tm)
17250 {
17251 #if HAVE_GETTIMEOFDAY
17252
17253     struct timeval timeVal;
17254     struct timezone timeZone;
17255
17256     gettimeofday(&timeVal, &timeZone);
17257     tm->sec = (long) timeVal.tv_sec;
17258     tm->ms = (int) (timeVal.tv_usec / 1000L);
17259
17260 #else /*!HAVE_GETTIMEOFDAY*/
17261 #if HAVE_FTIME
17262
17263 // include <sys/timeb.h> / moved to just above start of function
17264     struct timeb timeB;
17265
17266     ftime(&timeB);
17267     tm->sec = (long) timeB.time;
17268     tm->ms = (int) timeB.millitm;
17269
17270 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17271     tm->sec = (long) time(NULL);
17272     tm->ms = 0;
17273 #endif
17274 #endif
17275 }
17276
17277 /* Return the difference in milliseconds between two
17278    time marks.  We assume the difference will fit in a long!
17279 */
17280 long
17281 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17282 {
17283     return 1000L*(tm2->sec - tm1->sec) +
17284            (long) (tm2->ms - tm1->ms);
17285 }
17286
17287
17288 /*
17289  * Code to manage the game clocks.
17290  *
17291  * In tournament play, black starts the clock and then white makes a move.
17292  * We give the human user a slight advantage if he is playing white---the
17293  * clocks don't run until he makes his first move, so it takes zero time.
17294  * Also, we don't account for network lag, so we could get out of sync
17295  * with GNU Chess's clock -- but then, referees are always right.
17296  */
17297
17298 static TimeMark tickStartTM;
17299 static long intendedTickLength;
17300
17301 long
17302 NextTickLength (long timeRemaining)
17303 {
17304     long nominalTickLength, nextTickLength;
17305
17306     if (timeRemaining > 0L && timeRemaining <= 10000L)
17307       nominalTickLength = 100L;
17308     else
17309       nominalTickLength = 1000L;
17310     nextTickLength = timeRemaining % nominalTickLength;
17311     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17312
17313     return nextTickLength;
17314 }
17315
17316 /* Adjust clock one minute up or down */
17317 void
17318 AdjustClock (Boolean which, int dir)
17319 {
17320     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17321     if(which) blackTimeRemaining += 60000*dir;
17322     else      whiteTimeRemaining += 60000*dir;
17323     DisplayBothClocks();
17324     adjustedClock = TRUE;
17325 }
17326
17327 /* Stop clocks and reset to a fresh time control */
17328 void
17329 ResetClocks ()
17330 {
17331     (void) StopClockTimer();
17332     if (appData.icsActive) {
17333         whiteTimeRemaining = blackTimeRemaining = 0;
17334     } else if (searchTime) {
17335         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17336         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17337     } else { /* [HGM] correct new time quote for time odds */
17338         whiteTC = blackTC = fullTimeControlString;
17339         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17340         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17341     }
17342     if (whiteFlag || blackFlag) {
17343         DisplayTitle("");
17344         whiteFlag = blackFlag = FALSE;
17345     }
17346     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17347     DisplayBothClocks();
17348     adjustedClock = FALSE;
17349 }
17350
17351 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17352
17353 /* Decrement running clock by amount of time that has passed */
17354 void
17355 DecrementClocks ()
17356 {
17357     long timeRemaining;
17358     long lastTickLength, fudge;
17359     TimeMark now;
17360
17361     if (!appData.clockMode) return;
17362     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17363
17364     GetTimeMark(&now);
17365
17366     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17367
17368     /* Fudge if we woke up a little too soon */
17369     fudge = intendedTickLength - lastTickLength;
17370     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17371
17372     if (WhiteOnMove(forwardMostMove)) {
17373         if(whiteNPS >= 0) lastTickLength = 0;
17374         timeRemaining = whiteTimeRemaining -= lastTickLength;
17375         if(timeRemaining < 0 && !appData.icsActive) {
17376             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17377             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17378                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17379                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17380             }
17381         }
17382         DisplayWhiteClock(whiteTimeRemaining - fudge,
17383                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17384     } else {
17385         if(blackNPS >= 0) lastTickLength = 0;
17386         timeRemaining = blackTimeRemaining -= lastTickLength;
17387         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17388             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17389             if(suddenDeath) {
17390                 blackStartMove = forwardMostMove;
17391                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17392             }
17393         }
17394         DisplayBlackClock(blackTimeRemaining - fudge,
17395                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17396     }
17397     if (CheckFlags()) return;
17398
17399     if(twoBoards) { // count down secondary board's clocks as well
17400         activePartnerTime -= lastTickLength;
17401         partnerUp = 1;
17402         if(activePartner == 'W')
17403             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17404         else
17405             DisplayBlackClock(activePartnerTime, TRUE);
17406         partnerUp = 0;
17407     }
17408
17409     tickStartTM = now;
17410     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17411     StartClockTimer(intendedTickLength);
17412
17413     /* if the time remaining has fallen below the alarm threshold, sound the
17414      * alarm. if the alarm has sounded and (due to a takeback or time control
17415      * with increment) the time remaining has increased to a level above the
17416      * threshold, reset the alarm so it can sound again.
17417      */
17418
17419     if (appData.icsActive && appData.icsAlarm) {
17420
17421         /* make sure we are dealing with the user's clock */
17422         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17423                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17424            )) return;
17425
17426         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17427             alarmSounded = FALSE;
17428         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17429             PlayAlarmSound();
17430             alarmSounded = TRUE;
17431         }
17432     }
17433 }
17434
17435
17436 /* A player has just moved, so stop the previously running
17437    clock and (if in clock mode) start the other one.
17438    We redisplay both clocks in case we're in ICS mode, because
17439    ICS gives us an update to both clocks after every move.
17440    Note that this routine is called *after* forwardMostMove
17441    is updated, so the last fractional tick must be subtracted
17442    from the color that is *not* on move now.
17443 */
17444 void
17445 SwitchClocks (int newMoveNr)
17446 {
17447     long lastTickLength;
17448     TimeMark now;
17449     int flagged = FALSE;
17450
17451     GetTimeMark(&now);
17452
17453     if (StopClockTimer() && appData.clockMode) {
17454         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17455         if (!WhiteOnMove(forwardMostMove)) {
17456             if(blackNPS >= 0) lastTickLength = 0;
17457             blackTimeRemaining -= lastTickLength;
17458            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17459 //         if(pvInfoList[forwardMostMove].time == -1)
17460                  pvInfoList[forwardMostMove].time =               // use GUI time
17461                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17462         } else {
17463            if(whiteNPS >= 0) lastTickLength = 0;
17464            whiteTimeRemaining -= lastTickLength;
17465            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17466 //         if(pvInfoList[forwardMostMove].time == -1)
17467                  pvInfoList[forwardMostMove].time =
17468                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17469         }
17470         flagged = CheckFlags();
17471     }
17472     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17473     CheckTimeControl();
17474
17475     if (flagged || !appData.clockMode) return;
17476
17477     switch (gameMode) {
17478       case MachinePlaysBlack:
17479       case MachinePlaysWhite:
17480       case BeginningOfGame:
17481         if (pausing) return;
17482         break;
17483
17484       case EditGame:
17485       case PlayFromGameFile:
17486       case IcsExamining:
17487         return;
17488
17489       default:
17490         break;
17491     }
17492
17493     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17494         if(WhiteOnMove(forwardMostMove))
17495              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17496         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17497     }
17498
17499     tickStartTM = now;
17500     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17501       whiteTimeRemaining : blackTimeRemaining);
17502     StartClockTimer(intendedTickLength);
17503 }
17504
17505
17506 /* Stop both clocks */
17507 void
17508 StopClocks ()
17509 {
17510     long lastTickLength;
17511     TimeMark now;
17512
17513     if (!StopClockTimer()) return;
17514     if (!appData.clockMode) return;
17515
17516     GetTimeMark(&now);
17517
17518     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17519     if (WhiteOnMove(forwardMostMove)) {
17520         if(whiteNPS >= 0) lastTickLength = 0;
17521         whiteTimeRemaining -= lastTickLength;
17522         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17523     } else {
17524         if(blackNPS >= 0) lastTickLength = 0;
17525         blackTimeRemaining -= lastTickLength;
17526         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17527     }
17528     CheckFlags();
17529 }
17530
17531 /* Start clock of player on move.  Time may have been reset, so
17532    if clock is already running, stop and restart it. */
17533 void
17534 StartClocks ()
17535 {
17536     (void) StopClockTimer(); /* in case it was running already */
17537     DisplayBothClocks();
17538     if (CheckFlags()) return;
17539
17540     if (!appData.clockMode) return;
17541     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17542
17543     GetTimeMark(&tickStartTM);
17544     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17545       whiteTimeRemaining : blackTimeRemaining);
17546
17547    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17548     whiteNPS = blackNPS = -1;
17549     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17550        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17551         whiteNPS = first.nps;
17552     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17553        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17554         blackNPS = first.nps;
17555     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17556         whiteNPS = second.nps;
17557     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17558         blackNPS = second.nps;
17559     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17560
17561     StartClockTimer(intendedTickLength);
17562 }
17563
17564 char *
17565 TimeString (long ms)
17566 {
17567     long second, minute, hour, day;
17568     char *sign = "";
17569     static char buf[32];
17570
17571     if (ms > 0 && ms <= 9900) {
17572       /* convert milliseconds to tenths, rounding up */
17573       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17574
17575       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17576       return buf;
17577     }
17578
17579     /* convert milliseconds to seconds, rounding up */
17580     /* use floating point to avoid strangeness of integer division
17581        with negative dividends on many machines */
17582     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17583
17584     if (second < 0) {
17585         sign = "-";
17586         second = -second;
17587     }
17588
17589     day = second / (60 * 60 * 24);
17590     second = second % (60 * 60 * 24);
17591     hour = second / (60 * 60);
17592     second = second % (60 * 60);
17593     minute = second / 60;
17594     second = second % 60;
17595
17596     if (day > 0)
17597       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17598               sign, day, hour, minute, second);
17599     else if (hour > 0)
17600       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17601     else
17602       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17603
17604     return buf;
17605 }
17606
17607
17608 /*
17609  * This is necessary because some C libraries aren't ANSI C compliant yet.
17610  */
17611 char *
17612 StrStr (char *string, char *match)
17613 {
17614     int i, length;
17615
17616     length = strlen(match);
17617
17618     for (i = strlen(string) - length; i >= 0; i--, string++)
17619       if (!strncmp(match, string, length))
17620         return string;
17621
17622     return NULL;
17623 }
17624
17625 char *
17626 StrCaseStr (char *string, char *match)
17627 {
17628     int i, j, length;
17629
17630     length = strlen(match);
17631
17632     for (i = strlen(string) - length; i >= 0; i--, string++) {
17633         for (j = 0; j < length; j++) {
17634             if (ToLower(match[j]) != ToLower(string[j]))
17635               break;
17636         }
17637         if (j == length) return string;
17638     }
17639
17640     return NULL;
17641 }
17642
17643 #ifndef _amigados
17644 int
17645 StrCaseCmp (char *s1, char *s2)
17646 {
17647     char c1, c2;
17648
17649     for (;;) {
17650         c1 = ToLower(*s1++);
17651         c2 = ToLower(*s2++);
17652         if (c1 > c2) return 1;
17653         if (c1 < c2) return -1;
17654         if (c1 == NULLCHAR) return 0;
17655     }
17656 }
17657
17658
17659 int
17660 ToLower (int c)
17661 {
17662     return isupper(c) ? tolower(c) : c;
17663 }
17664
17665
17666 int
17667 ToUpper (int c)
17668 {
17669     return islower(c) ? toupper(c) : c;
17670 }
17671 #endif /* !_amigados    */
17672
17673 char *
17674 StrSave (char *s)
17675 {
17676   char *ret;
17677
17678   if ((ret = (char *) malloc(strlen(s) + 1)))
17679     {
17680       safeStrCpy(ret, s, strlen(s)+1);
17681     }
17682   return ret;
17683 }
17684
17685 char *
17686 StrSavePtr (char *s, char **savePtr)
17687 {
17688     if (*savePtr) {
17689         free(*savePtr);
17690     }
17691     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17692       safeStrCpy(*savePtr, s, strlen(s)+1);
17693     }
17694     return(*savePtr);
17695 }
17696
17697 char *
17698 PGNDate ()
17699 {
17700     time_t clock;
17701     struct tm *tm;
17702     char buf[MSG_SIZ];
17703
17704     clock = time((time_t *)NULL);
17705     tm = localtime(&clock);
17706     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17707             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17708     return StrSave(buf);
17709 }
17710
17711
17712 char *
17713 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17714 {
17715     int i, j, fromX, fromY, toX, toY;
17716     int whiteToPlay;
17717     char buf[MSG_SIZ];
17718     char *p, *q;
17719     int emptycount;
17720     ChessSquare piece;
17721
17722     whiteToPlay = (gameMode == EditPosition) ?
17723       !blackPlaysFirst : (move % 2 == 0);
17724     p = buf;
17725
17726     /* Piece placement data */
17727     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17728         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17729         emptycount = 0;
17730         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17731             if (boards[move][i][j] == EmptySquare) {
17732                 emptycount++;
17733             } else { ChessSquare piece = boards[move][i][j];
17734                 if (emptycount > 0) {
17735                     if(emptycount<10) /* [HGM] can be >= 10 */
17736                         *p++ = '0' + emptycount;
17737                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17738                     emptycount = 0;
17739                 }
17740                 if(PieceToChar(piece) == '+') {
17741                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17742                     *p++ = '+';
17743                     piece = (ChessSquare)(CHUDEMOTED piece);
17744                 }
17745                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17746                 if(p[-1] == '~') {
17747                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17748                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17749                     *p++ = '~';
17750                 }
17751             }
17752         }
17753         if (emptycount > 0) {
17754             if(emptycount<10) /* [HGM] can be >= 10 */
17755                 *p++ = '0' + emptycount;
17756             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17757             emptycount = 0;
17758         }
17759         *p++ = '/';
17760     }
17761     *(p - 1) = ' ';
17762
17763     /* [HGM] print Crazyhouse or Shogi holdings */
17764     if( gameInfo.holdingsWidth ) {
17765         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17766         q = p;
17767         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17768             piece = boards[move][i][BOARD_WIDTH-1];
17769             if( piece != EmptySquare )
17770               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17771                   *p++ = PieceToChar(piece);
17772         }
17773         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17774             piece = boards[move][BOARD_HEIGHT-i-1][0];
17775             if( piece != EmptySquare )
17776               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17777                   *p++ = PieceToChar(piece);
17778         }
17779
17780         if( q == p ) *p++ = '-';
17781         *p++ = ']';
17782         *p++ = ' ';
17783     }
17784
17785     /* Active color */
17786     *p++ = whiteToPlay ? 'w' : 'b';
17787     *p++ = ' ';
17788
17789   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17790     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17791   } else {
17792   if(nrCastlingRights) {
17793      q = p;
17794      if(appData.fischerCastling) {
17795        /* [HGM] write directly from rights */
17796            if(boards[move][CASTLING][2] != NoRights &&
17797               boards[move][CASTLING][0] != NoRights   )
17798                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17799            if(boards[move][CASTLING][2] != NoRights &&
17800               boards[move][CASTLING][1] != NoRights   )
17801                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17802            if(boards[move][CASTLING][5] != NoRights &&
17803               boards[move][CASTLING][3] != NoRights   )
17804                 *p++ = boards[move][CASTLING][3] + AAA;
17805            if(boards[move][CASTLING][5] != NoRights &&
17806               boards[move][CASTLING][4] != NoRights   )
17807                 *p++ = boards[move][CASTLING][4] + AAA;
17808      } else {
17809
17810         /* [HGM] write true castling rights */
17811         if( nrCastlingRights == 6 ) {
17812             int q, k=0;
17813             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17814                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17815             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17816                  boards[move][CASTLING][2] != NoRights  );
17817             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17818                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17819                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17820                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17821                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17822             }
17823             if(q) *p++ = 'Q';
17824             k = 0;
17825             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17826                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17827             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17828                  boards[move][CASTLING][5] != NoRights  );
17829             if(gameInfo.variant == VariantSChess) {
17830                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17831                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17832                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17833                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17834             }
17835             if(q) *p++ = 'q';
17836         }
17837      }
17838      if (q == p) *p++ = '-'; /* No castling rights */
17839      *p++ = ' ';
17840   }
17841
17842   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17843      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17844      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17845     /* En passant target square */
17846     if (move > backwardMostMove) {
17847         fromX = moveList[move - 1][0] - AAA;
17848         fromY = moveList[move - 1][1] - ONE;
17849         toX = moveList[move - 1][2] - AAA;
17850         toY = moveList[move - 1][3] - ONE;
17851         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17852             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17853             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17854             fromX == toX) {
17855             /* 2-square pawn move just happened */
17856             *p++ = toX + AAA;
17857             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17858         } else {
17859             *p++ = '-';
17860         }
17861     } else if(move == backwardMostMove) {
17862         // [HGM] perhaps we should always do it like this, and forget the above?
17863         if((signed char)boards[move][EP_STATUS] >= 0) {
17864             *p++ = boards[move][EP_STATUS] + AAA;
17865             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17866         } else {
17867             *p++ = '-';
17868         }
17869     } else {
17870         *p++ = '-';
17871     }
17872     *p++ = ' ';
17873   }
17874   }
17875
17876     if(moveCounts)
17877     {   int i = 0, j=move;
17878
17879         /* [HGM] find reversible plies */
17880         if (appData.debugMode) { int k;
17881             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17882             for(k=backwardMostMove; k<=forwardMostMove; k++)
17883                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17884
17885         }
17886
17887         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17888         if( j == backwardMostMove ) i += initialRulePlies;
17889         sprintf(p, "%d ", i);
17890         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17891
17892         /* Fullmove number */
17893         sprintf(p, "%d", (move / 2) + 1);
17894     } else *--p = NULLCHAR;
17895
17896     return StrSave(buf);
17897 }
17898
17899 Boolean
17900 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17901 {
17902     int i, j, k, w=0, subst=0, shuffle=0;
17903     char *p, c;
17904     int emptycount, virgin[BOARD_FILES];
17905     ChessSquare piece;
17906
17907     p = fen;
17908
17909     /* Piece placement data */
17910     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17911         j = 0;
17912         for (;;) {
17913             if (*p == '/' || *p == ' ' || *p == '[' ) {
17914                 if(j > w) w = j;
17915                 emptycount = gameInfo.boardWidth - j;
17916                 while (emptycount--)
17917                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17918                 if (*p == '/') p++;
17919                 else if(autoSize) { // we stumbled unexpectedly into end of board
17920                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17921                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17922                     }
17923                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17924                 }
17925                 break;
17926 #if(BOARD_FILES >= 10)*0
17927             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17928                 p++; emptycount=10;
17929                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17930                 while (emptycount--)
17931                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17932 #endif
17933             } else if (*p == '*') {
17934                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17935             } else if (isdigit(*p)) {
17936                 emptycount = *p++ - '0';
17937                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17938                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17939                 while (emptycount--)
17940                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17941             } else if (*p == '<') {
17942                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17943                 else if (i != 0 || !shuffle) return FALSE;
17944                 p++;
17945             } else if (shuffle && *p == '>') {
17946                 p++; // for now ignore closing shuffle range, and assume rank-end
17947             } else if (*p == '?') {
17948                 if (j >= gameInfo.boardWidth) return FALSE;
17949                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17950                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17951             } else if (*p == '+' || isalpha(*p)) {
17952                 if (j >= gameInfo.boardWidth) return FALSE;
17953                 if(*p=='+') {
17954                     piece = CharToPiece(*++p);
17955                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17956                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17957                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17958                 } else piece = CharToPiece(*p++);
17959
17960                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17961                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17962                     piece = (ChessSquare) (PROMOTED piece);
17963                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17964                     p++;
17965                 }
17966                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17967             } else {
17968                 return FALSE;
17969             }
17970         }
17971     }
17972     while (*p == '/' || *p == ' ') p++;
17973
17974     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17975
17976     /* [HGM] by default clear Crazyhouse holdings, if present */
17977     if(gameInfo.holdingsWidth) {
17978        for(i=0; i<BOARD_HEIGHT; i++) {
17979            board[i][0]             = EmptySquare; /* black holdings */
17980            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17981            board[i][1]             = (ChessSquare) 0; /* black counts */
17982            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17983        }
17984     }
17985
17986     /* [HGM] look for Crazyhouse holdings here */
17987     while(*p==' ') p++;
17988     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17989         int swap=0, wcnt=0, bcnt=0;
17990         if(*p == '[') p++;
17991         if(*p == '<') swap++, p++;
17992         if(*p == '-' ) p++; /* empty holdings */ else {
17993             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17994             /* if we would allow FEN reading to set board size, we would   */
17995             /* have to add holdings and shift the board read so far here   */
17996             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17997                 p++;
17998                 if((int) piece >= (int) BlackPawn ) {
17999                     i = (int)piece - (int)BlackPawn;
18000                     i = PieceToNumber((ChessSquare)i);
18001                     if( i >= gameInfo.holdingsSize ) return FALSE;
18002                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18003                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18004                     bcnt++;
18005                 } else {
18006                     i = (int)piece - (int)WhitePawn;
18007                     i = PieceToNumber((ChessSquare)i);
18008                     if( i >= gameInfo.holdingsSize ) return FALSE;
18009                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18010                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18011                     wcnt++;
18012                 }
18013             }
18014             if(subst) { // substitute back-rank question marks by holdings pieces
18015                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18016                     int k, m, n = bcnt + 1;
18017                     if(board[0][j] == ClearBoard) {
18018                         if(!wcnt) return FALSE;
18019                         n = rand() % wcnt;
18020                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18021                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18022                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18023                             break;
18024                         }
18025                     }
18026                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18027                         if(!bcnt) return FALSE;
18028                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18029                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18030                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18031                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18032                             break;
18033                         }
18034                     }
18035                 }
18036                 subst = 0;
18037             }
18038         }
18039         if(*p == ']') p++;
18040     }
18041
18042     if(subst) return FALSE; // substitution requested, but no holdings
18043
18044     while(*p == ' ') p++;
18045
18046     /* Active color */
18047     c = *p++;
18048     if(appData.colorNickNames) {
18049       if( c == appData.colorNickNames[0] ) c = 'w'; else
18050       if( c == appData.colorNickNames[1] ) c = 'b';
18051     }
18052     switch (c) {
18053       case 'w':
18054         *blackPlaysFirst = FALSE;
18055         break;
18056       case 'b':
18057         *blackPlaysFirst = TRUE;
18058         break;
18059       default:
18060         return FALSE;
18061     }
18062
18063     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18064     /* return the extra info in global variiables             */
18065
18066     /* set defaults in case FEN is incomplete */
18067     board[EP_STATUS] = EP_UNKNOWN;
18068     for(i=0; i<nrCastlingRights; i++ ) {
18069         board[CASTLING][i] =
18070             appData.fischerCastling ? NoRights : initialRights[i];
18071     }   /* assume possible unless obviously impossible */
18072     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18073     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18074     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18075                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18076     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18077     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18078     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18079                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18080     FENrulePlies = 0;
18081
18082     while(*p==' ') p++;
18083     if(nrCastlingRights) {
18084       int fischer = 0;
18085       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18086       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18087           /* castling indicator present, so default becomes no castlings */
18088           for(i=0; i<nrCastlingRights; i++ ) {
18089                  board[CASTLING][i] = NoRights;
18090           }
18091       }
18092       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18093              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18094              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18095              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18096         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18097
18098         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18099             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18100             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18101         }
18102         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18103             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18104         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18105                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18106         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18107                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18108         switch(c) {
18109           case'K':
18110               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18111               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18112               board[CASTLING][2] = whiteKingFile;
18113               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18114               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18115               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18116               break;
18117           case'Q':
18118               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18119               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18120               board[CASTLING][2] = whiteKingFile;
18121               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18122               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18123               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18124               break;
18125           case'k':
18126               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18127               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18128               board[CASTLING][5] = blackKingFile;
18129               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18130               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18131               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18132               break;
18133           case'q':
18134               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18135               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18136               board[CASTLING][5] = blackKingFile;
18137               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18138               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18139               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18140           case '-':
18141               break;
18142           default: /* FRC castlings */
18143               if(c >= 'a') { /* black rights */
18144                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18145                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18146                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18147                   if(i == BOARD_RGHT) break;
18148                   board[CASTLING][5] = i;
18149                   c -= AAA;
18150                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18151                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18152                   if(c > i)
18153                       board[CASTLING][3] = c;
18154                   else
18155                       board[CASTLING][4] = c;
18156               } else { /* white rights */
18157                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18158                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18159                     if(board[0][i] == WhiteKing) break;
18160                   if(i == BOARD_RGHT) break;
18161                   board[CASTLING][2] = i;
18162                   c -= AAA - 'a' + 'A';
18163                   if(board[0][c] >= WhiteKing) break;
18164                   if(c > i)
18165                       board[CASTLING][0] = c;
18166                   else
18167                       board[CASTLING][1] = c;
18168               }
18169         }
18170       }
18171       for(i=0; i<nrCastlingRights; i++)
18172         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18173       if(gameInfo.variant == VariantSChess)
18174         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18175       if(fischer && shuffle) appData.fischerCastling = TRUE;
18176     if (appData.debugMode) {
18177         fprintf(debugFP, "FEN castling rights:");
18178         for(i=0; i<nrCastlingRights; i++)
18179         fprintf(debugFP, " %d", board[CASTLING][i]);
18180         fprintf(debugFP, "\n");
18181     }
18182
18183       while(*p==' ') p++;
18184     }
18185
18186     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18187
18188     /* read e.p. field in games that know e.p. capture */
18189     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18190        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18191        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18192       if(*p=='-') {
18193         p++; board[EP_STATUS] = EP_NONE;
18194       } else {
18195          char c = *p++ - AAA;
18196
18197          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18198          if(*p >= '0' && *p <='9') p++;
18199          board[EP_STATUS] = c;
18200       }
18201     }
18202
18203
18204     if(sscanf(p, "%d", &i) == 1) {
18205         FENrulePlies = i; /* 50-move ply counter */
18206         /* (The move number is still ignored)    */
18207     }
18208
18209     return TRUE;
18210 }
18211
18212 void
18213 EditPositionPasteFEN (char *fen)
18214 {
18215   if (fen != NULL) {
18216     Board initial_position;
18217
18218     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18219       DisplayError(_("Bad FEN position in clipboard"), 0);
18220       return ;
18221     } else {
18222       int savedBlackPlaysFirst = blackPlaysFirst;
18223       EditPositionEvent();
18224       blackPlaysFirst = savedBlackPlaysFirst;
18225       CopyBoard(boards[0], initial_position);
18226       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18227       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18228       DisplayBothClocks();
18229       DrawPosition(FALSE, boards[currentMove]);
18230     }
18231   }
18232 }
18233
18234 static char cseq[12] = "\\   ";
18235
18236 Boolean
18237 set_cont_sequence (char *new_seq)
18238 {
18239     int len;
18240     Boolean ret;
18241
18242     // handle bad attempts to set the sequence
18243         if (!new_seq)
18244                 return 0; // acceptable error - no debug
18245
18246     len = strlen(new_seq);
18247     ret = (len > 0) && (len < sizeof(cseq));
18248     if (ret)
18249       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18250     else if (appData.debugMode)
18251       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18252     return ret;
18253 }
18254
18255 /*
18256     reformat a source message so words don't cross the width boundary.  internal
18257     newlines are not removed.  returns the wrapped size (no null character unless
18258     included in source message).  If dest is NULL, only calculate the size required
18259     for the dest buffer.  lp argument indicats line position upon entry, and it's
18260     passed back upon exit.
18261 */
18262 int
18263 wrap (char *dest, char *src, int count, int width, int *lp)
18264 {
18265     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18266
18267     cseq_len = strlen(cseq);
18268     old_line = line = *lp;
18269     ansi = len = clen = 0;
18270
18271     for (i=0; i < count; i++)
18272     {
18273         if (src[i] == '\033')
18274             ansi = 1;
18275
18276         // if we hit the width, back up
18277         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18278         {
18279             // store i & len in case the word is too long
18280             old_i = i, old_len = len;
18281
18282             // find the end of the last word
18283             while (i && src[i] != ' ' && src[i] != '\n')
18284             {
18285                 i--;
18286                 len--;
18287             }
18288
18289             // word too long?  restore i & len before splitting it
18290             if ((old_i-i+clen) >= width)
18291             {
18292                 i = old_i;
18293                 len = old_len;
18294             }
18295
18296             // extra space?
18297             if (i && src[i-1] == ' ')
18298                 len--;
18299
18300             if (src[i] != ' ' && src[i] != '\n')
18301             {
18302                 i--;
18303                 if (len)
18304                     len--;
18305             }
18306
18307             // now append the newline and continuation sequence
18308             if (dest)
18309                 dest[len] = '\n';
18310             len++;
18311             if (dest)
18312                 strncpy(dest+len, cseq, cseq_len);
18313             len += cseq_len;
18314             line = cseq_len;
18315             clen = cseq_len;
18316             continue;
18317         }
18318
18319         if (dest)
18320             dest[len] = src[i];
18321         len++;
18322         if (!ansi)
18323             line++;
18324         if (src[i] == '\n')
18325             line = 0;
18326         if (src[i] == 'm')
18327             ansi = 0;
18328     }
18329     if (dest && appData.debugMode)
18330     {
18331         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18332             count, width, line, len, *lp);
18333         show_bytes(debugFP, src, count);
18334         fprintf(debugFP, "\ndest: ");
18335         show_bytes(debugFP, dest, len);
18336         fprintf(debugFP, "\n");
18337     }
18338     *lp = dest ? line : old_line;
18339
18340     return len;
18341 }
18342
18343 // [HGM] vari: routines for shelving variations
18344 Boolean modeRestore = FALSE;
18345
18346 void
18347 PushInner (int firstMove, int lastMove)
18348 {
18349         int i, j, nrMoves = lastMove - firstMove;
18350
18351         // push current tail of game on stack
18352         savedResult[storedGames] = gameInfo.result;
18353         savedDetails[storedGames] = gameInfo.resultDetails;
18354         gameInfo.resultDetails = NULL;
18355         savedFirst[storedGames] = firstMove;
18356         savedLast [storedGames] = lastMove;
18357         savedFramePtr[storedGames] = framePtr;
18358         framePtr -= nrMoves; // reserve space for the boards
18359         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18360             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18361             for(j=0; j<MOVE_LEN; j++)
18362                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18363             for(j=0; j<2*MOVE_LEN; j++)
18364                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18365             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18366             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18367             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18368             pvInfoList[firstMove+i-1].depth = 0;
18369             commentList[framePtr+i] = commentList[firstMove+i];
18370             commentList[firstMove+i] = NULL;
18371         }
18372
18373         storedGames++;
18374         forwardMostMove = firstMove; // truncate game so we can start variation
18375 }
18376
18377 void
18378 PushTail (int firstMove, int lastMove)
18379 {
18380         if(appData.icsActive) { // only in local mode
18381                 forwardMostMove = currentMove; // mimic old ICS behavior
18382                 return;
18383         }
18384         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18385
18386         PushInner(firstMove, lastMove);
18387         if(storedGames == 1) GreyRevert(FALSE);
18388         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18389 }
18390
18391 void
18392 PopInner (Boolean annotate)
18393 {
18394         int i, j, nrMoves;
18395         char buf[8000], moveBuf[20];
18396
18397         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18398         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18399         nrMoves = savedLast[storedGames] - currentMove;
18400         if(annotate) {
18401                 int cnt = 10;
18402                 if(!WhiteOnMove(currentMove))
18403                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18404                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18405                 for(i=currentMove; i<forwardMostMove; i++) {
18406                         if(WhiteOnMove(i))
18407                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18408                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18409                         strcat(buf, moveBuf);
18410                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18411                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18412                 }
18413                 strcat(buf, ")");
18414         }
18415         for(i=1; i<=nrMoves; i++) { // copy last variation back
18416             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18417             for(j=0; j<MOVE_LEN; j++)
18418                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18419             for(j=0; j<2*MOVE_LEN; j++)
18420                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18421             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18422             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18423             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18424             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18425             commentList[currentMove+i] = commentList[framePtr+i];
18426             commentList[framePtr+i] = NULL;
18427         }
18428         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18429         framePtr = savedFramePtr[storedGames];
18430         gameInfo.result = savedResult[storedGames];
18431         if(gameInfo.resultDetails != NULL) {
18432             free(gameInfo.resultDetails);
18433       }
18434         gameInfo.resultDetails = savedDetails[storedGames];
18435         forwardMostMove = currentMove + nrMoves;
18436 }
18437
18438 Boolean
18439 PopTail (Boolean annotate)
18440 {
18441         if(appData.icsActive) return FALSE; // only in local mode
18442         if(!storedGames) return FALSE; // sanity
18443         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18444
18445         PopInner(annotate);
18446         if(currentMove < forwardMostMove) ForwardEvent(); else
18447         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18448
18449         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18450         return TRUE;
18451 }
18452
18453 void
18454 CleanupTail ()
18455 {       // remove all shelved variations
18456         int i;
18457         for(i=0; i<storedGames; i++) {
18458             if(savedDetails[i])
18459                 free(savedDetails[i]);
18460             savedDetails[i] = NULL;
18461         }
18462         for(i=framePtr; i<MAX_MOVES; i++) {
18463                 if(commentList[i]) free(commentList[i]);
18464                 commentList[i] = NULL;
18465         }
18466         framePtr = MAX_MOVES-1;
18467         storedGames = 0;
18468 }
18469
18470 void
18471 LoadVariation (int index, char *text)
18472 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18473         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18474         int level = 0, move;
18475
18476         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18477         // first find outermost bracketing variation
18478         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18479             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18480                 if(*p == '{') wait = '}'; else
18481                 if(*p == '[') wait = ']'; else
18482                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18483                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18484             }
18485             if(*p == wait) wait = NULLCHAR; // closing ]} found
18486             p++;
18487         }
18488         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18489         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18490         end[1] = NULLCHAR; // clip off comment beyond variation
18491         ToNrEvent(currentMove-1);
18492         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18493         // kludge: use ParsePV() to append variation to game
18494         move = currentMove;
18495         ParsePV(start, TRUE, TRUE);
18496         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18497         ClearPremoveHighlights();
18498         CommentPopDown();
18499         ToNrEvent(currentMove+1);
18500 }
18501
18502 void
18503 LoadTheme ()
18504 {
18505     char *p, *q, buf[MSG_SIZ];
18506     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18507         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18508         ParseArgsFromString(buf);
18509         ActivateTheme(TRUE); // also redo colors
18510         return;
18511     }
18512     p = nickName;
18513     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18514     {
18515         int len;
18516         q = appData.themeNames;
18517         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18518       if(appData.useBitmaps) {
18519         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18520                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18521                 appData.liteBackTextureMode,
18522                 appData.darkBackTextureMode );
18523       } else {
18524         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18525                 Col2Text(2),   // lightSquareColor
18526                 Col2Text(3) ); // darkSquareColor
18527       }
18528       if(appData.useBorder) {
18529         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18530                 appData.border);
18531       } else {
18532         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18533       }
18534       if(appData.useFont) {
18535         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18536                 appData.renderPiecesWithFont,
18537                 appData.fontToPieceTable,
18538                 Col2Text(9),    // appData.fontBackColorWhite
18539                 Col2Text(10) ); // appData.fontForeColorBlack
18540       } else {
18541         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18542                 appData.pieceDirectory);
18543         if(!appData.pieceDirectory[0])
18544           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18545                 Col2Text(0),   // whitePieceColor
18546                 Col2Text(1) ); // blackPieceColor
18547       }
18548       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18549                 Col2Text(4),   // highlightSquareColor
18550                 Col2Text(5) ); // premoveHighlightColor
18551         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18552         if(insert != q) insert[-1] = NULLCHAR;
18553         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18554         if(q)   free(q);
18555     }
18556     ActivateTheme(FALSE);
18557 }