Merge branch 'v4.8.x' into master
[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 int border;       /* [HGM] width of board rim, needed to size seek graph  */
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.noChessProgram ? "" : 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 VariantNormal:     /* definitely works! */
1205         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1206           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1207           return;
1208         }
1209       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1210       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1211       case VariantGothic:     /* [HGM] should work */
1212       case VariantCapablanca: /* [HGM] should work */
1213       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1214       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1215       case VariantChu:        /* [HGM] experimental */
1216       case VariantKnightmate: /* [HGM] should work */
1217       case VariantCylinder:   /* [HGM] untested */
1218       case VariantFalcon:     /* [HGM] untested */
1219       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1220                                  offboard interposition not understood */
1221       case VariantWildCastle: /* pieces not automatically shuffled */
1222       case VariantNoCastle:   /* pieces not automatically shuffled */
1223       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1224       case VariantLosers:     /* should work except for win condition,
1225                                  and doesn't know captures are mandatory */
1226       case VariantSuicide:    /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantGiveaway:   /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantTwoKings:   /* should work */
1231       case VariantAtomic:     /* should work except for win condition */
1232       case Variant3Check:     /* should work except for win condition */
1233       case VariantShatranj:   /* should work except for all win conditions */
1234       case VariantMakruk:     /* should work except for draw countdown */
1235       case VariantASEAN :     /* should work except for draw countdown */
1236       case VariantBerolina:   /* might work if TestLegality is off */
1237       case VariantCapaRandom: /* should work */
1238       case VariantJanus:      /* should work */
1239       case VariantSuper:      /* experimental */
1240       case VariantGreat:      /* experimental, requires legality testing to be off */
1241       case VariantSChess:     /* S-Chess, should work */
1242       case VariantGrand:      /* should work */
1243       case VariantSpartan:    /* should work */
1244       case VariantLion:       /* should work */
1245       case VariantChuChess:   /* should work */
1246         break;
1247       }
1248     }
1249
1250 }
1251
1252 int
1253 NextIntegerFromString (char ** str, long * value)
1254 {
1255     int result = -1;
1256     char * s = *str;
1257
1258     while( *s == ' ' || *s == '\t' ) {
1259         s++;
1260     }
1261
1262     *value = 0;
1263
1264     if( *s >= '0' && *s <= '9' ) {
1265         while( *s >= '0' && *s <= '9' ) {
1266             *value = *value * 10 + (*s - '0');
1267             s++;
1268         }
1269
1270         result = 0;
1271     }
1272
1273     *str = s;
1274
1275     return result;
1276 }
1277
1278 int
1279 NextTimeControlFromString (char ** str, long * value)
1280 {
1281     long temp;
1282     int result = NextIntegerFromString( str, &temp );
1283
1284     if( result == 0 ) {
1285         *value = temp * 60; /* Minutes */
1286         if( **str == ':' ) {
1287             (*str)++;
1288             result = NextIntegerFromString( str, &temp );
1289             *value += temp; /* Seconds */
1290         }
1291     }
1292
1293     return result;
1294 }
1295
1296 int
1297 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1298 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1299     int result = -1, type = 0; long temp, temp2;
1300
1301     if(**str != ':') return -1; // old params remain in force!
1302     (*str)++;
1303     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1304     if( NextIntegerFromString( str, &temp ) ) return -1;
1305     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1306
1307     if(**str != '/') {
1308         /* time only: incremental or sudden-death time control */
1309         if(**str == '+') { /* increment follows; read it */
1310             (*str)++;
1311             if(**str == '!') type = *(*str)++; // Bronstein TC
1312             if(result = NextIntegerFromString( str, &temp2)) return -1;
1313             *inc = temp2 * 1000;
1314             if(**str == '.') { // read fraction of increment
1315                 char *start = ++(*str);
1316                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1317                 temp2 *= 1000;
1318                 while(start++ < *str) temp2 /= 10;
1319                 *inc += temp2;
1320             }
1321         } else *inc = 0;
1322         *moves = 0; *tc = temp * 1000; *incType = type;
1323         return 0;
1324     }
1325
1326     (*str)++; /* classical time control */
1327     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1328
1329     if(result == 0) {
1330         *moves = temp;
1331         *tc    = temp2 * 1000;
1332         *inc   = 0;
1333         *incType = type;
1334     }
1335     return result;
1336 }
1337
1338 int
1339 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1340 {   /* [HGM] get time to add from the multi-session time-control string */
1341     int incType, moves=1; /* kludge to force reading of first session */
1342     long time, increment;
1343     char *s = tcString;
1344
1345     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1346     do {
1347         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1348         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1349         if(movenr == -1) return time;    /* last move before new session     */
1350         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1351         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1352         if(!moves) return increment;     /* current session is incremental   */
1353         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1354     } while(movenr >= -1);               /* try again for next session       */
1355
1356     return 0; // no new time quota on this move
1357 }
1358
1359 int
1360 ParseTimeControl (char *tc, float ti, int mps)
1361 {
1362   long tc1;
1363   long tc2;
1364   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1365   int min, sec=0;
1366
1367   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1368   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1369       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1370   if(ti > 0) {
1371
1372     if(mps)
1373       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1374     else
1375       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1376   } else {
1377     if(mps)
1378       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1379     else
1380       snprintf(buf, MSG_SIZ, ":%s", mytc);
1381   }
1382   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1383
1384   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1385     return FALSE;
1386   }
1387
1388   if( *tc == '/' ) {
1389     /* Parse second time control */
1390     tc++;
1391
1392     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1393       return FALSE;
1394     }
1395
1396     if( tc2 == 0 ) {
1397       return FALSE;
1398     }
1399
1400     timeControl_2 = tc2 * 1000;
1401   }
1402   else {
1403     timeControl_2 = 0;
1404   }
1405
1406   if( tc1 == 0 ) {
1407     return FALSE;
1408   }
1409
1410   timeControl = tc1 * 1000;
1411
1412   if (ti >= 0) {
1413     timeIncrement = ti * 1000;  /* convert to ms */
1414     movesPerSession = 0;
1415   } else {
1416     timeIncrement = 0;
1417     movesPerSession = mps;
1418   }
1419   return TRUE;
1420 }
1421
1422 void
1423 InitBackEnd2 ()
1424 {
1425     if (appData.debugMode) {
1426 #    ifdef __GIT_VERSION
1427       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1428 #    else
1429       fprintf(debugFP, "Version: %s\n", programVersion);
1430 #    endif
1431     }
1432     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1433
1434     set_cont_sequence(appData.wrapContSeq);
1435     if (appData.matchGames > 0) {
1436         appData.matchMode = TRUE;
1437     } else if (appData.matchMode) {
1438         appData.matchGames = 1;
1439     }
1440     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1441         appData.matchGames = appData.sameColorGames;
1442     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1443         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1444         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1445     }
1446     Reset(TRUE, FALSE);
1447     if (appData.noChessProgram || first.protocolVersion == 1) {
1448       InitBackEnd3();
1449     } else {
1450       /* kludge: allow timeout for initial "feature" commands */
1451       FreezeUI();
1452       DisplayMessage("", _("Starting chess program"));
1453       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1454     }
1455 }
1456
1457 int
1458 CalculateIndex (int index, int gameNr)
1459 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1460     int res;
1461     if(index > 0) return index; // fixed nmber
1462     if(index == 0) return 1;
1463     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1464     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1465     return res;
1466 }
1467
1468 int
1469 LoadGameOrPosition (int gameNr)
1470 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1471     if (*appData.loadGameFile != NULLCHAR) {
1472         if (!LoadGameFromFile(appData.loadGameFile,
1473                 CalculateIndex(appData.loadGameIndex, gameNr),
1474                               appData.loadGameFile, FALSE)) {
1475             DisplayFatalError(_("Bad game file"), 0, 1);
1476             return 0;
1477         }
1478     } else if (*appData.loadPositionFile != NULLCHAR) {
1479         if (!LoadPositionFromFile(appData.loadPositionFile,
1480                 CalculateIndex(appData.loadPositionIndex, gameNr),
1481                                   appData.loadPositionFile)) {
1482             DisplayFatalError(_("Bad position file"), 0, 1);
1483             return 0;
1484         }
1485     }
1486     return 1;
1487 }
1488
1489 void
1490 ReserveGame (int gameNr, char resChar)
1491 {
1492     FILE *tf = fopen(appData.tourneyFile, "r+");
1493     char *p, *q, c, buf[MSG_SIZ];
1494     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1495     safeStrCpy(buf, lastMsg, MSG_SIZ);
1496     DisplayMessage(_("Pick new game"), "");
1497     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1498     ParseArgsFromFile(tf);
1499     p = q = appData.results;
1500     if(appData.debugMode) {
1501       char *r = appData.participants;
1502       fprintf(debugFP, "results = '%s'\n", p);
1503       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1504       fprintf(debugFP, "\n");
1505     }
1506     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1507     nextGame = q - p;
1508     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1509     safeStrCpy(q, p, strlen(p) + 2);
1510     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1511     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1512     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1513         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1514         q[nextGame] = '*';
1515     }
1516     fseek(tf, -(strlen(p)+4), SEEK_END);
1517     c = fgetc(tf);
1518     if(c != '"') // depending on DOS or Unix line endings we can be one off
1519          fseek(tf, -(strlen(p)+2), SEEK_END);
1520     else fseek(tf, -(strlen(p)+3), SEEK_END);
1521     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1522     DisplayMessage(buf, "");
1523     free(p); appData.results = q;
1524     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1525        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1526       int round = appData.defaultMatchGames * appData.tourneyType;
1527       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1528          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1529         UnloadEngine(&first);  // next game belongs to other pairing;
1530         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1531     }
1532     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1533 }
1534
1535 void
1536 MatchEvent (int mode)
1537 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1538         int dummy;
1539         if(matchMode) { // already in match mode: switch it off
1540             abortMatch = TRUE;
1541             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1542             return;
1543         }
1544 //      if(gameMode != BeginningOfGame) {
1545 //          DisplayError(_("You can only start a match from the initial position."), 0);
1546 //          return;
1547 //      }
1548         abortMatch = FALSE;
1549         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1550         /* Set up machine vs. machine match */
1551         nextGame = 0;
1552         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1553         if(appData.tourneyFile[0]) {
1554             ReserveGame(-1, 0);
1555             if(nextGame > appData.matchGames) {
1556                 char buf[MSG_SIZ];
1557                 if(strchr(appData.results, '*') == NULL) {
1558                     FILE *f;
1559                     appData.tourneyCycles++;
1560                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1561                         fclose(f);
1562                         NextTourneyGame(-1, &dummy);
1563                         ReserveGame(-1, 0);
1564                         if(nextGame <= appData.matchGames) {
1565                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1566                             matchMode = mode;
1567                             ScheduleDelayedEvent(NextMatchGame, 10000);
1568                             return;
1569                         }
1570                     }
1571                 }
1572                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1573                 DisplayError(buf, 0);
1574                 appData.tourneyFile[0] = 0;
1575                 return;
1576             }
1577         } else
1578         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1579             DisplayFatalError(_("Can't have a match with no chess programs"),
1580                               0, 2);
1581             return;
1582         }
1583         matchMode = mode;
1584         matchGame = roundNr = 1;
1585         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1586         NextMatchGame();
1587 }
1588
1589 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1590
1591 void
1592 InitBackEnd3 P((void))
1593 {
1594     GameMode initialMode;
1595     char buf[MSG_SIZ];
1596     int err, len;
1597
1598     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1599        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1600         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1601        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1602        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1603         char c, *q = first.variants, *p = strchr(q, ',');
1604         if(p) *p = NULLCHAR;
1605         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1606             int w, h, s;
1607             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1608                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1609             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1610             Reset(TRUE, FALSE);         // and re-initialize
1611         }
1612         if(p) *p = ',';
1613     }
1614
1615     InitChessProgram(&first, startedFromSetupPosition);
1616
1617     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1618         free(programVersion);
1619         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1620         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1621         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1622     }
1623
1624     if (appData.icsActive) {
1625 #ifdef WIN32
1626         /* [DM] Make a console window if needed [HGM] merged ifs */
1627         ConsoleCreate();
1628 #endif
1629         err = establish();
1630         if (err != 0)
1631           {
1632             if (*appData.icsCommPort != NULLCHAR)
1633               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1634                              appData.icsCommPort);
1635             else
1636               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1637                         appData.icsHost, appData.icsPort);
1638
1639             if( (len >= MSG_SIZ) && appData.debugMode )
1640               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1641
1642             DisplayFatalError(buf, err, 1);
1643             return;
1644         }
1645         SetICSMode();
1646         telnetISR =
1647           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1648         fromUserISR =
1649           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1650         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1651             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1652     } else if (appData.noChessProgram) {
1653         SetNCPMode();
1654     } else {
1655         SetGNUMode();
1656     }
1657
1658     if (*appData.cmailGameName != NULLCHAR) {
1659         SetCmailMode();
1660         OpenLoopback(&cmailPR);
1661         cmailISR =
1662           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1663     }
1664
1665     ThawUI();
1666     DisplayMessage("", "");
1667     if (StrCaseCmp(appData.initialMode, "") == 0) {
1668       initialMode = BeginningOfGame;
1669       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1670         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1671         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1672         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1673         ModeHighlight();
1674       }
1675     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1676       initialMode = TwoMachinesPlay;
1677     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1678       initialMode = AnalyzeFile;
1679     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1680       initialMode = AnalyzeMode;
1681     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1682       initialMode = MachinePlaysWhite;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1684       initialMode = MachinePlaysBlack;
1685     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1686       initialMode = EditGame;
1687     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1688       initialMode = EditPosition;
1689     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1690       initialMode = Training;
1691     } else {
1692       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1693       if( (len >= MSG_SIZ) && appData.debugMode )
1694         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1695
1696       DisplayFatalError(buf, 0, 2);
1697       return;
1698     }
1699
1700     if (appData.matchMode) {
1701         if(appData.tourneyFile[0]) { // start tourney from command line
1702             FILE *f;
1703             if(f = fopen(appData.tourneyFile, "r")) {
1704                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1705                 fclose(f);
1706                 appData.clockMode = TRUE;
1707                 SetGNUMode();
1708             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1709         }
1710         MatchEvent(TRUE);
1711     } else if (*appData.cmailGameName != NULLCHAR) {
1712         /* Set up cmail mode */
1713         ReloadCmailMsgEvent(TRUE);
1714     } else {
1715         /* Set up other modes */
1716         if (initialMode == AnalyzeFile) {
1717           if (*appData.loadGameFile == NULLCHAR) {
1718             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1719             return;
1720           }
1721         }
1722         if (*appData.loadGameFile != NULLCHAR) {
1723             (void) LoadGameFromFile(appData.loadGameFile,
1724                                     appData.loadGameIndex,
1725                                     appData.loadGameFile, TRUE);
1726         } else if (*appData.loadPositionFile != NULLCHAR) {
1727             (void) LoadPositionFromFile(appData.loadPositionFile,
1728                                         appData.loadPositionIndex,
1729                                         appData.loadPositionFile);
1730             /* [HGM] try to make self-starting even after FEN load */
1731             /* to allow automatic setup of fairy variants with wtm */
1732             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1733                 gameMode = BeginningOfGame;
1734                 setboardSpoiledMachineBlack = 1;
1735             }
1736             /* [HGM] loadPos: make that every new game uses the setup */
1737             /* from file as long as we do not switch variant          */
1738             if(!blackPlaysFirst) {
1739                 startedFromPositionFile = TRUE;
1740                 CopyBoard(filePosition, boards[0]);
1741             }
1742         }
1743         if (initialMode == AnalyzeMode) {
1744           if (appData.noChessProgram) {
1745             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1746             return;
1747           }
1748           if (appData.icsActive) {
1749             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1750             return;
1751           }
1752           AnalyzeModeEvent();
1753         } else if (initialMode == AnalyzeFile) {
1754           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1755           ShowThinkingEvent();
1756           AnalyzeFileEvent();
1757           AnalysisPeriodicEvent(1);
1758         } else if (initialMode == MachinePlaysWhite) {
1759           if (appData.noChessProgram) {
1760             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1761                               0, 2);
1762             return;
1763           }
1764           if (appData.icsActive) {
1765             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1766                               0, 2);
1767             return;
1768           }
1769           MachineWhiteEvent();
1770         } else if (initialMode == MachinePlaysBlack) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           MachineBlackEvent();
1782         } else if (initialMode == TwoMachinesPlay) {
1783           if (appData.noChessProgram) {
1784             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1785                               0, 2);
1786             return;
1787           }
1788           if (appData.icsActive) {
1789             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1790                               0, 2);
1791             return;
1792           }
1793           TwoMachinesEvent();
1794         } else if (initialMode == EditGame) {
1795           EditGameEvent();
1796         } else if (initialMode == EditPosition) {
1797           EditPositionEvent();
1798         } else if (initialMode == Training) {
1799           if (*appData.loadGameFile == NULLCHAR) {
1800             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1801             return;
1802           }
1803           TrainingEvent();
1804         }
1805     }
1806 }
1807
1808 void
1809 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1810 {
1811     DisplayBook(current+1);
1812
1813     MoveHistorySet( movelist, first, last, current, pvInfoList );
1814
1815     EvalGraphSet( first, last, current, pvInfoList );
1816
1817     MakeEngineOutputTitle();
1818 }
1819
1820 /*
1821  * Establish will establish a contact to a remote host.port.
1822  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1823  *  used to talk to the host.
1824  * Returns 0 if okay, error code if not.
1825  */
1826 int
1827 establish ()
1828 {
1829     char buf[MSG_SIZ];
1830
1831     if (*appData.icsCommPort != NULLCHAR) {
1832         /* Talk to the host through a serial comm port */
1833         return OpenCommPort(appData.icsCommPort, &icsPR);
1834
1835     } else if (*appData.gateway != NULLCHAR) {
1836         if (*appData.remoteShell == NULLCHAR) {
1837             /* Use the rcmd protocol to run telnet program on a gateway host */
1838             snprintf(buf, sizeof(buf), "%s %s %s",
1839                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1840             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1841
1842         } else {
1843             /* Use the rsh program to run telnet program on a gateway host */
1844             if (*appData.remoteUser == NULLCHAR) {
1845                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1846                         appData.gateway, appData.telnetProgram,
1847                         appData.icsHost, appData.icsPort);
1848             } else {
1849                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1850                         appData.remoteShell, appData.gateway,
1851                         appData.remoteUser, appData.telnetProgram,
1852                         appData.icsHost, appData.icsPort);
1853             }
1854             return StartChildProcess(buf, "", &icsPR);
1855
1856         }
1857     } else if (appData.useTelnet) {
1858         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1859
1860     } else {
1861         /* TCP socket interface differs somewhat between
1862            Unix and NT; handle details in the front end.
1863            */
1864         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1865     }
1866 }
1867
1868 void
1869 EscapeExpand (char *p, char *q)
1870 {       // [HGM] initstring: routine to shape up string arguments
1871         while(*p++ = *q++) if(p[-1] == '\\')
1872             switch(*q++) {
1873                 case 'n': p[-1] = '\n'; break;
1874                 case 'r': p[-1] = '\r'; break;
1875                 case 't': p[-1] = '\t'; break;
1876                 case '\\': p[-1] = '\\'; break;
1877                 case 0: *p = 0; return;
1878                 default: p[-1] = q[-1]; break;
1879             }
1880 }
1881
1882 void
1883 show_bytes (FILE *fp, char *buf, int count)
1884 {
1885     while (count--) {
1886         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1887             fprintf(fp, "\\%03o", *buf & 0xff);
1888         } else {
1889             putc(*buf, fp);
1890         }
1891         buf++;
1892     }
1893     fflush(fp);
1894 }
1895
1896 /* Returns an errno value */
1897 int
1898 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1899 {
1900     char buf[8192], *p, *q, *buflim;
1901     int left, newcount, outcount;
1902
1903     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1904         *appData.gateway != NULLCHAR) {
1905         if (appData.debugMode) {
1906             fprintf(debugFP, ">ICS: ");
1907             show_bytes(debugFP, message, count);
1908             fprintf(debugFP, "\n");
1909         }
1910         return OutputToProcess(pr, message, count, outError);
1911     }
1912
1913     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1914     p = message;
1915     q = buf;
1916     left = count;
1917     newcount = 0;
1918     while (left) {
1919         if (q >= buflim) {
1920             if (appData.debugMode) {
1921                 fprintf(debugFP, ">ICS: ");
1922                 show_bytes(debugFP, buf, newcount);
1923                 fprintf(debugFP, "\n");
1924             }
1925             outcount = OutputToProcess(pr, buf, newcount, outError);
1926             if (outcount < newcount) return -1; /* to be sure */
1927             q = buf;
1928             newcount = 0;
1929         }
1930         if (*p == '\n') {
1931             *q++ = '\r';
1932             newcount++;
1933         } else if (((unsigned char) *p) == TN_IAC) {
1934             *q++ = (char) TN_IAC;
1935             newcount ++;
1936         }
1937         *q++ = *p++;
1938         newcount++;
1939         left--;
1940     }
1941     if (appData.debugMode) {
1942         fprintf(debugFP, ">ICS: ");
1943         show_bytes(debugFP, buf, newcount);
1944         fprintf(debugFP, "\n");
1945     }
1946     outcount = OutputToProcess(pr, buf, newcount, outError);
1947     if (outcount < newcount) return -1; /* to be sure */
1948     return count;
1949 }
1950
1951 void
1952 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1953 {
1954     int outError, outCount;
1955     static int gotEof = 0;
1956     static FILE *ini;
1957
1958     /* Pass data read from player on to ICS */
1959     if (count > 0) {
1960         gotEof = 0;
1961         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1962         if (outCount < count) {
1963             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1964         }
1965         if(have_sent_ICS_logon == 2) {
1966           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1967             fprintf(ini, "%s", message);
1968             have_sent_ICS_logon = 3;
1969           } else
1970             have_sent_ICS_logon = 1;
1971         } else if(have_sent_ICS_logon == 3) {
1972             fprintf(ini, "%s", message);
1973             fclose(ini);
1974           have_sent_ICS_logon = 1;
1975         }
1976     } else if (count < 0) {
1977         RemoveInputSource(isr);
1978         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1979     } else if (gotEof++ > 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1982     }
1983 }
1984
1985 void
1986 KeepAlive ()
1987 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1988     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1989     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1990     SendToICS("date\n");
1991     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1992 }
1993
1994 /* added routine for printf style output to ics */
1995 void
1996 ics_printf (char *format, ...)
1997 {
1998     char buffer[MSG_SIZ];
1999     va_list args;
2000
2001     va_start(args, format);
2002     vsnprintf(buffer, sizeof(buffer), format, args);
2003     buffer[sizeof(buffer)-1] = '\0';
2004     SendToICS(buffer);
2005     va_end(args);
2006 }
2007
2008 void
2009 SendToICS (char *s)
2010 {
2011     int count, outCount, outError;
2012
2013     if (icsPR == NoProc) return;
2014
2015     count = strlen(s);
2016     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2017     if (outCount < count) {
2018         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2019     }
2020 }
2021
2022 /* This is used for sending logon scripts to the ICS. Sending
2023    without a delay causes problems when using timestamp on ICC
2024    (at least on my machine). */
2025 void
2026 SendToICSDelayed (char *s, long msdelay)
2027 {
2028     int count, outCount, outError;
2029
2030     if (icsPR == NoProc) return;
2031
2032     count = strlen(s);
2033     if (appData.debugMode) {
2034         fprintf(debugFP, ">ICS: ");
2035         show_bytes(debugFP, s, count);
2036         fprintf(debugFP, "\n");
2037     }
2038     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2039                                       msdelay);
2040     if (outCount < count) {
2041         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2042     }
2043 }
2044
2045
2046 /* Remove all highlighting escape sequences in s
2047    Also deletes any suffix starting with '('
2048    */
2049 char *
2050 StripHighlightAndTitle (char *s)
2051 {
2052     static char retbuf[MSG_SIZ];
2053     char *p = retbuf;
2054
2055     while (*s != NULLCHAR) {
2056         while (*s == '\033') {
2057             while (*s != NULLCHAR && !isalpha(*s)) s++;
2058             if (*s != NULLCHAR) s++;
2059         }
2060         while (*s != NULLCHAR && *s != '\033') {
2061             if (*s == '(' || *s == '[') {
2062                 *p = NULLCHAR;
2063                 return retbuf;
2064             }
2065             *p++ = *s++;
2066         }
2067     }
2068     *p = NULLCHAR;
2069     return retbuf;
2070 }
2071
2072 /* Remove all highlighting escape sequences in s */
2073 char *
2074 StripHighlight (char *s)
2075 {
2076     static char retbuf[MSG_SIZ];
2077     char *p = retbuf;
2078
2079     while (*s != NULLCHAR) {
2080         while (*s == '\033') {
2081             while (*s != NULLCHAR && !isalpha(*s)) s++;
2082             if (*s != NULLCHAR) s++;
2083         }
2084         while (*s != NULLCHAR && *s != '\033') {
2085             *p++ = *s++;
2086         }
2087     }
2088     *p = NULLCHAR;
2089     return retbuf;
2090 }
2091
2092 char engineVariant[MSG_SIZ];
2093 char *variantNames[] = VARIANT_NAMES;
2094 char *
2095 VariantName (VariantClass v)
2096 {
2097     if(v == VariantUnknown || *engineVariant) return engineVariant;
2098     return variantNames[v];
2099 }
2100
2101
2102 /* Identify a variant from the strings the chess servers use or the
2103    PGN Variant tag names we use. */
2104 VariantClass
2105 StringToVariant (char *e)
2106 {
2107     char *p;
2108     int wnum = -1;
2109     VariantClass v = VariantNormal;
2110     int i, found = FALSE;
2111     char buf[MSG_SIZ], c;
2112     int len;
2113
2114     if (!e) return v;
2115
2116     /* [HGM] skip over optional board-size prefixes */
2117     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2118         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2119         while( *e++ != '_');
2120     }
2121
2122     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2123         v = VariantNormal;
2124         found = TRUE;
2125     } else
2126     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2127       if (p = StrCaseStr(e, variantNames[i])) {
2128         if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
2129         v = (VariantClass) i;
2130         found = TRUE;
2131         break;
2132       }
2133     }
2134
2135     if (!found) {
2136       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2137           || StrCaseStr(e, "wild/fr")
2138           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2139         v = VariantFischeRandom;
2140       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2141                  (i = 1, p = StrCaseStr(e, "w"))) {
2142         p += i;
2143         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2144         if (isdigit(*p)) {
2145           wnum = atoi(p);
2146         } else {
2147           wnum = -1;
2148         }
2149         switch (wnum) {
2150         case 0: /* FICS only, actually */
2151         case 1:
2152           /* Castling legal even if K starts on d-file */
2153           v = VariantWildCastle;
2154           break;
2155         case 2:
2156         case 3:
2157         case 4:
2158           /* Castling illegal even if K & R happen to start in
2159              normal positions. */
2160           v = VariantNoCastle;
2161           break;
2162         case 5:
2163         case 7:
2164         case 8:
2165         case 10:
2166         case 11:
2167         case 12:
2168         case 13:
2169         case 14:
2170         case 15:
2171         case 18:
2172         case 19:
2173           /* Castling legal iff K & R start in normal positions */
2174           v = VariantNormal;
2175           break;
2176         case 6:
2177         case 20:
2178         case 21:
2179           /* Special wilds for position setup; unclear what to do here */
2180           v = VariantLoadable;
2181           break;
2182         case 9:
2183           /* Bizarre ICC game */
2184           v = VariantTwoKings;
2185           break;
2186         case 16:
2187           v = VariantKriegspiel;
2188           break;
2189         case 17:
2190           v = VariantLosers;
2191           break;
2192         case 22:
2193           v = VariantFischeRandom;
2194           break;
2195         case 23:
2196           v = VariantCrazyhouse;
2197           break;
2198         case 24:
2199           v = VariantBughouse;
2200           break;
2201         case 25:
2202           v = Variant3Check;
2203           break;
2204         case 26:
2205           /* Not quite the same as FICS suicide! */
2206           v = VariantGiveaway;
2207           break;
2208         case 27:
2209           v = VariantAtomic;
2210           break;
2211         case 28:
2212           v = VariantShatranj;
2213           break;
2214
2215         /* Temporary names for future ICC types.  The name *will* change in
2216            the next xboard/WinBoard release after ICC defines it. */
2217         case 29:
2218           v = Variant29;
2219           break;
2220         case 30:
2221           v = Variant30;
2222           break;
2223         case 31:
2224           v = Variant31;
2225           break;
2226         case 32:
2227           v = Variant32;
2228           break;
2229         case 33:
2230           v = Variant33;
2231           break;
2232         case 34:
2233           v = Variant34;
2234           break;
2235         case 35:
2236           v = Variant35;
2237           break;
2238         case 36:
2239           v = Variant36;
2240           break;
2241         case 37:
2242           v = VariantShogi;
2243           break;
2244         case 38:
2245           v = VariantXiangqi;
2246           break;
2247         case 39:
2248           v = VariantCourier;
2249           break;
2250         case 40:
2251           v = VariantGothic;
2252           break;
2253         case 41:
2254           v = VariantCapablanca;
2255           break;
2256         case 42:
2257           v = VariantKnightmate;
2258           break;
2259         case 43:
2260           v = VariantFairy;
2261           break;
2262         case 44:
2263           v = VariantCylinder;
2264           break;
2265         case 45:
2266           v = VariantFalcon;
2267           break;
2268         case 46:
2269           v = VariantCapaRandom;
2270           break;
2271         case 47:
2272           v = VariantBerolina;
2273           break;
2274         case 48:
2275           v = VariantJanus;
2276           break;
2277         case 49:
2278           v = VariantSuper;
2279           break;
2280         case 50:
2281           v = VariantGreat;
2282           break;
2283         case -1:
2284           /* Found "wild" or "w" in the string but no number;
2285              must assume it's normal chess. */
2286           v = VariantNormal;
2287           break;
2288         default:
2289           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2290           if( (len >= MSG_SIZ) && appData.debugMode )
2291             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2292
2293           DisplayError(buf, 0);
2294           v = VariantUnknown;
2295           break;
2296         }
2297       }
2298     }
2299     if (appData.debugMode) {
2300       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2301               e, wnum, VariantName(v));
2302     }
2303     return v;
2304 }
2305
2306 static int leftover_start = 0, leftover_len = 0;
2307 char star_match[STAR_MATCH_N][MSG_SIZ];
2308
2309 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2310    advance *index beyond it, and set leftover_start to the new value of
2311    *index; else return FALSE.  If pattern contains the character '*', it
2312    matches any sequence of characters not containing '\r', '\n', or the
2313    character following the '*' (if any), and the matched sequence(s) are
2314    copied into star_match.
2315    */
2316 int
2317 looking_at ( char *buf, int *index, char *pattern)
2318 {
2319     char *bufp = &buf[*index], *patternp = pattern;
2320     int star_count = 0;
2321     char *matchp = star_match[0];
2322
2323     for (;;) {
2324         if (*patternp == NULLCHAR) {
2325             *index = leftover_start = bufp - buf;
2326             *matchp = NULLCHAR;
2327             return TRUE;
2328         }
2329         if (*bufp == NULLCHAR) return FALSE;
2330         if (*patternp == '*') {
2331             if (*bufp == *(patternp + 1)) {
2332                 *matchp = NULLCHAR;
2333                 matchp = star_match[++star_count];
2334                 patternp += 2;
2335                 bufp++;
2336                 continue;
2337             } else if (*bufp == '\n' || *bufp == '\r') {
2338                 patternp++;
2339                 if (*patternp == NULLCHAR)
2340                   continue;
2341                 else
2342                   return FALSE;
2343             } else {
2344                 *matchp++ = *bufp++;
2345                 continue;
2346             }
2347         }
2348         if (*patternp != *bufp) return FALSE;
2349         patternp++;
2350         bufp++;
2351     }
2352 }
2353
2354 void
2355 SendToPlayer (char *data, int length)
2356 {
2357     int error, outCount;
2358     outCount = OutputToProcess(NoProc, data, length, &error);
2359     if (outCount < length) {
2360         DisplayFatalError(_("Error writing to display"), error, 1);
2361     }
2362 }
2363
2364 void
2365 PackHolding (char packed[], char *holding)
2366 {
2367     char *p = holding;
2368     char *q = packed;
2369     int runlength = 0;
2370     int curr = 9999;
2371     do {
2372         if (*p == curr) {
2373             runlength++;
2374         } else {
2375             switch (runlength) {
2376               case 0:
2377                 break;
2378               case 1:
2379                 *q++ = curr;
2380                 break;
2381               case 2:
2382                 *q++ = curr;
2383                 *q++ = curr;
2384                 break;
2385               default:
2386                 sprintf(q, "%d", runlength);
2387                 while (*q) q++;
2388                 *q++ = curr;
2389                 break;
2390             }
2391             runlength = 1;
2392             curr = *p;
2393         }
2394     } while (*p++);
2395     *q = NULLCHAR;
2396 }
2397
2398 /* Telnet protocol requests from the front end */
2399 void
2400 TelnetRequest (unsigned char ddww, unsigned char option)
2401 {
2402     unsigned char msg[3];
2403     int outCount, outError;
2404
2405     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2406
2407     if (appData.debugMode) {
2408         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2409         switch (ddww) {
2410           case TN_DO:
2411             ddwwStr = "DO";
2412             break;
2413           case TN_DONT:
2414             ddwwStr = "DONT";
2415             break;
2416           case TN_WILL:
2417             ddwwStr = "WILL";
2418             break;
2419           case TN_WONT:
2420             ddwwStr = "WONT";
2421             break;
2422           default:
2423             ddwwStr = buf1;
2424             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2425             break;
2426         }
2427         switch (option) {
2428           case TN_ECHO:
2429             optionStr = "ECHO";
2430             break;
2431           default:
2432             optionStr = buf2;
2433             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2434             break;
2435         }
2436         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2437     }
2438     msg[0] = TN_IAC;
2439     msg[1] = ddww;
2440     msg[2] = option;
2441     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2442     if (outCount < 3) {
2443         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2444     }
2445 }
2446
2447 void
2448 DoEcho ()
2449 {
2450     if (!appData.icsActive) return;
2451     TelnetRequest(TN_DO, TN_ECHO);
2452 }
2453
2454 void
2455 DontEcho ()
2456 {
2457     if (!appData.icsActive) return;
2458     TelnetRequest(TN_DONT, TN_ECHO);
2459 }
2460
2461 void
2462 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2463 {
2464     /* put the holdings sent to us by the server on the board holdings area */
2465     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2466     char p;
2467     ChessSquare piece;
2468
2469     if(gameInfo.holdingsWidth < 2)  return;
2470     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2471         return; // prevent overwriting by pre-board holdings
2472
2473     if( (int)lowestPiece >= BlackPawn ) {
2474         holdingsColumn = 0;
2475         countsColumn = 1;
2476         holdingsStartRow = BOARD_HEIGHT-1;
2477         direction = -1;
2478     } else {
2479         holdingsColumn = BOARD_WIDTH-1;
2480         countsColumn = BOARD_WIDTH-2;
2481         holdingsStartRow = 0;
2482         direction = 1;
2483     }
2484
2485     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2486         board[i][holdingsColumn] = EmptySquare;
2487         board[i][countsColumn]   = (ChessSquare) 0;
2488     }
2489     while( (p=*holdings++) != NULLCHAR ) {
2490         piece = CharToPiece( ToUpper(p) );
2491         if(piece == EmptySquare) continue;
2492         /*j = (int) piece - (int) WhitePawn;*/
2493         j = PieceToNumber(piece);
2494         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2495         if(j < 0) continue;               /* should not happen */
2496         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2497         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2498         board[holdingsStartRow+j*direction][countsColumn]++;
2499     }
2500 }
2501
2502
2503 void
2504 VariantSwitch (Board board, VariantClass newVariant)
2505 {
2506    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2507    static Board oldBoard;
2508
2509    startedFromPositionFile = FALSE;
2510    if(gameInfo.variant == newVariant) return;
2511
2512    /* [HGM] This routine is called each time an assignment is made to
2513     * gameInfo.variant during a game, to make sure the board sizes
2514     * are set to match the new variant. If that means adding or deleting
2515     * holdings, we shift the playing board accordingly
2516     * This kludge is needed because in ICS observe mode, we get boards
2517     * of an ongoing game without knowing the variant, and learn about the
2518     * latter only later. This can be because of the move list we requested,
2519     * in which case the game history is refilled from the beginning anyway,
2520     * but also when receiving holdings of a crazyhouse game. In the latter
2521     * case we want to add those holdings to the already received position.
2522     */
2523
2524
2525    if (appData.debugMode) {
2526      fprintf(debugFP, "Switch board from %s to %s\n",
2527              VariantName(gameInfo.variant), VariantName(newVariant));
2528      setbuf(debugFP, NULL);
2529    }
2530    shuffleOpenings = 0;       /* [HGM] shuffle */
2531    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2532    switch(newVariant)
2533      {
2534      case VariantShogi:
2535        newWidth = 9;  newHeight = 9;
2536        gameInfo.holdingsSize = 7;
2537      case VariantBughouse:
2538      case VariantCrazyhouse:
2539        newHoldingsWidth = 2; break;
2540      case VariantGreat:
2541        newWidth = 10;
2542      case VariantSuper:
2543        newHoldingsWidth = 2;
2544        gameInfo.holdingsSize = 8;
2545        break;
2546      case VariantGothic:
2547      case VariantCapablanca:
2548      case VariantCapaRandom:
2549        newWidth = 10;
2550      default:
2551        newHoldingsWidth = gameInfo.holdingsSize = 0;
2552      };
2553
2554    if(newWidth  != gameInfo.boardWidth  ||
2555       newHeight != gameInfo.boardHeight ||
2556       newHoldingsWidth != gameInfo.holdingsWidth ) {
2557
2558      /* shift position to new playing area, if needed */
2559      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2560        for(i=0; i<BOARD_HEIGHT; i++)
2561          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2562            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2563              board[i][j];
2564        for(i=0; i<newHeight; i++) {
2565          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2566          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2567        }
2568      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2569        for(i=0; i<BOARD_HEIGHT; i++)
2570          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2571            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2572              board[i][j];
2573      }
2574      board[HOLDINGS_SET] = 0;
2575      gameInfo.boardWidth  = newWidth;
2576      gameInfo.boardHeight = newHeight;
2577      gameInfo.holdingsWidth = newHoldingsWidth;
2578      gameInfo.variant = newVariant;
2579      InitDrawingSizes(-2, 0);
2580    } else gameInfo.variant = newVariant;
2581    CopyBoard(oldBoard, board);   // remember correctly formatted board
2582      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2583    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2584 }
2585
2586 static int loggedOn = FALSE;
2587
2588 /*-- Game start info cache: --*/
2589 int gs_gamenum;
2590 char gs_kind[MSG_SIZ];
2591 static char player1Name[128] = "";
2592 static char player2Name[128] = "";
2593 static char cont_seq[] = "\n\\   ";
2594 static int player1Rating = -1;
2595 static int player2Rating = -1;
2596 /*----------------------------*/
2597
2598 ColorClass curColor = ColorNormal;
2599 int suppressKibitz = 0;
2600
2601 // [HGM] seekgraph
2602 Boolean soughtPending = FALSE;
2603 Boolean seekGraphUp;
2604 #define MAX_SEEK_ADS 200
2605 #define SQUARE 0x80
2606 char *seekAdList[MAX_SEEK_ADS];
2607 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2608 float tcList[MAX_SEEK_ADS];
2609 char colorList[MAX_SEEK_ADS];
2610 int nrOfSeekAds = 0;
2611 int minRating = 1010, maxRating = 2800;
2612 int hMargin = 10, vMargin = 20, h, w;
2613 extern int squareSize, lineGap;
2614
2615 void
2616 PlotSeekAd (int i)
2617 {
2618         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2619         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2620         if(r < minRating+100 && r >=0 ) r = minRating+100;
2621         if(r > maxRating) r = maxRating;
2622         if(tc < 1.f) tc = 1.f;
2623         if(tc > 95.f) tc = 95.f;
2624         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2625         y = ((double)r - minRating)/(maxRating - minRating)
2626             * (h-vMargin-squareSize/8-1) + vMargin;
2627         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2628         if(strstr(seekAdList[i], " u ")) color = 1;
2629         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2630            !strstr(seekAdList[i], "bullet") &&
2631            !strstr(seekAdList[i], "blitz") &&
2632            !strstr(seekAdList[i], "standard") ) color = 2;
2633         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2634         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2635 }
2636
2637 void
2638 PlotSingleSeekAd (int i)
2639 {
2640         PlotSeekAd(i);
2641 }
2642
2643 void
2644 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2645 {
2646         char buf[MSG_SIZ], *ext = "";
2647         VariantClass v = StringToVariant(type);
2648         if(strstr(type, "wild")) {
2649             ext = type + 4; // append wild number
2650             if(v == VariantFischeRandom) type = "chess960"; else
2651             if(v == VariantLoadable) type = "setup"; else
2652             type = VariantName(v);
2653         }
2654         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2655         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2656             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2657             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2658             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2659             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2660             seekNrList[nrOfSeekAds] = nr;
2661             zList[nrOfSeekAds] = 0;
2662             seekAdList[nrOfSeekAds++] = StrSave(buf);
2663             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2664         }
2665 }
2666
2667 void
2668 EraseSeekDot (int i)
2669 {
2670     int x = xList[i], y = yList[i], d=squareSize/4, k;
2671     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2672     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2673     // now replot every dot that overlapped
2674     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2675         int xx = xList[k], yy = yList[k];
2676         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2677             DrawSeekDot(xx, yy, colorList[k]);
2678     }
2679 }
2680
2681 void
2682 RemoveSeekAd (int nr)
2683 {
2684         int i;
2685         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2686             EraseSeekDot(i);
2687             if(seekAdList[i]) free(seekAdList[i]);
2688             seekAdList[i] = seekAdList[--nrOfSeekAds];
2689             seekNrList[i] = seekNrList[nrOfSeekAds];
2690             ratingList[i] = ratingList[nrOfSeekAds];
2691             colorList[i]  = colorList[nrOfSeekAds];
2692             tcList[i] = tcList[nrOfSeekAds];
2693             xList[i]  = xList[nrOfSeekAds];
2694             yList[i]  = yList[nrOfSeekAds];
2695             zList[i]  = zList[nrOfSeekAds];
2696             seekAdList[nrOfSeekAds] = NULL;
2697             break;
2698         }
2699 }
2700
2701 Boolean
2702 MatchSoughtLine (char *line)
2703 {
2704     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2705     int nr, base, inc, u=0; char dummy;
2706
2707     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2708        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2709        (u=1) &&
2710        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2712         // match: compact and save the line
2713         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2714         return TRUE;
2715     }
2716     return FALSE;
2717 }
2718
2719 int
2720 DrawSeekGraph ()
2721 {
2722     int i;
2723     if(!seekGraphUp) return FALSE;
2724     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2725     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2726
2727     DrawSeekBackground(0, 0, w, h);
2728     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2729     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2730     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2731         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2732         yy = h-1-yy;
2733         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2734         if(i%500 == 0) {
2735             char buf[MSG_SIZ];
2736             snprintf(buf, MSG_SIZ, "%d", i);
2737             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2738         }
2739     }
2740     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2741     for(i=1; i<100; i+=(i<10?1:5)) {
2742         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2743         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2744         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2748         }
2749     }
2750     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2751     return TRUE;
2752 }
2753
2754 int
2755 SeekGraphClick (ClickType click, int x, int y, int moving)
2756 {
2757     static int lastDown = 0, displayed = 0, lastSecond;
2758     if(y < 0) return FALSE;
2759     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2760         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2761         if(!seekGraphUp) return FALSE;
2762         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2763         DrawPosition(TRUE, NULL);
2764         return TRUE;
2765     }
2766     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2767         if(click == Release || moving) return FALSE;
2768         nrOfSeekAds = 0;
2769         soughtPending = TRUE;
2770         SendToICS(ics_prefix);
2771         SendToICS("sought\n"); // should this be "sought all"?
2772     } else { // issue challenge based on clicked ad
2773         int dist = 10000; int i, closest = 0, second = 0;
2774         for(i=0; i<nrOfSeekAds; i++) {
2775             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2776             if(d < dist) { dist = d; closest = i; }
2777             second += (d - zList[i] < 120); // count in-range ads
2778             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2779         }
2780         if(dist < 120) {
2781             char buf[MSG_SIZ];
2782             second = (second > 1);
2783             if(displayed != closest || second != lastSecond) {
2784                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2785                 lastSecond = second; displayed = closest;
2786             }
2787             if(click == Press) {
2788                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2789                 lastDown = closest;
2790                 return TRUE;
2791             } // on press 'hit', only show info
2792             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2793             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2794             SendToICS(ics_prefix);
2795             SendToICS(buf);
2796             return TRUE; // let incoming board of started game pop down the graph
2797         } else if(click == Release) { // release 'miss' is ignored
2798             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2799             if(moving == 2) { // right up-click
2800                 nrOfSeekAds = 0; // refresh graph
2801                 soughtPending = TRUE;
2802                 SendToICS(ics_prefix);
2803                 SendToICS("sought\n"); // should this be "sought all"?
2804             }
2805             return TRUE;
2806         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2807         // press miss or release hit 'pop down' seek graph
2808         seekGraphUp = FALSE;
2809         DrawPosition(TRUE, NULL);
2810     }
2811     return TRUE;
2812 }
2813
2814 void
2815 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2816 {
2817 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2818 #define STARTED_NONE 0
2819 #define STARTED_MOVES 1
2820 #define STARTED_BOARD 2
2821 #define STARTED_OBSERVE 3
2822 #define STARTED_HOLDINGS 4
2823 #define STARTED_CHATTER 5
2824 #define STARTED_COMMENT 6
2825 #define STARTED_MOVES_NOHIDE 7
2826
2827     static int started = STARTED_NONE;
2828     static char parse[20000];
2829     static int parse_pos = 0;
2830     static char buf[BUF_SIZE + 1];
2831     static int firstTime = TRUE, intfSet = FALSE;
2832     static ColorClass prevColor = ColorNormal;
2833     static int savingComment = FALSE;
2834     static int cmatch = 0; // continuation sequence match
2835     char *bp;
2836     char str[MSG_SIZ];
2837     int i, oldi;
2838     int buf_len;
2839     int next_out;
2840     int tkind;
2841     int backup;    /* [DM] For zippy color lines */
2842     char *p;
2843     char talker[MSG_SIZ]; // [HGM] chat
2844     int channel, collective=0;
2845
2846     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2847
2848     if (appData.debugMode) {
2849       if (!error) {
2850         fprintf(debugFP, "<ICS: ");
2851         show_bytes(debugFP, data, count);
2852         fprintf(debugFP, "\n");
2853       }
2854     }
2855
2856     if (appData.debugMode) { int f = forwardMostMove;
2857         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2858                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2859                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2860     }
2861     if (count > 0) {
2862         /* If last read ended with a partial line that we couldn't parse,
2863            prepend it to the new read and try again. */
2864         if (leftover_len > 0) {
2865             for (i=0; i<leftover_len; i++)
2866               buf[i] = buf[leftover_start + i];
2867         }
2868
2869     /* copy new characters into the buffer */
2870     bp = buf + leftover_len;
2871     buf_len=leftover_len;
2872     for (i=0; i<count; i++)
2873     {
2874         // ignore these
2875         if (data[i] == '\r')
2876             continue;
2877
2878         // join lines split by ICS?
2879         if (!appData.noJoin)
2880         {
2881             /*
2882                 Joining just consists of finding matches against the
2883                 continuation sequence, and discarding that sequence
2884                 if found instead of copying it.  So, until a match
2885                 fails, there's nothing to do since it might be the
2886                 complete sequence, and thus, something we don't want
2887                 copied.
2888             */
2889             if (data[i] == cont_seq[cmatch])
2890             {
2891                 cmatch++;
2892                 if (cmatch == strlen(cont_seq))
2893                 {
2894                     cmatch = 0; // complete match.  just reset the counter
2895
2896                     /*
2897                         it's possible for the ICS to not include the space
2898                         at the end of the last word, making our [correct]
2899                         join operation fuse two separate words.  the server
2900                         does this when the space occurs at the width setting.
2901                     */
2902                     if (!buf_len || buf[buf_len-1] != ' ')
2903                     {
2904                         *bp++ = ' ';
2905                         buf_len++;
2906                     }
2907                 }
2908                 continue;
2909             }
2910             else if (cmatch)
2911             {
2912                 /*
2913                     match failed, so we have to copy what matched before
2914                     falling through and copying this character.  In reality,
2915                     this will only ever be just the newline character, but
2916                     it doesn't hurt to be precise.
2917                 */
2918                 strncpy(bp, cont_seq, cmatch);
2919                 bp += cmatch;
2920                 buf_len += cmatch;
2921                 cmatch = 0;
2922             }
2923         }
2924
2925         // copy this char
2926         *bp++ = data[i];
2927         buf_len++;
2928     }
2929
2930         buf[buf_len] = NULLCHAR;
2931 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2932         next_out = 0;
2933         leftover_start = 0;
2934
2935         i = 0;
2936         while (i < buf_len) {
2937             /* Deal with part of the TELNET option negotiation
2938                protocol.  We refuse to do anything beyond the
2939                defaults, except that we allow the WILL ECHO option,
2940                which ICS uses to turn off password echoing when we are
2941                directly connected to it.  We reject this option
2942                if localLineEditing mode is on (always on in xboard)
2943                and we are talking to port 23, which might be a real
2944                telnet server that will try to keep WILL ECHO on permanently.
2945              */
2946             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2947                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2948                 unsigned char option;
2949                 oldi = i;
2950                 switch ((unsigned char) buf[++i]) {
2951                   case TN_WILL:
2952                     if (appData.debugMode)
2953                       fprintf(debugFP, "\n<WILL ");
2954                     switch (option = (unsigned char) buf[++i]) {
2955                       case TN_ECHO:
2956                         if (appData.debugMode)
2957                           fprintf(debugFP, "ECHO ");
2958                         /* Reply only if this is a change, according
2959                            to the protocol rules. */
2960                         if (remoteEchoOption) break;
2961                         if (appData.localLineEditing &&
2962                             atoi(appData.icsPort) == TN_PORT) {
2963                             TelnetRequest(TN_DONT, TN_ECHO);
2964                         } else {
2965                             EchoOff();
2966                             TelnetRequest(TN_DO, TN_ECHO);
2967                             remoteEchoOption = TRUE;
2968                         }
2969                         break;
2970                       default:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "%d ", option);
2973                         /* Whatever this is, we don't want it. */
2974                         TelnetRequest(TN_DONT, option);
2975                         break;
2976                     }
2977                     break;
2978                   case TN_WONT:
2979                     if (appData.debugMode)
2980                       fprintf(debugFP, "\n<WONT ");
2981                     switch (option = (unsigned char) buf[++i]) {
2982                       case TN_ECHO:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "ECHO ");
2985                         /* Reply only if this is a change, according
2986                            to the protocol rules. */
2987                         if (!remoteEchoOption) break;
2988                         EchoOn();
2989                         TelnetRequest(TN_DONT, TN_ECHO);
2990                         remoteEchoOption = FALSE;
2991                         break;
2992                       default:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "%d ", (unsigned char) option);
2995                         /* Whatever this is, it must already be turned
2996                            off, because we never agree to turn on
2997                            anything non-default, so according to the
2998                            protocol rules, we don't reply. */
2999                         break;
3000                     }
3001                     break;
3002                   case TN_DO:
3003                     if (appData.debugMode)
3004                       fprintf(debugFP, "\n<DO ");
3005                     switch (option = (unsigned char) buf[++i]) {
3006                       default:
3007                         /* Whatever this is, we refuse to do it. */
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         TelnetRequest(TN_WONT, option);
3011                         break;
3012                     }
3013                     break;
3014                   case TN_DONT:
3015                     if (appData.debugMode)
3016                       fprintf(debugFP, "\n<DONT ");
3017                     switch (option = (unsigned char) buf[++i]) {
3018                       default:
3019                         if (appData.debugMode)
3020                           fprintf(debugFP, "%d ", option);
3021                         /* Whatever this is, we are already not doing
3022                            it, because we never agree to do anything
3023                            non-default, so according to the protocol
3024                            rules, we don't reply. */
3025                         break;
3026                     }
3027                     break;
3028                   case TN_IAC:
3029                     if (appData.debugMode)
3030                       fprintf(debugFP, "\n<IAC ");
3031                     /* Doubled IAC; pass it through */
3032                     i--;
3033                     break;
3034                   default:
3035                     if (appData.debugMode)
3036                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3037                     /* Drop all other telnet commands on the floor */
3038                     break;
3039                 }
3040                 if (oldi > next_out)
3041                   SendToPlayer(&buf[next_out], oldi - next_out);
3042                 if (++i > next_out)
3043                   next_out = i;
3044                 continue;
3045             }
3046
3047             /* OK, this at least will *usually* work */
3048             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3049                 loggedOn = TRUE;
3050             }
3051
3052             if (loggedOn && !intfSet) {
3053                 if (ics_type == ICS_ICC) {
3054                   snprintf(str, MSG_SIZ,
3055                           "/set-quietly interface %s\n/set-quietly style 12\n",
3056                           programVersion);
3057                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3058                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3059                 } else if (ics_type == ICS_CHESSNET) {
3060                   snprintf(str, MSG_SIZ, "/style 12\n");
3061                 } else {
3062                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3063                   strcat(str, programVersion);
3064                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3065                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3066                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3067 #ifdef WIN32
3068                   strcat(str, "$iset nohighlight 1\n");
3069 #endif
3070                   strcat(str, "$iset lock 1\n$style 12\n");
3071                 }
3072                 SendToICS(str);
3073                 NotifyFrontendLogin();
3074                 intfSet = TRUE;
3075             }
3076
3077             if (started == STARTED_COMMENT) {
3078                 /* Accumulate characters in comment */
3079                 parse[parse_pos++] = buf[i];
3080                 if (buf[i] == '\n') {
3081                     parse[parse_pos] = NULLCHAR;
3082                     if(chattingPartner>=0) {
3083                         char mess[MSG_SIZ];
3084                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3085                         OutputChatMessage(chattingPartner, mess);
3086                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3087                             int p;
3088                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3089                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3090                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3091                                 OutputChatMessage(p, mess);
3092                                 break;
3093                             }
3094                         }
3095                         chattingPartner = -1;
3096                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3097                         collective = 0;
3098                     } else
3099                     if(!suppressKibitz) // [HGM] kibitz
3100                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3101                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3102                         int nrDigit = 0, nrAlph = 0, j;
3103                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3104                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3105                         parse[parse_pos] = NULLCHAR;
3106                         // try to be smart: if it does not look like search info, it should go to
3107                         // ICS interaction window after all, not to engine-output window.
3108                         for(j=0; j<parse_pos; j++) { // count letters and digits
3109                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3110                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3111                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3112                         }
3113                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3114                             int depth=0; float score;
3115                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3116                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3117                                 pvInfoList[forwardMostMove-1].depth = depth;
3118                                 pvInfoList[forwardMostMove-1].score = 100*score;
3119                             }
3120                             OutputKibitz(suppressKibitz, parse);
3121                         } else {
3122                             char tmp[MSG_SIZ];
3123                             if(gameMode == IcsObserving) // restore original ICS messages
3124                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3125                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3126                             else
3127                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3129                             SendToPlayer(tmp, strlen(tmp));
3130                         }
3131                         next_out = i+1; // [HGM] suppress printing in ICS window
3132                     }
3133                     started = STARTED_NONE;
3134                 } else {
3135                     /* Don't match patterns against characters in comment */
3136                     i++;
3137                     continue;
3138                 }
3139             }
3140             if (started == STARTED_CHATTER) {
3141                 if (buf[i] != '\n') {
3142                     /* Don't match patterns against characters in chatter */
3143                     i++;
3144                     continue;
3145                 }
3146                 started = STARTED_NONE;
3147                 if(suppressKibitz) next_out = i+1;
3148             }
3149
3150             /* Kludge to deal with rcmd protocol */
3151             if (firstTime && looking_at(buf, &i, "\001*")) {
3152                 DisplayFatalError(&buf[1], 0, 1);
3153                 continue;
3154             } else {
3155                 firstTime = FALSE;
3156             }
3157
3158             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3159                 ics_type = ICS_ICC;
3160                 ics_prefix = "/";
3161                 if (appData.debugMode)
3162                   fprintf(debugFP, "ics_type %d\n", ics_type);
3163                 continue;
3164             }
3165             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3166                 ics_type = ICS_FICS;
3167                 ics_prefix = "$";
3168                 if (appData.debugMode)
3169                   fprintf(debugFP, "ics_type %d\n", ics_type);
3170                 continue;
3171             }
3172             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3173                 ics_type = ICS_CHESSNET;
3174                 ics_prefix = "/";
3175                 if (appData.debugMode)
3176                   fprintf(debugFP, "ics_type %d\n", ics_type);
3177                 continue;
3178             }
3179
3180             if (!loggedOn &&
3181                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3182                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3183                  looking_at(buf, &i, "will be \"*\""))) {
3184               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3185               continue;
3186             }
3187
3188             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3189               char buf[MSG_SIZ];
3190               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3191               DisplayIcsInteractionTitle(buf);
3192               have_set_title = TRUE;
3193             }
3194
3195             /* skip finger notes */
3196             if (started == STARTED_NONE &&
3197                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3198                  (buf[i] == '1' && buf[i+1] == '0')) &&
3199                 buf[i+2] == ':' && buf[i+3] == ' ') {
3200               started = STARTED_CHATTER;
3201               i += 3;
3202               continue;
3203             }
3204
3205             oldi = i;
3206             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3207             if(appData.seekGraph) {
3208                 if(soughtPending && MatchSoughtLine(buf+i)) {
3209                     i = strstr(buf+i, "rated") - buf;
3210                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3211                     next_out = leftover_start = i;
3212                     started = STARTED_CHATTER;
3213                     suppressKibitz = TRUE;
3214                     continue;
3215                 }
3216                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3217                         && looking_at(buf, &i, "* ads displayed")) {
3218                     soughtPending = FALSE;
3219                     seekGraphUp = TRUE;
3220                     DrawSeekGraph();
3221                     continue;
3222                 }
3223                 if(appData.autoRefresh) {
3224                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3225                         int s = (ics_type == ICS_ICC); // ICC format differs
3226                         if(seekGraphUp)
3227                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3228                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3229                         looking_at(buf, &i, "*% "); // eat prompt
3230                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3231                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                         next_out = i; // suppress
3233                         continue;
3234                     }
3235                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3236                         char *p = star_match[0];
3237                         while(*p) {
3238                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3239                             while(*p && *p++ != ' '); // next
3240                         }
3241                         looking_at(buf, &i, "*% "); // eat prompt
3242                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                         next_out = i;
3244                         continue;
3245                     }
3246                 }
3247             }
3248
3249             /* skip formula vars */
3250             if (started == STARTED_NONE &&
3251                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3252               started = STARTED_CHATTER;
3253               i += 3;
3254               continue;
3255             }
3256
3257             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3258             if (appData.autoKibitz && started == STARTED_NONE &&
3259                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3260                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3261                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3262                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3263                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3264                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3265                         suppressKibitz = TRUE;
3266                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3267                         next_out = i;
3268                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3269                                 && (gameMode == IcsPlayingWhite)) ||
3270                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3271                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3272                             started = STARTED_CHATTER; // own kibitz we simply discard
3273                         else {
3274                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3275                             parse_pos = 0; parse[0] = NULLCHAR;
3276                             savingComment = TRUE;
3277                             suppressKibitz = gameMode != IcsObserving ? 2 :
3278                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3279                         }
3280                         continue;
3281                 } else
3282                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3283                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3284                          && atoi(star_match[0])) {
3285                     // suppress the acknowledgements of our own autoKibitz
3286                     char *p;
3287                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3288                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3289                     SendToPlayer(star_match[0], strlen(star_match[0]));
3290                     if(looking_at(buf, &i, "*% ")) // eat prompt
3291                         suppressKibitz = FALSE;
3292                     next_out = i;
3293                     continue;
3294                 }
3295             } // [HGM] kibitz: end of patch
3296
3297             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3298
3299             // [HGM] chat: intercept tells by users for which we have an open chat window
3300             channel = -1;
3301             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3302                                            looking_at(buf, &i, "* whispers:") ||
3303                                            looking_at(buf, &i, "* kibitzes:") ||
3304                                            looking_at(buf, &i, "* shouts:") ||
3305                                            looking_at(buf, &i, "* c-shouts:") ||
3306                                            looking_at(buf, &i, "--> * ") ||
3307                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3308                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3309                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3310                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3311                 int p;
3312                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3313                 chattingPartner = -1; collective = 0;
3314
3315                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3316                 for(p=0; p<MAX_CHAT; p++) {
3317                     collective = 1;
3318                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3319                     talker[0] = '['; strcat(talker, "] ");
3320                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3321                     chattingPartner = p; break;
3322                     }
3323                 } else
3324                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3325                 for(p=0; p<MAX_CHAT; p++) {
3326                     collective = 1;
3327                     if(!strcmp("kibitzes", chatPartner[p])) {
3328                         talker[0] = '['; strcat(talker, "] ");
3329                         chattingPartner = p; break;
3330                     }
3331                 } else
3332                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3333                 for(p=0; p<MAX_CHAT; p++) {
3334                     collective = 1;
3335                     if(!strcmp("whispers", chatPartner[p])) {
3336                         talker[0] = '['; strcat(talker, "] ");
3337                         chattingPartner = p; break;
3338                     }
3339                 } else
3340                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3341                   if(buf[i-8] == '-' && buf[i-3] == 't')
3342                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3343                     collective = 1;
3344                     if(!strcmp("c-shouts", chatPartner[p])) {
3345                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3346                         chattingPartner = p; break;
3347                     }
3348                   }
3349                   if(chattingPartner < 0)
3350                   for(p=0; p<MAX_CHAT; p++) {
3351                     collective = 1;
3352                     if(!strcmp("shouts", chatPartner[p])) {
3353                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3354                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3355                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                 }
3360                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3361                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3362                     talker[0] = 0;
3363                     Colorize(ColorTell, FALSE);
3364                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3365                     collective |= 2;
3366                     chattingPartner = p; break;
3367                 }
3368                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3369                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3370                     started = STARTED_COMMENT;
3371                     parse_pos = 0; parse[0] = NULLCHAR;
3372                     savingComment = 3 + chattingPartner; // counts as TRUE
3373                     if(collective == 3) i = oldi; else {
3374                         suppressKibitz = TRUE;
3375                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3376                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3377                         continue;
3378                     }
3379                 }
3380             } // [HGM] chat: end of patch
3381
3382           backup = i;
3383             if (appData.zippyTalk || appData.zippyPlay) {
3384                 /* [DM] Backup address for color zippy lines */
3385 #if ZIPPY
3386                if (loggedOn == TRUE)
3387                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3388                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3389 #endif
3390             } // [DM] 'else { ' deleted
3391                 if (
3392                     /* Regular tells and says */
3393                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3394                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3395                     looking_at(buf, &i, "* says: ") ||
3396                     /* Don't color "message" or "messages" output */
3397                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3398                     looking_at(buf, &i, "*. * at *:*: ") ||
3399                     looking_at(buf, &i, "--* (*:*): ") ||
3400                     /* Message notifications (same color as tells) */
3401                     looking_at(buf, &i, "* has left a message ") ||
3402                     looking_at(buf, &i, "* just sent you a message:\n") ||
3403                     /* Whispers and kibitzes */
3404                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3405                     looking_at(buf, &i, "* kibitzes: ") ||
3406                     /* Channel tells */
3407                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3408
3409                   if (tkind == 1 && strchr(star_match[0], ':')) {
3410                       /* Avoid "tells you:" spoofs in channels */
3411                      tkind = 3;
3412                   }
3413                   if (star_match[0][0] == NULLCHAR ||
3414                       strchr(star_match[0], ' ') ||
3415                       (tkind == 3 && strchr(star_match[1], ' '))) {
3416                     /* Reject bogus matches */
3417                     i = oldi;
3418                   } else {
3419                     if (appData.colorize) {
3420                       if (oldi > next_out) {
3421                         SendToPlayer(&buf[next_out], oldi - next_out);
3422                         next_out = oldi;
3423                       }
3424                       switch (tkind) {
3425                       case 1:
3426                         Colorize(ColorTell, FALSE);
3427                         curColor = ColorTell;
3428                         break;
3429                       case 2:
3430                         Colorize(ColorKibitz, FALSE);
3431                         curColor = ColorKibitz;
3432                         break;
3433                       case 3:
3434                         p = strrchr(star_match[1], '(');
3435                         if (p == NULL) {
3436                           p = star_match[1];
3437                         } else {
3438                           p++;
3439                         }
3440                         if (atoi(p) == 1) {
3441                           Colorize(ColorChannel1, FALSE);
3442                           curColor = ColorChannel1;
3443                         } else {
3444                           Colorize(ColorChannel, FALSE);
3445                           curColor = ColorChannel;
3446                         }
3447                         break;
3448                       case 5:
3449                         curColor = ColorNormal;
3450                         break;
3451                       }
3452                     }
3453                     if (started == STARTED_NONE && appData.autoComment &&
3454                         (gameMode == IcsObserving ||
3455                          gameMode == IcsPlayingWhite ||
3456                          gameMode == IcsPlayingBlack)) {
3457                       parse_pos = i - oldi;
3458                       memcpy(parse, &buf[oldi], parse_pos);
3459                       parse[parse_pos] = NULLCHAR;
3460                       started = STARTED_COMMENT;
3461                       savingComment = TRUE;
3462                     } else if(collective != 3) {
3463                       started = STARTED_CHATTER;
3464                       savingComment = FALSE;
3465                     }
3466                     loggedOn = TRUE;
3467                     continue;
3468                   }
3469                 }
3470
3471                 if (looking_at(buf, &i, "* s-shouts: ") ||
3472                     looking_at(buf, &i, "* c-shouts: ")) {
3473                     if (appData.colorize) {
3474                         if (oldi > next_out) {
3475                             SendToPlayer(&buf[next_out], oldi - next_out);
3476                             next_out = oldi;
3477                         }
3478                         Colorize(ColorSShout, FALSE);
3479                         curColor = ColorSShout;
3480                     }
3481                     loggedOn = TRUE;
3482                     started = STARTED_CHATTER;
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "--->")) {
3487                     loggedOn = TRUE;
3488                     continue;
3489                 }
3490
3491                 if (looking_at(buf, &i, "* shouts: ") ||
3492                     looking_at(buf, &i, "--> ")) {
3493                     if (appData.colorize) {
3494                         if (oldi > next_out) {
3495                             SendToPlayer(&buf[next_out], oldi - next_out);
3496                             next_out = oldi;
3497                         }
3498                         Colorize(ColorShout, FALSE);
3499                         curColor = ColorShout;
3500                     }
3501                     loggedOn = TRUE;
3502                     started = STARTED_CHATTER;
3503                     continue;
3504                 }
3505
3506                 if (looking_at( buf, &i, "Challenge:")) {
3507                     if (appData.colorize) {
3508                         if (oldi > next_out) {
3509                             SendToPlayer(&buf[next_out], oldi - next_out);
3510                             next_out = oldi;
3511                         }
3512                         Colorize(ColorChallenge, FALSE);
3513                         curColor = ColorChallenge;
3514                     }
3515                     loggedOn = TRUE;
3516                     continue;
3517                 }
3518
3519                 if (looking_at(buf, &i, "* offers you") ||
3520                     looking_at(buf, &i, "* offers to be") ||
3521                     looking_at(buf, &i, "* would like to") ||
3522                     looking_at(buf, &i, "* requests to") ||
3523                     looking_at(buf, &i, "Your opponent offers") ||
3524                     looking_at(buf, &i, "Your opponent requests")) {
3525
3526                     if (appData.colorize) {
3527                         if (oldi > next_out) {
3528                             SendToPlayer(&buf[next_out], oldi - next_out);
3529                             next_out = oldi;
3530                         }
3531                         Colorize(ColorRequest, FALSE);
3532                         curColor = ColorRequest;
3533                     }
3534                     continue;
3535                 }
3536
3537                 if (looking_at(buf, &i, "* (*) seeking")) {
3538                     if (appData.colorize) {
3539                         if (oldi > next_out) {
3540                             SendToPlayer(&buf[next_out], oldi - next_out);
3541                             next_out = oldi;
3542                         }
3543                         Colorize(ColorSeek, FALSE);
3544                         curColor = ColorSeek;
3545                     }
3546                     continue;
3547             }
3548
3549           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3550
3551             if (looking_at(buf, &i, "\\   ")) {
3552                 if (prevColor != ColorNormal) {
3553                     if (oldi > next_out) {
3554                         SendToPlayer(&buf[next_out], oldi - next_out);
3555                         next_out = oldi;
3556                     }
3557                     Colorize(prevColor, TRUE);
3558                     curColor = prevColor;
3559                 }
3560                 if (savingComment) {
3561                     parse_pos = i - oldi;
3562                     memcpy(parse, &buf[oldi], parse_pos);
3563                     parse[parse_pos] = NULLCHAR;
3564                     started = STARTED_COMMENT;
3565                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3566                         chattingPartner = savingComment - 3; // kludge to remember the box
3567                 } else {
3568                     started = STARTED_CHATTER;
3569                 }
3570                 continue;
3571             }
3572
3573             if (looking_at(buf, &i, "Black Strength :") ||
3574                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3575                 looking_at(buf, &i, "<10>") ||
3576                 looking_at(buf, &i, "#@#")) {
3577                 /* Wrong board style */
3578                 loggedOn = TRUE;
3579                 SendToICS(ics_prefix);
3580                 SendToICS("set style 12\n");
3581                 SendToICS(ics_prefix);
3582                 SendToICS("refresh\n");
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "login:")) {
3587               if (!have_sent_ICS_logon) {
3588                 if(ICSInitScript())
3589                   have_sent_ICS_logon = 1;
3590                 else // no init script was found
3591                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3592               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3593                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3594               }
3595                 continue;
3596             }
3597
3598             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3599                 (looking_at(buf, &i, "\n<12> ") ||
3600                  looking_at(buf, &i, "<12> "))) {
3601                 loggedOn = TRUE;
3602                 if (oldi > next_out) {
3603                     SendToPlayer(&buf[next_out], oldi - next_out);
3604                 }
3605                 next_out = i;
3606                 started = STARTED_BOARD;
3607                 parse_pos = 0;
3608                 continue;
3609             }
3610
3611             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3612                 looking_at(buf, &i, "<b1> ")) {
3613                 if (oldi > next_out) {
3614                     SendToPlayer(&buf[next_out], oldi - next_out);
3615                 }
3616                 next_out = i;
3617                 started = STARTED_HOLDINGS;
3618                 parse_pos = 0;
3619                 continue;
3620             }
3621
3622             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3623                 loggedOn = TRUE;
3624                 /* Header for a move list -- first line */
3625
3626                 switch (ics_getting_history) {
3627                   case H_FALSE:
3628                     switch (gameMode) {
3629                       case IcsIdle:
3630                       case BeginningOfGame:
3631                         /* User typed "moves" or "oldmoves" while we
3632                            were idle.  Pretend we asked for these
3633                            moves and soak them up so user can step
3634                            through them and/or save them.
3635                            */
3636                         Reset(FALSE, TRUE);
3637                         gameMode = IcsObserving;
3638                         ModeHighlight();
3639                         ics_gamenum = -1;
3640                         ics_getting_history = H_GOT_UNREQ_HEADER;
3641                         break;
3642                       case EditGame: /*?*/
3643                       case EditPosition: /*?*/
3644                         /* Should above feature work in these modes too? */
3645                         /* For now it doesn't */
3646                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3647                         break;
3648                       default:
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                     }
3652                     break;
3653                   case H_REQUESTED:
3654                     /* Is this the right one? */
3655                     if (gameInfo.white && gameInfo.black &&
3656                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3657                         strcmp(gameInfo.black, star_match[2]) == 0) {
3658                         /* All is well */
3659                         ics_getting_history = H_GOT_REQ_HEADER;
3660                     }
3661                     break;
3662                   case H_GOT_REQ_HEADER:
3663                   case H_GOT_UNREQ_HEADER:
3664                   case H_GOT_UNWANTED_HEADER:
3665                   case H_GETTING_MOVES:
3666                     /* Should not happen */
3667                     DisplayError(_("Error gathering move list: two headers"), 0);
3668                     ics_getting_history = H_FALSE;
3669                     break;
3670                 }
3671
3672                 /* Save player ratings into gameInfo if needed */
3673                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3674                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3675                     (gameInfo.whiteRating == -1 ||
3676                      gameInfo.blackRating == -1)) {
3677
3678                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3679                     gameInfo.blackRating = string_to_rating(star_match[3]);
3680                     if (appData.debugMode)
3681                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3682                               gameInfo.whiteRating, gameInfo.blackRating);
3683                 }
3684                 continue;
3685             }
3686
3687             if (looking_at(buf, &i,
3688               "* * match, initial time: * minute*, increment: * second")) {
3689                 /* Header for a move list -- second line */
3690                 /* Initial board will follow if this is a wild game */
3691                 if (gameInfo.event != NULL) free(gameInfo.event);
3692                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3693                 gameInfo.event = StrSave(str);
3694                 /* [HGM] we switched variant. Translate boards if needed. */
3695                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3696                 continue;
3697             }
3698
3699             if (looking_at(buf, &i, "Move  ")) {
3700                 /* Beginning of a move list */
3701                 switch (ics_getting_history) {
3702                   case H_FALSE:
3703                     /* Normally should not happen */
3704                     /* Maybe user hit reset while we were parsing */
3705                     break;
3706                   case H_REQUESTED:
3707                     /* Happens if we are ignoring a move list that is not
3708                      * the one we just requested.  Common if the user
3709                      * tries to observe two games without turning off
3710                      * getMoveList */
3711                     break;
3712                   case H_GETTING_MOVES:
3713                     /* Should not happen */
3714                     DisplayError(_("Error gathering move list: nested"), 0);
3715                     ics_getting_history = H_FALSE;
3716                     break;
3717                   case H_GOT_REQ_HEADER:
3718                     ics_getting_history = H_GETTING_MOVES;
3719                     started = STARTED_MOVES;
3720                     parse_pos = 0;
3721                     if (oldi > next_out) {
3722                         SendToPlayer(&buf[next_out], oldi - next_out);
3723                     }
3724                     break;
3725                   case H_GOT_UNREQ_HEADER:
3726                     ics_getting_history = H_GETTING_MOVES;
3727                     started = STARTED_MOVES_NOHIDE;
3728                     parse_pos = 0;
3729                     break;
3730                   case H_GOT_UNWANTED_HEADER:
3731                     ics_getting_history = H_FALSE;
3732                     break;
3733                 }
3734                 continue;
3735             }
3736
3737             if (looking_at(buf, &i, "% ") ||
3738                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3739                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3740                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3741                     soughtPending = FALSE;
3742                     seekGraphUp = TRUE;
3743                     DrawSeekGraph();
3744                 }
3745                 if(suppressKibitz) next_out = i;
3746                 savingComment = FALSE;
3747                 suppressKibitz = 0;
3748                 switch (started) {
3749                   case STARTED_MOVES:
3750                   case STARTED_MOVES_NOHIDE:
3751                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3752                     parse[parse_pos + i - oldi] = NULLCHAR;
3753                     ParseGameHistory(parse);
3754 #if ZIPPY
3755                     if (appData.zippyPlay && first.initDone) {
3756                         FeedMovesToProgram(&first, forwardMostMove);
3757                         if (gameMode == IcsPlayingWhite) {
3758                             if (WhiteOnMove(forwardMostMove)) {
3759                                 if (first.sendTime) {
3760                                   if (first.useColors) {
3761                                     SendToProgram("black\n", &first);
3762                                   }
3763                                   SendTimeRemaining(&first, TRUE);
3764                                 }
3765                                 if (first.useColors) {
3766                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3767                                 }
3768                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3769                                 first.maybeThinking = TRUE;
3770                             } else {
3771                                 if (first.usePlayother) {
3772                                   if (first.sendTime) {
3773                                     SendTimeRemaining(&first, TRUE);
3774                                   }
3775                                   SendToProgram("playother\n", &first);
3776                                   firstMove = FALSE;
3777                                 } else {
3778                                   firstMove = TRUE;
3779                                 }
3780                             }
3781                         } else if (gameMode == IcsPlayingBlack) {
3782                             if (!WhiteOnMove(forwardMostMove)) {
3783                                 if (first.sendTime) {
3784                                   if (first.useColors) {
3785                                     SendToProgram("white\n", &first);
3786                                   }
3787                                   SendTimeRemaining(&first, FALSE);
3788                                 }
3789                                 if (first.useColors) {
3790                                   SendToProgram("black\n", &first);
3791                                 }
3792                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3793                                 first.maybeThinking = TRUE;
3794                             } else {
3795                                 if (first.usePlayother) {
3796                                   if (first.sendTime) {
3797                                     SendTimeRemaining(&first, FALSE);
3798                                   }
3799                                   SendToProgram("playother\n", &first);
3800                                   firstMove = FALSE;
3801                                 } else {
3802                                   firstMove = TRUE;
3803                                 }
3804                             }
3805                         }
3806                     }
3807 #endif
3808                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3809                         /* Moves came from oldmoves or moves command
3810                            while we weren't doing anything else.
3811                            */
3812                         currentMove = forwardMostMove;
3813                         ClearHighlights();/*!!could figure this out*/
3814                         flipView = appData.flipView;
3815                         DrawPosition(TRUE, boards[currentMove]);
3816                         DisplayBothClocks();
3817                         snprintf(str, MSG_SIZ, "%s %s %s",
3818                                 gameInfo.white, _("vs."),  gameInfo.black);
3819                         DisplayTitle(str);
3820                         gameMode = IcsIdle;
3821                     } else {
3822                         /* Moves were history of an active game */
3823                         if (gameInfo.resultDetails != NULL) {
3824                             free(gameInfo.resultDetails);
3825                             gameInfo.resultDetails = NULL;
3826                         }
3827                     }
3828                     HistorySet(parseList, backwardMostMove,
3829                                forwardMostMove, currentMove-1);
3830                     DisplayMove(currentMove - 1);
3831                     if (started == STARTED_MOVES) next_out = i;
3832                     started = STARTED_NONE;
3833                     ics_getting_history = H_FALSE;
3834                     break;
3835
3836                   case STARTED_OBSERVE:
3837                     started = STARTED_NONE;
3838                     SendToICS(ics_prefix);
3839                     SendToICS("refresh\n");
3840                     break;
3841
3842                   default:
3843                     break;
3844                 }
3845                 if(bookHit) { // [HGM] book: simulate book reply
3846                     static char bookMove[MSG_SIZ]; // a bit generous?
3847
3848                     programStats.nodes = programStats.depth = programStats.time =
3849                     programStats.score = programStats.got_only_move = 0;
3850                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3851
3852                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3853                     strcat(bookMove, bookHit);
3854                     HandleMachineMove(bookMove, &first);
3855                 }
3856                 continue;
3857             }
3858
3859             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3860                  started == STARTED_HOLDINGS ||
3861                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3862                 /* Accumulate characters in move list or board */
3863                 parse[parse_pos++] = buf[i];
3864             }
3865
3866             /* Start of game messages.  Mostly we detect start of game
3867                when the first board image arrives.  On some versions
3868                of the ICS, though, we need to do a "refresh" after starting
3869                to observe in order to get the current board right away. */
3870             if (looking_at(buf, &i, "Adding game * to observation list")) {
3871                 started = STARTED_OBSERVE;
3872                 continue;
3873             }
3874
3875             /* Handle auto-observe */
3876             if (appData.autoObserve &&
3877                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3878                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3879                 char *player;
3880                 /* Choose the player that was highlighted, if any. */
3881                 if (star_match[0][0] == '\033' ||
3882                     star_match[1][0] != '\033') {
3883                     player = star_match[0];
3884                 } else {
3885                     player = star_match[2];
3886                 }
3887                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3888                         ics_prefix, StripHighlightAndTitle(player));
3889                 SendToICS(str);
3890
3891                 /* Save ratings from notify string */
3892                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3893                 player1Rating = string_to_rating(star_match[1]);
3894                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3895                 player2Rating = string_to_rating(star_match[3]);
3896
3897                 if (appData.debugMode)
3898                   fprintf(debugFP,
3899                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3900                           player1Name, player1Rating,
3901                           player2Name, player2Rating);
3902
3903                 continue;
3904             }
3905
3906             /* Deal with automatic examine mode after a game,
3907                and with IcsObserving -> IcsExamining transition */
3908             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3909                 looking_at(buf, &i, "has made you an examiner of game *")) {
3910
3911                 int gamenum = atoi(star_match[0]);
3912                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3913                     gamenum == ics_gamenum) {
3914                     /* We were already playing or observing this game;
3915                        no need to refetch history */
3916                     gameMode = IcsExamining;
3917                     if (pausing) {
3918                         pauseExamForwardMostMove = forwardMostMove;
3919                     } else if (currentMove < forwardMostMove) {
3920                         ForwardInner(forwardMostMove);
3921                     }
3922                 } else {
3923                     /* I don't think this case really can happen */
3924                     SendToICS(ics_prefix);
3925                     SendToICS("refresh\n");
3926                 }
3927                 continue;
3928             }
3929
3930             /* Error messages */
3931 //          if (ics_user_moved) {
3932             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3933                 if (looking_at(buf, &i, "Illegal move") ||
3934                     looking_at(buf, &i, "Not a legal move") ||
3935                     looking_at(buf, &i, "Your king is in check") ||
3936                     looking_at(buf, &i, "It isn't your turn") ||
3937                     looking_at(buf, &i, "It is not your move")) {
3938                     /* Illegal move */
3939                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3940                         currentMove = forwardMostMove-1;
3941                         DisplayMove(currentMove - 1); /* before DMError */
3942                         DrawPosition(FALSE, boards[currentMove]);
3943                         SwitchClocks(forwardMostMove-1); // [HGM] race
3944                         DisplayBothClocks();
3945                     }
3946                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3947                     ics_user_moved = 0;
3948                     continue;
3949                 }
3950             }
3951
3952             if (looking_at(buf, &i, "still have time") ||
3953                 looking_at(buf, &i, "not out of time") ||
3954                 looking_at(buf, &i, "either player is out of time") ||
3955                 looking_at(buf, &i, "has timeseal; checking")) {
3956                 /* We must have called his flag a little too soon */
3957                 whiteFlag = blackFlag = FALSE;
3958                 continue;
3959             }
3960
3961             if (looking_at(buf, &i, "added * seconds to") ||
3962                 looking_at(buf, &i, "seconds were added to")) {
3963                 /* Update the clocks */
3964                 SendToICS(ics_prefix);
3965                 SendToICS("refresh\n");
3966                 continue;
3967             }
3968
3969             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3970                 ics_clock_paused = TRUE;
3971                 StopClocks();
3972                 continue;
3973             }
3974
3975             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3976                 ics_clock_paused = FALSE;
3977                 StartClocks();
3978                 continue;
3979             }
3980
3981             /* Grab player ratings from the Creating: message.
3982                Note we have to check for the special case when
3983                the ICS inserts things like [white] or [black]. */
3984             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3985                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3986                 /* star_matches:
3987                    0    player 1 name (not necessarily white)
3988                    1    player 1 rating
3989                    2    empty, white, or black (IGNORED)
3990                    3    player 2 name (not necessarily black)
3991                    4    player 2 rating
3992
3993                    The names/ratings are sorted out when the game
3994                    actually starts (below).
3995                 */
3996                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3997                 player1Rating = string_to_rating(star_match[1]);
3998                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3999                 player2Rating = string_to_rating(star_match[4]);
4000
4001                 if (appData.debugMode)
4002                   fprintf(debugFP,
4003                           "Ratings from 'Creating:' %s %d, %s %d\n",
4004                           player1Name, player1Rating,
4005                           player2Name, player2Rating);
4006
4007                 continue;
4008             }
4009
4010             /* Improved generic start/end-of-game messages */
4011             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4012                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4013                 /* If tkind == 0: */
4014                 /* star_match[0] is the game number */
4015                 /*           [1] is the white player's name */
4016                 /*           [2] is the black player's name */
4017                 /* For end-of-game: */
4018                 /*           [3] is the reason for the game end */
4019                 /*           [4] is a PGN end game-token, preceded by " " */
4020                 /* For start-of-game: */
4021                 /*           [3] begins with "Creating" or "Continuing" */
4022                 /*           [4] is " *" or empty (don't care). */
4023                 int gamenum = atoi(star_match[0]);
4024                 char *whitename, *blackname, *why, *endtoken;
4025                 ChessMove endtype = EndOfFile;
4026
4027                 if (tkind == 0) {
4028                   whitename = star_match[1];
4029                   blackname = star_match[2];
4030                   why = star_match[3];
4031                   endtoken = star_match[4];
4032                 } else {
4033                   whitename = star_match[1];
4034                   blackname = star_match[3];
4035                   why = star_match[5];
4036                   endtoken = star_match[6];
4037                 }
4038
4039                 /* Game start messages */
4040                 if (strncmp(why, "Creating ", 9) == 0 ||
4041                     strncmp(why, "Continuing ", 11) == 0) {
4042                     gs_gamenum = gamenum;
4043                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4044                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4045                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4046 #if ZIPPY
4047                     if (appData.zippyPlay) {
4048                         ZippyGameStart(whitename, blackname);
4049                     }
4050 #endif /*ZIPPY*/
4051                     partnerBoardValid = FALSE; // [HGM] bughouse
4052                     continue;
4053                 }
4054
4055                 /* Game end messages */
4056                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4057                     ics_gamenum != gamenum) {
4058                     continue;
4059                 }
4060                 while (endtoken[0] == ' ') endtoken++;
4061                 switch (endtoken[0]) {
4062                   case '*':
4063                   default:
4064                     endtype = GameUnfinished;
4065                     break;
4066                   case '0':
4067                     endtype = BlackWins;
4068                     break;
4069                   case '1':
4070                     if (endtoken[1] == '/')
4071                       endtype = GameIsDrawn;
4072                     else
4073                       endtype = WhiteWins;
4074                     break;
4075                 }
4076                 GameEnds(endtype, why, GE_ICS);
4077 #if ZIPPY
4078                 if (appData.zippyPlay && first.initDone) {
4079                     ZippyGameEnd(endtype, why);
4080                     if (first.pr == NoProc) {
4081                       /* Start the next process early so that we'll
4082                          be ready for the next challenge */
4083                       StartChessProgram(&first);
4084                     }
4085                     /* Send "new" early, in case this command takes
4086                        a long time to finish, so that we'll be ready
4087                        for the next challenge. */
4088                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4089                     Reset(TRUE, TRUE);
4090                 }
4091 #endif /*ZIPPY*/
4092                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4093                 continue;
4094             }
4095
4096             if (looking_at(buf, &i, "Removing game * from observation") ||
4097                 looking_at(buf, &i, "no longer observing game *") ||
4098                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4099                 if (gameMode == IcsObserving &&
4100                     atoi(star_match[0]) == ics_gamenum)
4101                   {
4102                       /* icsEngineAnalyze */
4103                       if (appData.icsEngineAnalyze) {
4104                             ExitAnalyzeMode();
4105                             ModeHighlight();
4106                       }
4107                       StopClocks();
4108                       gameMode = IcsIdle;
4109                       ics_gamenum = -1;
4110                       ics_user_moved = FALSE;
4111                   }
4112                 continue;
4113             }
4114
4115             if (looking_at(buf, &i, "no longer examining game *")) {
4116                 if (gameMode == IcsExamining &&
4117                     atoi(star_match[0]) == ics_gamenum)
4118                   {
4119                       gameMode = IcsIdle;
4120                       ics_gamenum = -1;
4121                       ics_user_moved = FALSE;
4122                   }
4123                 continue;
4124             }
4125
4126             /* Advance leftover_start past any newlines we find,
4127                so only partial lines can get reparsed */
4128             if (looking_at(buf, &i, "\n")) {
4129                 prevColor = curColor;
4130                 if (curColor != ColorNormal) {
4131                     if (oldi > next_out) {
4132                         SendToPlayer(&buf[next_out], oldi - next_out);
4133                         next_out = oldi;
4134                     }
4135                     Colorize(ColorNormal, FALSE);
4136                     curColor = ColorNormal;
4137                 }
4138                 if (started == STARTED_BOARD) {
4139                     started = STARTED_NONE;
4140                     parse[parse_pos] = NULLCHAR;
4141                     ParseBoard12(parse);
4142                     ics_user_moved = 0;
4143
4144                     /* Send premove here */
4145                     if (appData.premove) {
4146                       char str[MSG_SIZ];
4147                       if (currentMove == 0 &&
4148                           gameMode == IcsPlayingWhite &&
4149                           appData.premoveWhite) {
4150                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4151                         if (appData.debugMode)
4152                           fprintf(debugFP, "Sending premove:\n");
4153                         SendToICS(str);
4154                       } else if (currentMove == 1 &&
4155                                  gameMode == IcsPlayingBlack &&
4156                                  appData.premoveBlack) {
4157                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4158                         if (appData.debugMode)
4159                           fprintf(debugFP, "Sending premove:\n");
4160                         SendToICS(str);
4161                       } else if (gotPremove) {
4162                         gotPremove = 0;
4163                         ClearPremoveHighlights();
4164                         if (appData.debugMode)
4165                           fprintf(debugFP, "Sending premove:\n");
4166                           UserMoveEvent(premoveFromX, premoveFromY,
4167                                         premoveToX, premoveToY,
4168                                         premovePromoChar);
4169                       }
4170                     }
4171
4172                     /* Usually suppress following prompt */
4173                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4174                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4175                         if (looking_at(buf, &i, "*% ")) {
4176                             savingComment = FALSE;
4177                             suppressKibitz = 0;
4178                         }
4179                     }
4180                     next_out = i;
4181                 } else if (started == STARTED_HOLDINGS) {
4182                     int gamenum;
4183                     char new_piece[MSG_SIZ];
4184                     started = STARTED_NONE;
4185                     parse[parse_pos] = NULLCHAR;
4186                     if (appData.debugMode)
4187                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4188                                                         parse, currentMove);
4189                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4190                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4191                         if (gameInfo.variant == VariantNormal) {
4192                           /* [HGM] We seem to switch variant during a game!
4193                            * Presumably no holdings were displayed, so we have
4194                            * to move the position two files to the right to
4195                            * create room for them!
4196                            */
4197                           VariantClass newVariant;
4198                           switch(gameInfo.boardWidth) { // base guess on board width
4199                                 case 9:  newVariant = VariantShogi; break;
4200                                 case 10: newVariant = VariantGreat; break;
4201                                 default: newVariant = VariantCrazyhouse; break;
4202                           }
4203                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4204                           /* Get a move list just to see the header, which
4205                              will tell us whether this is really bug or zh */
4206                           if (ics_getting_history == H_FALSE) {
4207                             ics_getting_history = H_REQUESTED;
4208                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4209                             SendToICS(str);
4210                           }
4211                         }
4212                         new_piece[0] = NULLCHAR;
4213                         sscanf(parse, "game %d white [%s black [%s <- %s",
4214                                &gamenum, white_holding, black_holding,
4215                                new_piece);
4216                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4217                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4218                         /* [HGM] copy holdings to board holdings area */
4219                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4220                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4221                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4222 #if ZIPPY
4223                         if (appData.zippyPlay && first.initDone) {
4224                             ZippyHoldings(white_holding, black_holding,
4225                                           new_piece);
4226                         }
4227 #endif /*ZIPPY*/
4228                         if (tinyLayout || smallLayout) {
4229                             char wh[16], bh[16];
4230                             PackHolding(wh, white_holding);
4231                             PackHolding(bh, black_holding);
4232                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4233                                     gameInfo.white, gameInfo.black);
4234                         } else {
4235                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4236                                     gameInfo.white, white_holding, _("vs."),
4237                                     gameInfo.black, black_holding);
4238                         }
4239                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4240                         DrawPosition(FALSE, boards[currentMove]);
4241                         DisplayTitle(str);
4242                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4243                         sscanf(parse, "game %d white [%s black [%s <- %s",
4244                                &gamenum, white_holding, black_holding,
4245                                new_piece);
4246                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4247                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4248                         /* [HGM] copy holdings to partner-board holdings area */
4249                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4250                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4251                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4252                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4253                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4254                       }
4255                     }
4256                     /* Suppress following prompt */
4257                     if (looking_at(buf, &i, "*% ")) {
4258                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4259                         savingComment = FALSE;
4260                         suppressKibitz = 0;
4261                     }
4262                     next_out = i;
4263                 }
4264                 continue;
4265             }
4266
4267             i++;                /* skip unparsed character and loop back */
4268         }
4269
4270         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4271 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4272 //          SendToPlayer(&buf[next_out], i - next_out);
4273             started != STARTED_HOLDINGS && leftover_start > next_out) {
4274             SendToPlayer(&buf[next_out], leftover_start - next_out);
4275             next_out = i;
4276         }
4277
4278         leftover_len = buf_len - leftover_start;
4279         /* if buffer ends with something we couldn't parse,
4280            reparse it after appending the next read */
4281
4282     } else if (count == 0) {
4283         RemoveInputSource(isr);
4284         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4285     } else {
4286         DisplayFatalError(_("Error reading from ICS"), error, 1);
4287     }
4288 }
4289
4290
4291 /* Board style 12 looks like this:
4292
4293    <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
4294
4295  * The "<12> " is stripped before it gets to this routine.  The two
4296  * trailing 0's (flip state and clock ticking) are later addition, and
4297  * some chess servers may not have them, or may have only the first.
4298  * Additional trailing fields may be added in the future.
4299  */
4300
4301 #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"
4302
4303 #define RELATION_OBSERVING_PLAYED    0
4304 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4305 #define RELATION_PLAYING_MYMOVE      1
4306 #define RELATION_PLAYING_NOTMYMOVE  -1
4307 #define RELATION_EXAMINING           2
4308 #define RELATION_ISOLATED_BOARD     -3
4309 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4310
4311 void
4312 ParseBoard12 (char *string)
4313 {
4314 #if ZIPPY
4315     int i, takeback;
4316     char *bookHit = NULL; // [HGM] book
4317 #endif
4318     GameMode newGameMode;
4319     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4320     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4321     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4322     char to_play, board_chars[200];
4323     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4324     char black[32], white[32];
4325     Board board;
4326     int prevMove = currentMove;
4327     int ticking = 2;
4328     ChessMove moveType;
4329     int fromX, fromY, toX, toY;
4330     char promoChar;
4331     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4332     Boolean weird = FALSE, reqFlag = FALSE;
4333
4334     fromX = fromY = toX = toY = -1;
4335
4336     newGame = FALSE;
4337
4338     if (appData.debugMode)
4339       fprintf(debugFP, "Parsing board: %s\n", string);
4340
4341     move_str[0] = NULLCHAR;
4342     elapsed_time[0] = NULLCHAR;
4343     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4344         int  i = 0, j;
4345         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4346             if(string[i] == ' ') { ranks++; files = 0; }
4347             else files++;
4348             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4349             i++;
4350         }
4351         for(j = 0; j <i; j++) board_chars[j] = string[j];
4352         board_chars[i] = '\0';
4353         string += i + 1;
4354     }
4355     n = sscanf(string, PATTERN, &to_play, &double_push,
4356                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4357                &gamenum, white, black, &relation, &basetime, &increment,
4358                &white_stren, &black_stren, &white_time, &black_time,
4359                &moveNum, str, elapsed_time, move_str, &ics_flip,
4360                &ticking);
4361
4362     if (n < 21) {
4363         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4364         DisplayError(str, 0);
4365         return;
4366     }
4367
4368     /* Convert the move number to internal form */
4369     moveNum = (moveNum - 1) * 2;
4370     if (to_play == 'B') moveNum++;
4371     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4372       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4373                         0, 1);
4374       return;
4375     }
4376
4377     switch (relation) {
4378       case RELATION_OBSERVING_PLAYED:
4379       case RELATION_OBSERVING_STATIC:
4380         if (gamenum == -1) {
4381             /* Old ICC buglet */
4382             relation = RELATION_OBSERVING_STATIC;
4383         }
4384         newGameMode = IcsObserving;
4385         break;
4386       case RELATION_PLAYING_MYMOVE:
4387       case RELATION_PLAYING_NOTMYMOVE:
4388         newGameMode =
4389           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4390             IcsPlayingWhite : IcsPlayingBlack;
4391         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4392         break;
4393       case RELATION_EXAMINING:
4394         newGameMode = IcsExamining;
4395         break;
4396       case RELATION_ISOLATED_BOARD:
4397       default:
4398         /* Just display this board.  If user was doing something else,
4399            we will forget about it until the next board comes. */
4400         newGameMode = IcsIdle;
4401         break;
4402       case RELATION_STARTING_POSITION:
4403         newGameMode = gameMode;
4404         break;
4405     }
4406
4407     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4408         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4409          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4410       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4411       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4412       static int lastBgGame = -1;
4413       char *toSqr;
4414       for (k = 0; k < ranks; k++) {
4415         for (j = 0; j < files; j++)
4416           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4417         if(gameInfo.holdingsWidth > 1) {
4418              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4419              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4420         }
4421       }
4422       CopyBoard(partnerBoard, board);
4423       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4424         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4425         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4426       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4427       if(toSqr = strchr(str, '-')) {
4428         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4429         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4430       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4431       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4432       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4433       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4434       if(twoBoards) {
4435           DisplayWhiteClock(white_time*fac, to_play == 'W');
4436           DisplayBlackClock(black_time*fac, to_play != 'W');
4437           activePartner = to_play;
4438           if(gamenum != lastBgGame) {
4439               char buf[MSG_SIZ];
4440               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4441               DisplayTitle(buf);
4442           }
4443           lastBgGame = gamenum;
4444           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4445                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4446       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4447                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4448       if(!twoBoards) DisplayMessage(partnerStatus, "");
4449         partnerBoardValid = TRUE;
4450       return;
4451     }
4452
4453     if(appData.dualBoard && appData.bgObserve) {
4454         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4455             SendToICS(ics_prefix), SendToICS("pobserve\n");
4456         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4457             char buf[MSG_SIZ];
4458             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4459             SendToICS(buf);
4460         }
4461     }
4462
4463     /* Modify behavior for initial board display on move listing
4464        of wild games.
4465        */
4466     switch (ics_getting_history) {
4467       case H_FALSE:
4468       case H_REQUESTED:
4469         break;
4470       case H_GOT_REQ_HEADER:
4471       case H_GOT_UNREQ_HEADER:
4472         /* This is the initial position of the current game */
4473         gamenum = ics_gamenum;
4474         moveNum = 0;            /* old ICS bug workaround */
4475         if (to_play == 'B') {
4476           startedFromSetupPosition = TRUE;
4477           blackPlaysFirst = TRUE;
4478           moveNum = 1;
4479           if (forwardMostMove == 0) forwardMostMove = 1;
4480           if (backwardMostMove == 0) backwardMostMove = 1;
4481           if (currentMove == 0) currentMove = 1;
4482         }
4483         newGameMode = gameMode;
4484         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4485         break;
4486       case H_GOT_UNWANTED_HEADER:
4487         /* This is an initial board that we don't want */
4488         return;
4489       case H_GETTING_MOVES:
4490         /* Should not happen */
4491         DisplayError(_("Error gathering move list: extra board"), 0);
4492         ics_getting_history = H_FALSE;
4493         return;
4494     }
4495
4496    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4497                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4498                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4499      /* [HGM] We seem to have switched variant unexpectedly
4500       * Try to guess new variant from board size
4501       */
4502           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4503           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4504           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4505           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4506           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4507           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4508           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4509           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4510           /* Get a move list just to see the header, which
4511              will tell us whether this is really bug or zh */
4512           if (ics_getting_history == H_FALSE) {
4513             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4514             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4515             SendToICS(str);
4516           }
4517     }
4518
4519     /* Take action if this is the first board of a new game, or of a
4520        different game than is currently being displayed.  */
4521     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4522         relation == RELATION_ISOLATED_BOARD) {
4523
4524         /* Forget the old game and get the history (if any) of the new one */
4525         if (gameMode != BeginningOfGame) {
4526           Reset(TRUE, TRUE);
4527         }
4528         newGame = TRUE;
4529         if (appData.autoRaiseBoard) BoardToTop();
4530         prevMove = -3;
4531         if (gamenum == -1) {
4532             newGameMode = IcsIdle;
4533         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4534                    appData.getMoveList && !reqFlag) {
4535             /* Need to get game history */
4536             ics_getting_history = H_REQUESTED;
4537             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4538             SendToICS(str);
4539         }
4540
4541         /* Initially flip the board to have black on the bottom if playing
4542            black or if the ICS flip flag is set, but let the user change
4543            it with the Flip View button. */
4544         flipView = appData.autoFlipView ?
4545           (newGameMode == IcsPlayingBlack) || ics_flip :
4546           appData.flipView;
4547
4548         /* Done with values from previous mode; copy in new ones */
4549         gameMode = newGameMode;
4550         ModeHighlight();
4551         ics_gamenum = gamenum;
4552         if (gamenum == gs_gamenum) {
4553             int klen = strlen(gs_kind);
4554             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4555             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4556             gameInfo.event = StrSave(str);
4557         } else {
4558             gameInfo.event = StrSave("ICS game");
4559         }
4560         gameInfo.site = StrSave(appData.icsHost);
4561         gameInfo.date = PGNDate();
4562         gameInfo.round = StrSave("-");
4563         gameInfo.white = StrSave(white);
4564         gameInfo.black = StrSave(black);
4565         timeControl = basetime * 60 * 1000;
4566         timeControl_2 = 0;
4567         timeIncrement = increment * 1000;
4568         movesPerSession = 0;
4569         gameInfo.timeControl = TimeControlTagValue();
4570         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4571   if (appData.debugMode) {
4572     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4573     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4574     setbuf(debugFP, NULL);
4575   }
4576
4577         gameInfo.outOfBook = NULL;
4578
4579         /* Do we have the ratings? */
4580         if (strcmp(player1Name, white) == 0 &&
4581             strcmp(player2Name, black) == 0) {
4582             if (appData.debugMode)
4583               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4584                       player1Rating, player2Rating);
4585             gameInfo.whiteRating = player1Rating;
4586             gameInfo.blackRating = player2Rating;
4587         } else if (strcmp(player2Name, white) == 0 &&
4588                    strcmp(player1Name, black) == 0) {
4589             if (appData.debugMode)
4590               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4591                       player2Rating, player1Rating);
4592             gameInfo.whiteRating = player2Rating;
4593             gameInfo.blackRating = player1Rating;
4594         }
4595         player1Name[0] = player2Name[0] = NULLCHAR;
4596
4597         /* Silence shouts if requested */
4598         if (appData.quietPlay &&
4599             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4600             SendToICS(ics_prefix);
4601             SendToICS("set shout 0\n");
4602         }
4603     }
4604
4605     /* Deal with midgame name changes */
4606     if (!newGame) {
4607         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4608             if (gameInfo.white) free(gameInfo.white);
4609             gameInfo.white = StrSave(white);
4610         }
4611         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4612             if (gameInfo.black) free(gameInfo.black);
4613             gameInfo.black = StrSave(black);
4614         }
4615     }
4616
4617     /* Throw away game result if anything actually changes in examine mode */
4618     if (gameMode == IcsExamining && !newGame) {
4619         gameInfo.result = GameUnfinished;
4620         if (gameInfo.resultDetails != NULL) {
4621             free(gameInfo.resultDetails);
4622             gameInfo.resultDetails = NULL;
4623         }
4624     }
4625
4626     /* In pausing && IcsExamining mode, we ignore boards coming
4627        in if they are in a different variation than we are. */
4628     if (pauseExamInvalid) return;
4629     if (pausing && gameMode == IcsExamining) {
4630         if (moveNum <= pauseExamForwardMostMove) {
4631             pauseExamInvalid = TRUE;
4632             forwardMostMove = pauseExamForwardMostMove;
4633             return;
4634         }
4635     }
4636
4637   if (appData.debugMode) {
4638     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4639   }
4640     /* Parse the board */
4641     for (k = 0; k < ranks; k++) {
4642       for (j = 0; j < files; j++)
4643         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4644       if(gameInfo.holdingsWidth > 1) {
4645            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4646            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4647       }
4648     }
4649     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4650       board[5][BOARD_RGHT+1] = WhiteAngel;
4651       board[6][BOARD_RGHT+1] = WhiteMarshall;
4652       board[1][0] = BlackMarshall;
4653       board[2][0] = BlackAngel;
4654       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4655     }
4656     CopyBoard(boards[moveNum], board);
4657     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4658     if (moveNum == 0) {
4659         startedFromSetupPosition =
4660           !CompareBoards(board, initialPosition);
4661         if(startedFromSetupPosition)
4662             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4663     }
4664
4665     /* [HGM] Set castling rights. Take the outermost Rooks,
4666        to make it also work for FRC opening positions. Note that board12
4667        is really defective for later FRC positions, as it has no way to
4668        indicate which Rook can castle if they are on the same side of King.
4669        For the initial position we grant rights to the outermost Rooks,
4670        and remember thos rights, and we then copy them on positions
4671        later in an FRC game. This means WB might not recognize castlings with
4672        Rooks that have moved back to their original position as illegal,
4673        but in ICS mode that is not its job anyway.
4674     */
4675     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4676     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4677
4678         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4679             if(board[0][i] == WhiteRook) j = i;
4680         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4681         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4685             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4686         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690
4691         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4692         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4694             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4696             if(board[BOARD_HEIGHT-1][k] == bKing)
4697                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4698         if(gameInfo.variant == VariantTwoKings) {
4699             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4700             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4701             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4702         }
4703     } else { int r;
4704         r = boards[moveNum][CASTLING][0] = initialRights[0];
4705         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4706         r = boards[moveNum][CASTLING][1] = initialRights[1];
4707         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4708         r = boards[moveNum][CASTLING][3] = initialRights[3];
4709         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4710         r = boards[moveNum][CASTLING][4] = initialRights[4];
4711         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4712         /* wildcastle kludge: always assume King has rights */
4713         r = boards[moveNum][CASTLING][2] = initialRights[2];
4714         r = boards[moveNum][CASTLING][5] = initialRights[5];
4715     }
4716     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4717     boards[moveNum][EP_STATUS] = EP_NONE;
4718     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4719     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4720     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4721
4722
4723     if (ics_getting_history == H_GOT_REQ_HEADER ||
4724         ics_getting_history == H_GOT_UNREQ_HEADER) {
4725         /* This was an initial position from a move list, not
4726            the current position */
4727         return;
4728     }
4729
4730     /* Update currentMove and known move number limits */
4731     newMove = newGame || moveNum > forwardMostMove;
4732
4733     if (newGame) {
4734         forwardMostMove = backwardMostMove = currentMove = moveNum;
4735         if (gameMode == IcsExamining && moveNum == 0) {
4736           /* Workaround for ICS limitation: we are not told the wild
4737              type when starting to examine a game.  But if we ask for
4738              the move list, the move list header will tell us */
4739             ics_getting_history = H_REQUESTED;
4740             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4741             SendToICS(str);
4742         }
4743     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4744                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4745 #if ZIPPY
4746         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4747         /* [HGM] applied this also to an engine that is silently watching        */
4748         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4749             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4750             gameInfo.variant == currentlyInitializedVariant) {
4751           takeback = forwardMostMove - moveNum;
4752           for (i = 0; i < takeback; i++) {
4753             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4754             SendToProgram("undo\n", &first);
4755           }
4756         }
4757 #endif
4758
4759         forwardMostMove = moveNum;
4760         if (!pausing || currentMove > forwardMostMove)
4761           currentMove = forwardMostMove;
4762     } else {
4763         /* New part of history that is not contiguous with old part */
4764         if (pausing && gameMode == IcsExamining) {
4765             pauseExamInvalid = TRUE;
4766             forwardMostMove = pauseExamForwardMostMove;
4767             return;
4768         }
4769         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4770 #if ZIPPY
4771             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4772                 // [HGM] when we will receive the move list we now request, it will be
4773                 // fed to the engine from the first move on. So if the engine is not
4774                 // in the initial position now, bring it there.
4775                 InitChessProgram(&first, 0);
4776             }
4777 #endif
4778             ics_getting_history = H_REQUESTED;
4779             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4780             SendToICS(str);
4781         }
4782         forwardMostMove = backwardMostMove = currentMove = moveNum;
4783     }
4784
4785     /* Update the clocks */
4786     if (strchr(elapsed_time, '.')) {
4787       /* Time is in ms */
4788       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4789       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4790     } else {
4791       /* Time is in seconds */
4792       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4793       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4794     }
4795
4796
4797 #if ZIPPY
4798     if (appData.zippyPlay && newGame &&
4799         gameMode != IcsObserving && gameMode != IcsIdle &&
4800         gameMode != IcsExamining)
4801       ZippyFirstBoard(moveNum, basetime, increment);
4802 #endif
4803
4804     /* Put the move on the move list, first converting
4805        to canonical algebraic form. */
4806     if (moveNum > 0) {
4807   if (appData.debugMode) {
4808     int f = forwardMostMove;
4809     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4810             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4811             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4812     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4813     fprintf(debugFP, "moveNum = %d\n", moveNum);
4814     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4815     setbuf(debugFP, NULL);
4816   }
4817         if (moveNum <= backwardMostMove) {
4818             /* We don't know what the board looked like before
4819                this move.  Punt. */
4820           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4821             strcat(parseList[moveNum - 1], " ");
4822             strcat(parseList[moveNum - 1], elapsed_time);
4823             moveList[moveNum - 1][0] = NULLCHAR;
4824         } else if (strcmp(move_str, "none") == 0) {
4825             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4826             /* Again, we don't know what the board looked like;
4827                this is really the start of the game. */
4828             parseList[moveNum - 1][0] = NULLCHAR;
4829             moveList[moveNum - 1][0] = NULLCHAR;
4830             backwardMostMove = moveNum;
4831             startedFromSetupPosition = TRUE;
4832             fromX = fromY = toX = toY = -1;
4833         } else {
4834           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4835           //                 So we parse the long-algebraic move string in stead of the SAN move
4836           int valid; char buf[MSG_SIZ], *prom;
4837
4838           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4839                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4840           // str looks something like "Q/a1-a2"; kill the slash
4841           if(str[1] == '/')
4842             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4843           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4844           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4845                 strcat(buf, prom); // long move lacks promo specification!
4846           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4847                 if(appData.debugMode)
4848                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4849                 safeStrCpy(move_str, buf, MSG_SIZ);
4850           }
4851           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4852                                 &fromX, &fromY, &toX, &toY, &promoChar)
4853                || ParseOneMove(buf, moveNum - 1, &moveType,
4854                                 &fromX, &fromY, &toX, &toY, &promoChar);
4855           // end of long SAN patch
4856           if (valid) {
4857             (void) CoordsToAlgebraic(boards[moveNum - 1],
4858                                      PosFlags(moveNum - 1),
4859                                      fromY, fromX, toY, toX, promoChar,
4860                                      parseList[moveNum-1]);
4861             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4862               case MT_NONE:
4863               case MT_STALEMATE:
4864               default:
4865                 break;
4866               case MT_CHECK:
4867                 if(!IS_SHOGI(gameInfo.variant))
4868                     strcat(parseList[moveNum - 1], "+");
4869                 break;
4870               case MT_CHECKMATE:
4871               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4872                 strcat(parseList[moveNum - 1], "#");
4873                 break;
4874             }
4875             strcat(parseList[moveNum - 1], " ");
4876             strcat(parseList[moveNum - 1], elapsed_time);
4877             /* currentMoveString is set as a side-effect of ParseOneMove */
4878             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4879             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4880             strcat(moveList[moveNum - 1], "\n");
4881
4882             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4883                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4884               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4885                 ChessSquare old, new = boards[moveNum][k][j];
4886                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4887                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4888                   if(old == new) continue;
4889                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4890                   else if(new == WhiteWazir || new == BlackWazir) {
4891                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4892                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4893                       else boards[moveNum][k][j] = old; // preserve type of Gold
4894                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4895                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4896               }
4897           } else {
4898             /* Move from ICS was illegal!?  Punt. */
4899             if (appData.debugMode) {
4900               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4901               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4902             }
4903             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4904             strcat(parseList[moveNum - 1], " ");
4905             strcat(parseList[moveNum - 1], elapsed_time);
4906             moveList[moveNum - 1][0] = NULLCHAR;
4907             fromX = fromY = toX = toY = -1;
4908           }
4909         }
4910   if (appData.debugMode) {
4911     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4912     setbuf(debugFP, NULL);
4913   }
4914
4915 #if ZIPPY
4916         /* Send move to chess program (BEFORE animating it). */
4917         if (appData.zippyPlay && !newGame && newMove &&
4918            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4919
4920             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4921                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4922                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4923                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4924                             move_str);
4925                     DisplayError(str, 0);
4926                 } else {
4927                     if (first.sendTime) {
4928                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4929                     }
4930                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4931                     if (firstMove && !bookHit) {
4932                         firstMove = FALSE;
4933                         if (first.useColors) {
4934                           SendToProgram(gameMode == IcsPlayingWhite ?
4935                                         "white\ngo\n" :
4936                                         "black\ngo\n", &first);
4937                         } else {
4938                           SendToProgram("go\n", &first);
4939                         }
4940                         first.maybeThinking = TRUE;
4941                     }
4942                 }
4943             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4944               if (moveList[moveNum - 1][0] == NULLCHAR) {
4945                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4946                 DisplayError(str, 0);
4947               } else {
4948                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4949                 SendMoveToProgram(moveNum - 1, &first);
4950               }
4951             }
4952         }
4953 #endif
4954     }
4955
4956     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4957         /* If move comes from a remote source, animate it.  If it
4958            isn't remote, it will have already been animated. */
4959         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4960             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4961         }
4962         if (!pausing && appData.highlightLastMove) {
4963             SetHighlights(fromX, fromY, toX, toY);
4964         }
4965     }
4966
4967     /* Start the clocks */
4968     whiteFlag = blackFlag = FALSE;
4969     appData.clockMode = !(basetime == 0 && increment == 0);
4970     if (ticking == 0) {
4971       ics_clock_paused = TRUE;
4972       StopClocks();
4973     } else if (ticking == 1) {
4974       ics_clock_paused = FALSE;
4975     }
4976     if (gameMode == IcsIdle ||
4977         relation == RELATION_OBSERVING_STATIC ||
4978         relation == RELATION_EXAMINING ||
4979         ics_clock_paused)
4980       DisplayBothClocks();
4981     else
4982       StartClocks();
4983
4984     /* Display opponents and material strengths */
4985     if (gameInfo.variant != VariantBughouse &&
4986         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4987         if (tinyLayout || smallLayout) {
4988             if(gameInfo.variant == VariantNormal)
4989               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4990                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4991                     basetime, increment);
4992             else
4993               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4994                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4995                     basetime, increment, (int) gameInfo.variant);
4996         } else {
4997             if(gameInfo.variant == VariantNormal)
4998               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4999                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5000                     basetime, increment);
5001             else
5002               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5003                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5004                     basetime, increment, VariantName(gameInfo.variant));
5005         }
5006         DisplayTitle(str);
5007   if (appData.debugMode) {
5008     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5009   }
5010     }
5011
5012
5013     /* Display the board */
5014     if (!pausing && !appData.noGUI) {
5015
5016       if (appData.premove)
5017           if (!gotPremove ||
5018              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5019              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5020               ClearPremoveHighlights();
5021
5022       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5023         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5024       DrawPosition(j, boards[currentMove]);
5025
5026       DisplayMove(moveNum - 1);
5027       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5028             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5029               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5030         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5031       }
5032     }
5033
5034     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5035 #if ZIPPY
5036     if(bookHit) { // [HGM] book: simulate book reply
5037         static char bookMove[MSG_SIZ]; // a bit generous?
5038
5039         programStats.nodes = programStats.depth = programStats.time =
5040         programStats.score = programStats.got_only_move = 0;
5041         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5042
5043         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5044         strcat(bookMove, bookHit);
5045         HandleMachineMove(bookMove, &first);
5046     }
5047 #endif
5048 }
5049
5050 void
5051 GetMoveListEvent ()
5052 {
5053     char buf[MSG_SIZ];
5054     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5055         ics_getting_history = H_REQUESTED;
5056         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5057         SendToICS(buf);
5058     }
5059 }
5060
5061 void
5062 SendToBoth (char *msg)
5063 {   // to make it easy to keep two engines in step in dual analysis
5064     SendToProgram(msg, &first);
5065     if(second.analyzing) SendToProgram(msg, &second);
5066 }
5067
5068 void
5069 AnalysisPeriodicEvent (int force)
5070 {
5071     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5072          && !force) || !appData.periodicUpdates)
5073       return;
5074
5075     /* Send . command to Crafty to collect stats */
5076     SendToBoth(".\n");
5077
5078     /* Don't send another until we get a response (this makes
5079        us stop sending to old Crafty's which don't understand
5080        the "." command (sending illegal cmds resets node count & time,
5081        which looks bad)) */
5082     programStats.ok_to_send = 0;
5083 }
5084
5085 void
5086 ics_update_width (int new_width)
5087 {
5088         ics_printf("set width %d\n", new_width);
5089 }
5090
5091 void
5092 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5093 {
5094     char buf[MSG_SIZ];
5095
5096     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5097         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5098             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5099             SendToProgram(buf, cps);
5100             return;
5101         }
5102         // null move in variant where engine does not understand it (for analysis purposes)
5103         SendBoard(cps, moveNum + 1); // send position after move in stead.
5104         return;
5105     }
5106     if (cps->useUsermove) {
5107       SendToProgram("usermove ", cps);
5108     }
5109     if (cps->useSAN) {
5110       char *space;
5111       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5112         int len = space - parseList[moveNum];
5113         memcpy(buf, parseList[moveNum], len);
5114         buf[len++] = '\n';
5115         buf[len] = NULLCHAR;
5116       } else {
5117         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5118       }
5119       SendToProgram(buf, cps);
5120     } else {
5121       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5122         AlphaRank(moveList[moveNum], 4);
5123         SendToProgram(moveList[moveNum], cps);
5124         AlphaRank(moveList[moveNum], 4); // and back
5125       } else
5126       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5127        * the engine. It would be nice to have a better way to identify castle
5128        * moves here. */
5129       if(appData.fischerCastling && cps->useOOCastle) {
5130         int fromX = moveList[moveNum][0] - AAA;
5131         int fromY = moveList[moveNum][1] - ONE;
5132         int toX = moveList[moveNum][2] - AAA;
5133         int toY = moveList[moveNum][3] - ONE;
5134         if((boards[moveNum][fromY][fromX] == WhiteKing
5135             && boards[moveNum][toY][toX] == WhiteRook)
5136            || (boards[moveNum][fromY][fromX] == BlackKing
5137                && boards[moveNum][toY][toX] == BlackRook)) {
5138           if(toX > fromX) SendToProgram("O-O\n", cps);
5139           else SendToProgram("O-O-O\n", cps);
5140         }
5141         else SendToProgram(moveList[moveNum], cps);
5142       } else
5143       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5144         char *m = moveList[moveNum];
5145         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5146           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5147                                                m[2], m[3] - '0',
5148                                                m[5], m[6] - '0',
5149                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5150         else
5151           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5152                                                m[5], m[6] - '0',
5153                                                m[5], m[6] - '0',
5154                                                m[2], m[3] - '0');
5155           SendToProgram(buf, cps);
5156       } else
5157       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5158         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5159           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5160           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5161                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5162         } else
5163           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5164                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         SendToProgram(buf, cps);
5166       }
5167       else SendToProgram(moveList[moveNum], cps);
5168       /* End of additions by Tord */
5169     }
5170
5171     /* [HGM] setting up the opening has brought engine in force mode! */
5172     /*       Send 'go' if we are in a mode where machine should play. */
5173     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5174         (gameMode == TwoMachinesPlay   ||
5175 #if ZIPPY
5176          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5177 #endif
5178          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5179         SendToProgram("go\n", cps);
5180   if (appData.debugMode) {
5181     fprintf(debugFP, "(extra)\n");
5182   }
5183     }
5184     setboardSpoiledMachineBlack = 0;
5185 }
5186
5187 void
5188 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5189 {
5190     char user_move[MSG_SIZ];
5191     char suffix[4];
5192
5193     if(gameInfo.variant == VariantSChess && promoChar) {
5194         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5195         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5196     } else suffix[0] = NULLCHAR;
5197
5198     switch (moveType) {
5199       default:
5200         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5201                 (int)moveType, fromX, fromY, toX, toY);
5202         DisplayError(user_move + strlen("say "), 0);
5203         break;
5204       case WhiteKingSideCastle:
5205       case BlackKingSideCastle:
5206       case WhiteQueenSideCastleWild:
5207       case BlackQueenSideCastleWild:
5208       /* PUSH Fabien */
5209       case WhiteHSideCastleFR:
5210       case BlackHSideCastleFR:
5211       /* POP Fabien */
5212         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5213         break;
5214       case WhiteQueenSideCastle:
5215       case BlackQueenSideCastle:
5216       case WhiteKingSideCastleWild:
5217       case BlackKingSideCastleWild:
5218       /* PUSH Fabien */
5219       case WhiteASideCastleFR:
5220       case BlackASideCastleFR:
5221       /* POP Fabien */
5222         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5223         break;
5224       case WhiteNonPromotion:
5225       case BlackNonPromotion:
5226         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5227         break;
5228       case WhitePromotion:
5229       case BlackPromotion:
5230         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5231            gameInfo.variant == VariantMakruk)
5232           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5233                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5234                 PieceToChar(WhiteFerz));
5235         else if(gameInfo.variant == VariantGreat)
5236           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5237                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5238                 PieceToChar(WhiteMan));
5239         else
5240           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5241                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5242                 promoChar);
5243         break;
5244       case WhiteDrop:
5245       case BlackDrop:
5246       drop:
5247         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5248                  ToUpper(PieceToChar((ChessSquare) fromX)),
5249                  AAA + toX, ONE + toY);
5250         break;
5251       case IllegalMove:  /* could be a variant we don't quite understand */
5252         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5253       case NormalMove:
5254       case WhiteCapturesEnPassant:
5255       case BlackCapturesEnPassant:
5256         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5257                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5258         break;
5259     }
5260     SendToICS(user_move);
5261     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5262         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5263 }
5264
5265 void
5266 UploadGameEvent ()
5267 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5268     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5269     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5270     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5271       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5272       return;
5273     }
5274     if(gameMode != IcsExamining) { // is this ever not the case?
5275         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5276
5277         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5278           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5279         } else { // on FICS we must first go to general examine mode
5280           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5281         }
5282         if(gameInfo.variant != VariantNormal) {
5283             // try figure out wild number, as xboard names are not always valid on ICS
5284             for(i=1; i<=36; i++) {
5285               snprintf(buf, MSG_SIZ, "wild/%d", i);
5286                 if(StringToVariant(buf) == gameInfo.variant) break;
5287             }
5288             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5289             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5290             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5291         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5292         SendToICS(ics_prefix);
5293         SendToICS(buf);
5294         if(startedFromSetupPosition || backwardMostMove != 0) {
5295           fen = PositionToFEN(backwardMostMove, NULL, 1);
5296           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5297             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5298             SendToICS(buf);
5299           } else { // FICS: everything has to set by separate bsetup commands
5300             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5301             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5302             SendToICS(buf);
5303             if(!WhiteOnMove(backwardMostMove)) {
5304                 SendToICS("bsetup tomove black\n");
5305             }
5306             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5307             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5308             SendToICS(buf);
5309             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5310             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = boards[backwardMostMove][EP_STATUS];
5313             if(i >= 0) { // set e.p.
5314               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5315                 SendToICS(buf);
5316             }
5317             bsetup++;
5318           }
5319         }
5320       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5321             SendToICS("bsetup done\n"); // switch to normal examining.
5322     }
5323     for(i = backwardMostMove; i<last; i++) {
5324         char buf[20];
5325         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5326         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5327             int len = strlen(moveList[i]);
5328             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5329             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5330         }
5331         SendToICS(buf);
5332     }
5333     SendToICS(ics_prefix);
5334     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5335 }
5336
5337 int killX = -1, killY = -1, kill2X, kill2Y; // [HGM] lion: used for passing e.p. capture square to MakeMove
5338 int legNr = 1;
5339
5340 void
5341 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5342 {
5343     if (rf == DROP_RANK) {
5344       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5345       sprintf(move, "%c@%c%c\n",
5346                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5347     } else {
5348         if (promoChar == 'x' || promoChar == NULLCHAR) {
5349           sprintf(move, "%c%c%c%c\n",
5350                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5351           if(killX >= 0 && killY >= 0) {
5352             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5353             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5354           }
5355         } else {
5356             sprintf(move, "%c%c%c%c%c\n",
5357                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5358         }
5359     }
5360 }
5361
5362 void
5363 ProcessICSInitScript (FILE *f)
5364 {
5365     char buf[MSG_SIZ];
5366
5367     while (fgets(buf, MSG_SIZ, f)) {
5368         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5369     }
5370
5371     fclose(f);
5372 }
5373
5374
5375 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5376 int dragging;
5377 static ClickType lastClickType;
5378
5379 int
5380 Partner (ChessSquare *p)
5381 { // change piece into promotion partner if one shogi-promotes to the other
5382   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5383   ChessSquare partner;
5384   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5385   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5386   *p = partner;
5387   return 1;
5388 }
5389
5390 void
5391 Sweep (int step)
5392 {
5393     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5394     static int toggleFlag;
5395     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5396     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5397     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5398     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5399     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5400     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5401     do {
5402         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5403         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5404         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5405         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5406         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5407         if(!step) step = -1;
5408     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5409             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5410             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5411             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5412     if(toX >= 0) {
5413         int victim = boards[currentMove][toY][toX];
5414         boards[currentMove][toY][toX] = promoSweep;
5415         DrawPosition(FALSE, boards[currentMove]);
5416         boards[currentMove][toY][toX] = victim;
5417     } else
5418     ChangeDragPiece(promoSweep);
5419 }
5420
5421 int
5422 PromoScroll (int x, int y)
5423 {
5424   int step = 0;
5425
5426   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5427   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5428   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5429   if(!step) return FALSE;
5430   lastX = x; lastY = y;
5431   if((promoSweep < BlackPawn) == flipView) step = -step;
5432   if(step > 0) selectFlag = 1;
5433   if(!selectFlag) Sweep(step);
5434   return FALSE;
5435 }
5436
5437 void
5438 NextPiece (int step)
5439 {
5440     ChessSquare piece = boards[currentMove][toY][toX];
5441     do {
5442         pieceSweep -= step;
5443         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5444         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5445         if(!step) step = -1;
5446     } while(PieceToChar(pieceSweep) == '.');
5447     boards[currentMove][toY][toX] = pieceSweep;
5448     DrawPosition(FALSE, boards[currentMove]);
5449     boards[currentMove][toY][toX] = piece;
5450 }
5451 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5452 void
5453 AlphaRank (char *move, int n)
5454 {
5455 //    char *p = move, c; int x, y;
5456
5457     if (appData.debugMode) {
5458         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5459     }
5460
5461     if(move[1]=='*' &&
5462        move[2]>='0' && move[2]<='9' &&
5463        move[3]>='a' && move[3]<='x'    ) {
5464         move[1] = '@';
5465         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5466         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5467     } else
5468     if(move[0]>='0' && move[0]<='9' &&
5469        move[1]>='a' && move[1]<='x' &&
5470        move[2]>='0' && move[2]<='9' &&
5471        move[3]>='a' && move[3]<='x'    ) {
5472         /* input move, Shogi -> normal */
5473         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5474         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5475         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5476         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5477     } else
5478     if(move[1]=='@' &&
5479        move[3]>='0' && move[3]<='9' &&
5480        move[2]>='a' && move[2]<='x'    ) {
5481         move[1] = '*';
5482         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5483         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5484     } else
5485     if(
5486        move[0]>='a' && move[0]<='x' &&
5487        move[3]>='0' && move[3]<='9' &&
5488        move[2]>='a' && move[2]<='x'    ) {
5489          /* output move, normal -> Shogi */
5490         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5491         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5492         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5493         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5494         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5495     }
5496     if (appData.debugMode) {
5497         fprintf(debugFP, "   out = '%s'\n", move);
5498     }
5499 }
5500
5501 char yy_textstr[8000];
5502
5503 /* Parser for moves from gnuchess, ICS, or user typein box */
5504 Boolean
5505 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5506 {
5507     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5508
5509     switch (*moveType) {
5510       case WhitePromotion:
5511       case BlackPromotion:
5512       case WhiteNonPromotion:
5513       case BlackNonPromotion:
5514       case NormalMove:
5515       case FirstLeg:
5516       case WhiteCapturesEnPassant:
5517       case BlackCapturesEnPassant:
5518       case WhiteKingSideCastle:
5519       case WhiteQueenSideCastle:
5520       case BlackKingSideCastle:
5521       case BlackQueenSideCastle:
5522       case WhiteKingSideCastleWild:
5523       case WhiteQueenSideCastleWild:
5524       case BlackKingSideCastleWild:
5525       case BlackQueenSideCastleWild:
5526       /* Code added by Tord: */
5527       case WhiteHSideCastleFR:
5528       case WhiteASideCastleFR:
5529       case BlackHSideCastleFR:
5530       case BlackASideCastleFR:
5531       /* End of code added by Tord */
5532       case IllegalMove:         /* bug or odd chess variant */
5533         if(currentMoveString[1] == '@') { // illegal drop
5534           *fromX = WhiteOnMove(moveNum) ?
5535             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5536             (int) CharToPiece(ToLower(currentMoveString[0]));
5537           goto drop;
5538         }
5539         *fromX = currentMoveString[0] - AAA;
5540         *fromY = currentMoveString[1] - ONE;
5541         *toX = currentMoveString[2] - AAA;
5542         *toY = currentMoveString[3] - ONE;
5543         *promoChar = currentMoveString[4];
5544         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5545             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5546     if (appData.debugMode) {
5547         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5548     }
5549             *fromX = *fromY = *toX = *toY = 0;
5550             return FALSE;
5551         }
5552         if (appData.testLegality) {
5553           return (*moveType != IllegalMove);
5554         } else {
5555           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5556                          // [HGM] lion: if this is a double move we are less critical
5557                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5558         }
5559
5560       case WhiteDrop:
5561       case BlackDrop:
5562         *fromX = *moveType == WhiteDrop ?
5563           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5564           (int) CharToPiece(ToLower(currentMoveString[0]));
5565       drop:
5566         *fromY = DROP_RANK;
5567         *toX = currentMoveString[2] - AAA;
5568         *toY = currentMoveString[3] - ONE;
5569         *promoChar = NULLCHAR;
5570         return TRUE;
5571
5572       case AmbiguousMove:
5573       case ImpossibleMove:
5574       case EndOfFile:
5575       case ElapsedTime:
5576       case Comment:
5577       case PGNTag:
5578       case NAG:
5579       case WhiteWins:
5580       case BlackWins:
5581       case GameIsDrawn:
5582       default:
5583     if (appData.debugMode) {
5584         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5585     }
5586         /* bug? */
5587         *fromX = *fromY = *toX = *toY = 0;
5588         *promoChar = NULLCHAR;
5589         return FALSE;
5590     }
5591 }
5592
5593 Boolean pushed = FALSE;
5594 char *lastParseAttempt;
5595
5596 void
5597 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5598 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5599   int fromX, fromY, toX, toY; char promoChar;
5600   ChessMove moveType;
5601   Boolean valid;
5602   int nr = 0;
5603
5604   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5605   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5606     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5607     pushed = TRUE;
5608   }
5609   endPV = forwardMostMove;
5610   do {
5611     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5612     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5613     lastParseAttempt = pv;
5614     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5615     if(!valid && nr == 0 &&
5616        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5617         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5618         // Hande case where played move is different from leading PV move
5619         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5620         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5621         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5622         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5623           endPV += 2; // if position different, keep this
5624           moveList[endPV-1][0] = fromX + AAA;
5625           moveList[endPV-1][1] = fromY + ONE;
5626           moveList[endPV-1][2] = toX + AAA;
5627           moveList[endPV-1][3] = toY + ONE;
5628           parseList[endPV-1][0] = NULLCHAR;
5629           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5630         }
5631       }
5632     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5633     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5634     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5635     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5636         valid++; // allow comments in PV
5637         continue;
5638     }
5639     nr++;
5640     if(endPV+1 > framePtr) break; // no space, truncate
5641     if(!valid) break;
5642     endPV++;
5643     CopyBoard(boards[endPV], boards[endPV-1]);
5644     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5645     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5646     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5647     CoordsToAlgebraic(boards[endPV - 1],
5648                              PosFlags(endPV - 1),
5649                              fromY, fromX, toY, toX, promoChar,
5650                              parseList[endPV - 1]);
5651   } while(valid);
5652   if(atEnd == 2) return; // used hidden, for PV conversion
5653   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5654   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5655   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5656                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5657   DrawPosition(TRUE, boards[currentMove]);
5658 }
5659
5660 int
5661 MultiPV (ChessProgramState *cps)
5662 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5663         int i;
5664         for(i=0; i<cps->nrOptions; i++)
5665             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5666                 return i;
5667         return -1;
5668 }
5669
5670 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5671
5672 Boolean
5673 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5674 {
5675         int startPV, multi, lineStart, origIndex = index;
5676         char *p, buf2[MSG_SIZ];
5677         ChessProgramState *cps = (pane ? &second : &first);
5678
5679         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5680         lastX = x; lastY = y;
5681         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5682         lineStart = startPV = index;
5683         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5684         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5685         index = startPV;
5686         do{ while(buf[index] && buf[index] != '\n') index++;
5687         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5688         buf[index] = 0;
5689         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5690                 int n = cps->option[multi].value;
5691                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5692                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5693                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5694                 cps->option[multi].value = n;
5695                 *start = *end = 0;
5696                 return FALSE;
5697         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5698                 ExcludeClick(origIndex - lineStart);
5699                 return FALSE;
5700         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5701                 Collapse(origIndex - lineStart);
5702                 return FALSE;
5703         }
5704         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5705         *start = startPV; *end = index-1;
5706         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5707         return TRUE;
5708 }
5709
5710 char *
5711 PvToSAN (char *pv)
5712 {
5713         static char buf[10*MSG_SIZ];
5714         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5715         *buf = NULLCHAR;
5716         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5717         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5718         for(i = forwardMostMove; i<endPV; i++){
5719             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5720             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5721             k += strlen(buf+k);
5722         }
5723         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5724         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5725         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5726         endPV = savedEnd;
5727         return buf;
5728 }
5729
5730 Boolean
5731 LoadPV (int x, int y)
5732 { // called on right mouse click to load PV
5733   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5734   lastX = x; lastY = y;
5735   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5736   extendGame = FALSE;
5737   return TRUE;
5738 }
5739
5740 void
5741 UnLoadPV ()
5742 {
5743   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5744   if(endPV < 0) return;
5745   if(appData.autoCopyPV) CopyFENToClipboard();
5746   endPV = -1;
5747   if(extendGame && currentMove > forwardMostMove) {
5748         Boolean saveAnimate = appData.animate;
5749         if(pushed) {
5750             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5751                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5752             } else storedGames--; // abandon shelved tail of original game
5753         }
5754         pushed = FALSE;
5755         forwardMostMove = currentMove;
5756         currentMove = oldFMM;
5757         appData.animate = FALSE;
5758         ToNrEvent(forwardMostMove);
5759         appData.animate = saveAnimate;
5760   }
5761   currentMove = forwardMostMove;
5762   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5763   ClearPremoveHighlights();
5764   DrawPosition(TRUE, boards[currentMove]);
5765 }
5766
5767 void
5768 MovePV (int x, int y, int h)
5769 { // step through PV based on mouse coordinates (called on mouse move)
5770   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5771
5772   // we must somehow check if right button is still down (might be released off board!)
5773   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5774   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5775   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5776   if(!step) return;
5777   lastX = x; lastY = y;
5778
5779   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5780   if(endPV < 0) return;
5781   if(y < margin) step = 1; else
5782   if(y > h - margin) step = -1;
5783   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5784   currentMove += step;
5785   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5786   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5787                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5788   DrawPosition(FALSE, boards[currentMove]);
5789 }
5790
5791
5792 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5793 // All positions will have equal probability, but the current method will not provide a unique
5794 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5795 #define DARK 1
5796 #define LITE 2
5797 #define ANY 3
5798
5799 int squaresLeft[4];
5800 int piecesLeft[(int)BlackPawn];
5801 int seed, nrOfShuffles;
5802
5803 void
5804 GetPositionNumber ()
5805 {       // sets global variable seed
5806         int i;
5807
5808         seed = appData.defaultFrcPosition;
5809         if(seed < 0) { // randomize based on time for negative FRC position numbers
5810                 for(i=0; i<50; i++) seed += random();
5811                 seed = random() ^ random() >> 8 ^ random() << 8;
5812                 if(seed<0) seed = -seed;
5813         }
5814 }
5815
5816 int
5817 put (Board board, int pieceType, int rank, int n, int shade)
5818 // put the piece on the (n-1)-th empty squares of the given shade
5819 {
5820         int i;
5821
5822         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5823                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5824                         board[rank][i] = (ChessSquare) pieceType;
5825                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5826                         squaresLeft[ANY]--;
5827                         piecesLeft[pieceType]--;
5828                         return i;
5829                 }
5830         }
5831         return -1;
5832 }
5833
5834
5835 void
5836 AddOnePiece (Board board, int pieceType, int rank, int shade)
5837 // calculate where the next piece goes, (any empty square), and put it there
5838 {
5839         int i;
5840
5841         i = seed % squaresLeft[shade];
5842         nrOfShuffles *= squaresLeft[shade];
5843         seed /= squaresLeft[shade];
5844         put(board, pieceType, rank, i, shade);
5845 }
5846
5847 void
5848 AddTwoPieces (Board board, int pieceType, int rank)
5849 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5850 {
5851         int i, n=squaresLeft[ANY], j=n-1, k;
5852
5853         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5854         i = seed % k;  // pick one
5855         nrOfShuffles *= k;
5856         seed /= k;
5857         while(i >= j) i -= j--;
5858         j = n - 1 - j; i += j;
5859         put(board, pieceType, rank, j, ANY);
5860         put(board, pieceType, rank, i, ANY);
5861 }
5862
5863 void
5864 SetUpShuffle (Board board, int number)
5865 {
5866         int i, p, first=1;
5867
5868         GetPositionNumber(); nrOfShuffles = 1;
5869
5870         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5871         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5872         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5873
5874         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5875
5876         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5877             p = (int) board[0][i];
5878             if(p < (int) BlackPawn) piecesLeft[p] ++;
5879             board[0][i] = EmptySquare;
5880         }
5881
5882         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5883             // shuffles restricted to allow normal castling put KRR first
5884             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5885                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5886             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5887                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5888             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5889                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5890             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5891                 put(board, WhiteRook, 0, 0, ANY);
5892             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5893         }
5894
5895         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5896             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5897             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5898                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5899                 while(piecesLeft[p] >= 2) {
5900                     AddOnePiece(board, p, 0, LITE);
5901                     AddOnePiece(board, p, 0, DARK);
5902                 }
5903                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5904             }
5905
5906         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5907             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5908             // but we leave King and Rooks for last, to possibly obey FRC restriction
5909             if(p == (int)WhiteRook) continue;
5910             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5911             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5912         }
5913
5914         // now everything is placed, except perhaps King (Unicorn) and Rooks
5915
5916         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5917             // Last King gets castling rights
5918             while(piecesLeft[(int)WhiteUnicorn]) {
5919                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5920                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5921             }
5922
5923             while(piecesLeft[(int)WhiteKing]) {
5924                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5925                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5926             }
5927
5928
5929         } else {
5930             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5931             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5932         }
5933
5934         // Only Rooks can be left; simply place them all
5935         while(piecesLeft[(int)WhiteRook]) {
5936                 i = put(board, WhiteRook, 0, 0, ANY);
5937                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5938                         if(first) {
5939                                 first=0;
5940                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5941                         }
5942                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5943                 }
5944         }
5945         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5946             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5947         }
5948
5949         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5950 }
5951
5952 int
5953 ptclen (const char *s, char *escapes)
5954 {
5955     int n = 0;
5956     if(!*escapes) return strlen(s);
5957     while(*s) n += (*s != ':' && !strchr(escapes, *s)), s++;
5958     return n;
5959 }
5960
5961 int
5962 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
5963 /* [HGM] moved here from winboard.c because of its general usefulness */
5964 /*       Basically a safe strcpy that uses the last character as King */
5965 {
5966     int result = FALSE; int NrPieces;
5967
5968     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
5969                     && NrPieces >= 12 && !(NrPieces&1)) {
5970         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
5971
5972         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5973         for( i=0; i<NrPieces/2-1; i++ ) {
5974             char *p;
5975             if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
5976             table[i] = map[j++];
5977             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
5978         }
5979         table[(int) WhiteKing]  = map[j++];
5980         for( i=0; i<NrPieces/2-1; i++ ) {
5981             char *p;
5982             if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
5983             table[WHITE_TO_BLACK i] = map[j++];
5984             if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i] += 64*(p - escapes + 1);
5985         }
5986         table[(int) BlackKing]  = map[j++];
5987
5988         result = TRUE;
5989     }
5990
5991     return result;
5992 }
5993
5994 int
5995 SetCharTable (unsigned char *table, const char * map)
5996 {
5997     return SetCharTableEsc(table, map, "");
5998 }
5999
6000 void
6001 Prelude (Board board)
6002 {       // [HGM] superchess: random selection of exo-pieces
6003         int i, j, k; ChessSquare p;
6004         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6005
6006         GetPositionNumber(); // use FRC position number
6007
6008         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6009             SetCharTable(pieceToChar, appData.pieceToCharTable);
6010             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6011                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6012         }
6013
6014         j = seed%4;                 seed /= 4;
6015         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6016         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6017         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6018         j = seed%3 + (seed%3 >= j); seed /= 3;
6019         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6020         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6021         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6022         j = seed%3;                 seed /= 3;
6023         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6024         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6025         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6026         j = seed%2 + (seed%2 >= j); seed /= 2;
6027         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6028         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6029         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6030         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6031         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6032         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6033         put(board, exoPieces[0],    0, 0, ANY);
6034         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6035 }
6036
6037 void
6038 InitPosition (int redraw)
6039 {
6040     ChessSquare (* pieces)[BOARD_FILES];
6041     int i, j, pawnRow=1, pieceRows=1, overrule,
6042     oldx = gameInfo.boardWidth,
6043     oldy = gameInfo.boardHeight,
6044     oldh = gameInfo.holdingsWidth;
6045     static int oldv;
6046
6047     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6048
6049     /* [AS] Initialize pv info list [HGM] and game status */
6050     {
6051         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6052             pvInfoList[i].depth = 0;
6053             boards[i][EP_STATUS] = EP_NONE;
6054             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6055         }
6056
6057         initialRulePlies = 0; /* 50-move counter start */
6058
6059         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6060         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6061     }
6062
6063
6064     /* [HGM] logic here is completely changed. In stead of full positions */
6065     /* the initialized data only consist of the two backranks. The switch */
6066     /* selects which one we will use, which is than copied to the Board   */
6067     /* initialPosition, which for the rest is initialized by Pawns and    */
6068     /* empty squares. This initial position is then copied to boards[0],  */
6069     /* possibly after shuffling, so that it remains available.            */
6070
6071     gameInfo.holdingsWidth = 0; /* default board sizes */
6072     gameInfo.boardWidth    = 8;
6073     gameInfo.boardHeight   = 8;
6074     gameInfo.holdingsSize  = 0;
6075     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6076     for(i=0; i<BOARD_FILES-6; i++)
6077       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6078     initialPosition[EP_STATUS] = EP_NONE;
6079     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6080     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6081     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6082          SetCharTable(pieceNickName, appData.pieceNickNames);
6083     else SetCharTable(pieceNickName, "............");
6084     pieces = FIDEArray;
6085
6086     switch (gameInfo.variant) {
6087     case VariantFischeRandom:
6088       shuffleOpenings = TRUE;
6089       appData.fischerCastling = TRUE;
6090     default:
6091       break;
6092     case VariantShatranj:
6093       pieces = ShatranjArray;
6094       nrCastlingRights = 0;
6095       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6096       break;
6097     case VariantMakruk:
6098       pieces = makrukArray;
6099       nrCastlingRights = 0;
6100       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6101       break;
6102     case VariantASEAN:
6103       pieces = aseanArray;
6104       nrCastlingRights = 0;
6105       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6106       break;
6107     case VariantTwoKings:
6108       pieces = twoKingsArray;
6109       break;
6110     case VariantGrand:
6111       pieces = GrandArray;
6112       nrCastlingRights = 0;
6113       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6114       gameInfo.boardWidth = 10;
6115       gameInfo.boardHeight = 10;
6116       gameInfo.holdingsSize = 7;
6117       break;
6118     case VariantCapaRandom:
6119       shuffleOpenings = TRUE;
6120       appData.fischerCastling = TRUE;
6121     case VariantCapablanca:
6122       pieces = CapablancaArray;
6123       gameInfo.boardWidth = 10;
6124       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6125       break;
6126     case VariantGothic:
6127       pieces = GothicArray;
6128       gameInfo.boardWidth = 10;
6129       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6130       break;
6131     case VariantSChess:
6132       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6133       gameInfo.holdingsSize = 7;
6134       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6135       break;
6136     case VariantJanus:
6137       pieces = JanusArray;
6138       gameInfo.boardWidth = 10;
6139       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6140       nrCastlingRights = 6;
6141         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6142         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6143         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6144         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6145         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6146         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6147       break;
6148     case VariantFalcon:
6149       pieces = FalconArray;
6150       gameInfo.boardWidth = 10;
6151       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6152       break;
6153     case VariantXiangqi:
6154       pieces = XiangqiArray;
6155       gameInfo.boardWidth  = 9;
6156       gameInfo.boardHeight = 10;
6157       nrCastlingRights = 0;
6158       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6159       break;
6160     case VariantShogi:
6161       pieces = ShogiArray;
6162       gameInfo.boardWidth  = 9;
6163       gameInfo.boardHeight = 9;
6164       gameInfo.holdingsSize = 7;
6165       nrCastlingRights = 0;
6166       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6167       break;
6168     case VariantChu:
6169       pieces = ChuArray; pieceRows = 3;
6170       gameInfo.boardWidth  = 12;
6171       gameInfo.boardHeight = 12;
6172       nrCastlingRights = 0;
6173       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN:+.++.++++++++++.+++++K"
6174                                    "p.brqsexogcathd.vmlifn:+.++.++++++++++.+++++k", SUFFIXES);
6175       break;
6176     case VariantCourier:
6177       pieces = CourierArray;
6178       gameInfo.boardWidth  = 12;
6179       nrCastlingRights = 0;
6180       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6181       break;
6182     case VariantKnightmate:
6183       pieces = KnightmateArray;
6184       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6185       break;
6186     case VariantSpartan:
6187       pieces = SpartanArray;
6188       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6189       break;
6190     case VariantLion:
6191       pieces = lionArray;
6192       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6193       break;
6194     case VariantChuChess:
6195       pieces = ChuChessArray;
6196       gameInfo.boardWidth = 10;
6197       gameInfo.boardHeight = 10;
6198       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6199       break;
6200     case VariantFairy:
6201       pieces = fairyArray;
6202       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6203       break;
6204     case VariantGreat:
6205       pieces = GreatArray;
6206       gameInfo.boardWidth = 10;
6207       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6208       gameInfo.holdingsSize = 8;
6209       break;
6210     case VariantSuper:
6211       pieces = FIDEArray;
6212       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6213       gameInfo.holdingsSize = 8;
6214       startedFromSetupPosition = TRUE;
6215       break;
6216     case VariantCrazyhouse:
6217     case VariantBughouse:
6218       pieces = FIDEArray;
6219       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6220       gameInfo.holdingsSize = 5;
6221       break;
6222     case VariantWildCastle:
6223       pieces = FIDEArray;
6224       /* !!?shuffle with kings guaranteed to be on d or e file */
6225       shuffleOpenings = 1;
6226       break;
6227     case VariantNoCastle:
6228       pieces = FIDEArray;
6229       nrCastlingRights = 0;
6230       /* !!?unconstrained back-rank shuffle */
6231       shuffleOpenings = 1;
6232       break;
6233     }
6234
6235     overrule = 0;
6236     if(appData.NrFiles >= 0) {
6237         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6238         gameInfo.boardWidth = appData.NrFiles;
6239     }
6240     if(appData.NrRanks >= 0) {
6241         gameInfo.boardHeight = appData.NrRanks;
6242     }
6243     if(appData.holdingsSize >= 0) {
6244         i = appData.holdingsSize;
6245         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6246         gameInfo.holdingsSize = i;
6247     }
6248     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6249     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6250         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6251
6252     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6253     if(pawnRow < 1) pawnRow = 1;
6254     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6255        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6256     if(gameInfo.variant == VariantChu) pawnRow = 3;
6257
6258     /* User pieceToChar list overrules defaults */
6259     if(appData.pieceToCharTable != NULL)
6260         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6261
6262     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6263
6264         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6265             s = (ChessSquare) 0; /* account holding counts in guard band */
6266         for( i=0; i<BOARD_HEIGHT; i++ )
6267             initialPosition[i][j] = s;
6268
6269         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6270         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6271         initialPosition[pawnRow][j] = WhitePawn;
6272         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6273         if(gameInfo.variant == VariantXiangqi) {
6274             if(j&1) {
6275                 initialPosition[pawnRow][j] =
6276                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6277                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6278                    initialPosition[2][j] = WhiteCannon;
6279                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6280                 }
6281             }
6282         }
6283         if(gameInfo.variant == VariantChu) {
6284              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6285                initialPosition[pawnRow+1][j] = WhiteCobra,
6286                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6287              for(i=1; i<pieceRows; i++) {
6288                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6289                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6290              }
6291         }
6292         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6293             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6294                initialPosition[0][j] = WhiteRook;
6295                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6296             }
6297         }
6298         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6299     }
6300     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6301     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6302
6303             j=BOARD_LEFT+1;
6304             initialPosition[1][j] = WhiteBishop;
6305             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6306             j=BOARD_RGHT-2;
6307             initialPosition[1][j] = WhiteRook;
6308             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6309     }
6310
6311     if( nrCastlingRights == -1) {
6312         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6313         /*       This sets default castling rights from none to normal corners   */
6314         /* Variants with other castling rights must set them themselves above    */
6315         nrCastlingRights = 6;
6316
6317         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6318         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6319         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6320         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6321         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6322         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6323      }
6324
6325      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6326      if(gameInfo.variant == VariantGreat) { // promotion commoners
6327         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6328         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6329         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6330         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6331      }
6332      if( gameInfo.variant == VariantSChess ) {
6333       initialPosition[1][0] = BlackMarshall;
6334       initialPosition[2][0] = BlackAngel;
6335       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6336       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6337       initialPosition[1][1] = initialPosition[2][1] =
6338       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6339      }
6340   if (appData.debugMode) {
6341     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6342   }
6343     if(shuffleOpenings) {
6344         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6345         startedFromSetupPosition = TRUE;
6346     }
6347     if(startedFromPositionFile) {
6348       /* [HGM] loadPos: use PositionFile for every new game */
6349       CopyBoard(initialPosition, filePosition);
6350       for(i=0; i<nrCastlingRights; i++)
6351           initialRights[i] = filePosition[CASTLING][i];
6352       startedFromSetupPosition = TRUE;
6353     }
6354
6355     CopyBoard(boards[0], initialPosition);
6356
6357     if(oldx != gameInfo.boardWidth ||
6358        oldy != gameInfo.boardHeight ||
6359        oldv != gameInfo.variant ||
6360        oldh != gameInfo.holdingsWidth
6361                                          )
6362             InitDrawingSizes(-2 ,0);
6363
6364     oldv = gameInfo.variant;
6365     if (redraw)
6366       DrawPosition(TRUE, boards[currentMove]);
6367 }
6368
6369 void
6370 SendBoard (ChessProgramState *cps, int moveNum)
6371 {
6372     char message[MSG_SIZ];
6373
6374     if (cps->useSetboard) {
6375       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6376       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6377       SendToProgram(message, cps);
6378       free(fen);
6379
6380     } else {
6381       ChessSquare *bp;
6382       int i, j, left=0, right=BOARD_WIDTH;
6383       /* Kludge to set black to move, avoiding the troublesome and now
6384        * deprecated "black" command.
6385        */
6386       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6387         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6388
6389       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6390
6391       SendToProgram("edit\n", cps);
6392       SendToProgram("#\n", cps);
6393       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6394         bp = &boards[moveNum][i][left];
6395         for (j = left; j < right; j++, bp++) {
6396           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6397           if ((int) *bp < (int) BlackPawn) {
6398             if(j == BOARD_RGHT+1)
6399                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6400             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6401             if(message[0] == '+' || message[0] == '~') {
6402               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6403                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6404                         AAA + j, ONE + i);
6405             }
6406             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6407                 message[1] = BOARD_RGHT   - 1 - j + '1';
6408                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6409             }
6410             SendToProgram(message, cps);
6411           }
6412         }
6413       }
6414
6415       SendToProgram("c\n", cps);
6416       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6417         bp = &boards[moveNum][i][left];
6418         for (j = left; j < right; j++, bp++) {
6419           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6420           if (((int) *bp != (int) EmptySquare)
6421               && ((int) *bp >= (int) BlackPawn)) {
6422             if(j == BOARD_LEFT-2)
6423                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6424             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6425                     AAA + j, ONE + i);
6426             if(message[0] == '+' || message[0] == '~') {
6427               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6428                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6429                         AAA + j, ONE + i);
6430             }
6431             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6432                 message[1] = BOARD_RGHT   - 1 - j + '1';
6433                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6434             }
6435             SendToProgram(message, cps);
6436           }
6437         }
6438       }
6439
6440       SendToProgram(".\n", cps);
6441     }
6442     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6443 }
6444
6445 char exclusionHeader[MSG_SIZ];
6446 int exCnt, excludePtr;
6447 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6448 static Exclusion excluTab[200];
6449 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6450
6451 static void
6452 WriteMap (int s)
6453 {
6454     int j;
6455     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6456     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6457 }
6458
6459 static void
6460 ClearMap ()
6461 {
6462     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6463     excludePtr = 24; exCnt = 0;
6464     WriteMap(0);
6465 }
6466
6467 static void
6468 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6469 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6470     char buf[2*MOVE_LEN], *p;
6471     Exclusion *e = excluTab;
6472     int i;
6473     for(i=0; i<exCnt; i++)
6474         if(e[i].ff == fromX && e[i].fr == fromY &&
6475            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6476     if(i == exCnt) { // was not in exclude list; add it
6477         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6478         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6479             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6480             return; // abort
6481         }
6482         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6483         excludePtr++; e[i].mark = excludePtr++;
6484         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6485         exCnt++;
6486     }
6487     exclusionHeader[e[i].mark] = state;
6488 }
6489
6490 static int
6491 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6492 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6493     char buf[MSG_SIZ];
6494     int j, k;
6495     ChessMove moveType;
6496     if((signed char)promoChar == -1) { // kludge to indicate best move
6497         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6498             return 1; // if unparsable, abort
6499     }
6500     // update exclusion map (resolving toggle by consulting existing state)
6501     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6502     j = k%8; k >>= 3;
6503     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6504     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6505          excludeMap[k] |=   1<<j;
6506     else excludeMap[k] &= ~(1<<j);
6507     // update header
6508     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6509     // inform engine
6510     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6511     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6512     SendToBoth(buf);
6513     return (state == '+');
6514 }
6515
6516 static void
6517 ExcludeClick (int index)
6518 {
6519     int i, j;
6520     Exclusion *e = excluTab;
6521     if(index < 25) { // none, best or tail clicked
6522         if(index < 13) { // none: include all
6523             WriteMap(0); // clear map
6524             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6525             SendToBoth("include all\n"); // and inform engine
6526         } else if(index > 18) { // tail
6527             if(exclusionHeader[19] == '-') { // tail was excluded
6528                 SendToBoth("include all\n");
6529                 WriteMap(0); // clear map completely
6530                 // now re-exclude selected moves
6531                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6532                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6533             } else { // tail was included or in mixed state
6534                 SendToBoth("exclude all\n");
6535                 WriteMap(0xFF); // fill map completely
6536                 // now re-include selected moves
6537                 j = 0; // count them
6538                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6539                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6540                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6541             }
6542         } else { // best
6543             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6544         }
6545     } else {
6546         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6547             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6548             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6549             break;
6550         }
6551     }
6552 }
6553
6554 ChessSquare
6555 DefaultPromoChoice (int white)
6556 {
6557     ChessSquare result;
6558     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6559        gameInfo.variant == VariantMakruk)
6560         result = WhiteFerz; // no choice
6561     else if(gameInfo.variant == VariantASEAN)
6562         result = WhiteRook; // no choice
6563     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6564         result= WhiteKing; // in Suicide Q is the last thing we want
6565     else if(gameInfo.variant == VariantSpartan)
6566         result = white ? WhiteQueen : WhiteAngel;
6567     else result = WhiteQueen;
6568     if(!white) result = WHITE_TO_BLACK result;
6569     return result;
6570 }
6571
6572 static int autoQueen; // [HGM] oneclick
6573
6574 int
6575 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6576 {
6577     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6578     /* [HGM] add Shogi promotions */
6579     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6580     ChessSquare piece, partner;
6581     ChessMove moveType;
6582     Boolean premove;
6583
6584     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6585     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6586
6587     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6588       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6589         return FALSE;
6590
6591     piece = boards[currentMove][fromY][fromX];
6592     if(gameInfo.variant == VariantChu) {
6593         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6594         promotionZoneSize = BOARD_HEIGHT/3;
6595         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6596     } else if(gameInfo.variant == VariantShogi) {
6597         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6598         highestPromotingPiece = (int)WhiteAlfil;
6599     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6600         promotionZoneSize = 3;
6601     }
6602
6603     // Treat Lance as Pawn when it is not representing Amazon or Lance
6604     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6605         if(piece == WhiteLance) piece = WhitePawn; else
6606         if(piece == BlackLance) piece = BlackPawn;
6607     }
6608
6609     // next weed out all moves that do not touch the promotion zone at all
6610     if((int)piece >= BlackPawn) {
6611         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6612              return FALSE;
6613         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6614         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6615     } else {
6616         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6617            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6618         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6619              return FALSE;
6620     }
6621
6622     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6623
6624     // weed out mandatory Shogi promotions
6625     if(gameInfo.variant == VariantShogi) {
6626         if(piece >= BlackPawn) {
6627             if(toY == 0 && piece == BlackPawn ||
6628                toY == 0 && piece == BlackQueen ||
6629                toY <= 1 && piece == BlackKnight) {
6630                 *promoChoice = '+';
6631                 return FALSE;
6632             }
6633         } else {
6634             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6635                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6636                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6637                 *promoChoice = '+';
6638                 return FALSE;
6639             }
6640         }
6641     }
6642
6643     // weed out obviously illegal Pawn moves
6644     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6645         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6646         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6647         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6648         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6649         // note we are not allowed to test for valid (non-)capture, due to premove
6650     }
6651
6652     // we either have a choice what to promote to, or (in Shogi) whether to promote
6653     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6654        gameInfo.variant == VariantMakruk) {
6655         ChessSquare p=BlackFerz;  // no choice
6656         while(p < EmptySquare) {  //but make sure we use piece that exists
6657             *promoChoice = PieceToChar(p++);
6658             if(*promoChoice != '.') break;
6659         }
6660         return FALSE;
6661     }
6662     // no sense asking what we must promote to if it is going to explode...
6663     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6664         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6665         return FALSE;
6666     }
6667     // give caller the default choice even if we will not make it
6668     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6669     partner = piece; // pieces can promote if the pieceToCharTable says so
6670     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6671     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6672     if(        sweepSelect && gameInfo.variant != VariantGreat
6673                            && gameInfo.variant != VariantGrand
6674                            && gameInfo.variant != VariantSuper) return FALSE;
6675     if(autoQueen) return FALSE; // predetermined
6676
6677     // suppress promotion popup on illegal moves that are not premoves
6678     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6679               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6680     if(appData.testLegality && !premove) {
6681         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6682                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6683         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6684         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6685             return FALSE;
6686     }
6687
6688     return TRUE;
6689 }
6690
6691 int
6692 InPalace (int row, int column)
6693 {   /* [HGM] for Xiangqi */
6694     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6695          column < (BOARD_WIDTH + 4)/2 &&
6696          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6697     return FALSE;
6698 }
6699
6700 int
6701 PieceForSquare (int x, int y)
6702 {
6703   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6704      return -1;
6705   else
6706      return boards[currentMove][y][x];
6707 }
6708
6709 int
6710 OKToStartUserMove (int x, int y)
6711 {
6712     ChessSquare from_piece;
6713     int white_piece;
6714
6715     if (matchMode) return FALSE;
6716     if (gameMode == EditPosition) return TRUE;
6717
6718     if (x >= 0 && y >= 0)
6719       from_piece = boards[currentMove][y][x];
6720     else
6721       from_piece = EmptySquare;
6722
6723     if (from_piece == EmptySquare) return FALSE;
6724
6725     white_piece = (int)from_piece >= (int)WhitePawn &&
6726       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6727
6728     switch (gameMode) {
6729       case AnalyzeFile:
6730       case TwoMachinesPlay:
6731       case EndOfGame:
6732         return FALSE;
6733
6734       case IcsObserving:
6735       case IcsIdle:
6736         return FALSE;
6737
6738       case MachinePlaysWhite:
6739       case IcsPlayingBlack:
6740         if (appData.zippyPlay) return FALSE;
6741         if (white_piece) {
6742             DisplayMoveError(_("You are playing Black"));
6743             return FALSE;
6744         }
6745         break;
6746
6747       case MachinePlaysBlack:
6748       case IcsPlayingWhite:
6749         if (appData.zippyPlay) return FALSE;
6750         if (!white_piece) {
6751             DisplayMoveError(_("You are playing White"));
6752             return FALSE;
6753         }
6754         break;
6755
6756       case PlayFromGameFile:
6757             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6758       case EditGame:
6759         if (!white_piece && WhiteOnMove(currentMove)) {
6760             DisplayMoveError(_("It is White's turn"));
6761             return FALSE;
6762         }
6763         if (white_piece && !WhiteOnMove(currentMove)) {
6764             DisplayMoveError(_("It is Black's turn"));
6765             return FALSE;
6766         }
6767         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6768             /* Editing correspondence game history */
6769             /* Could disallow this or prompt for confirmation */
6770             cmailOldMove = -1;
6771         }
6772         break;
6773
6774       case BeginningOfGame:
6775         if (appData.icsActive) return FALSE;
6776         if (!appData.noChessProgram) {
6777             if (!white_piece) {
6778                 DisplayMoveError(_("You are playing White"));
6779                 return FALSE;
6780             }
6781         }
6782         break;
6783
6784       case Training:
6785         if (!white_piece && WhiteOnMove(currentMove)) {
6786             DisplayMoveError(_("It is White's turn"));
6787             return FALSE;
6788         }
6789         if (white_piece && !WhiteOnMove(currentMove)) {
6790             DisplayMoveError(_("It is Black's turn"));
6791             return FALSE;
6792         }
6793         break;
6794
6795       default:
6796       case IcsExamining:
6797         break;
6798     }
6799     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6800         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6801         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6802         && gameMode != AnalyzeFile && gameMode != Training) {
6803         DisplayMoveError(_("Displayed position is not current"));
6804         return FALSE;
6805     }
6806     return TRUE;
6807 }
6808
6809 Boolean
6810 OnlyMove (int *x, int *y, Boolean captures)
6811 {
6812     DisambiguateClosure cl;
6813     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6814     switch(gameMode) {
6815       case MachinePlaysBlack:
6816       case IcsPlayingWhite:
6817       case BeginningOfGame:
6818         if(!WhiteOnMove(currentMove)) return FALSE;
6819         break;
6820       case MachinePlaysWhite:
6821       case IcsPlayingBlack:
6822         if(WhiteOnMove(currentMove)) return FALSE;
6823         break;
6824       case EditGame:
6825         break;
6826       default:
6827         return FALSE;
6828     }
6829     cl.pieceIn = EmptySquare;
6830     cl.rfIn = *y;
6831     cl.ffIn = *x;
6832     cl.rtIn = -1;
6833     cl.ftIn = -1;
6834     cl.promoCharIn = NULLCHAR;
6835     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6836     if( cl.kind == NormalMove ||
6837         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6838         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6839         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6840       fromX = cl.ff;
6841       fromY = cl.rf;
6842       *x = cl.ft;
6843       *y = cl.rt;
6844       return TRUE;
6845     }
6846     if(cl.kind != ImpossibleMove) return FALSE;
6847     cl.pieceIn = EmptySquare;
6848     cl.rfIn = -1;
6849     cl.ffIn = -1;
6850     cl.rtIn = *y;
6851     cl.ftIn = *x;
6852     cl.promoCharIn = NULLCHAR;
6853     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6854     if( cl.kind == NormalMove ||
6855         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6856         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6857         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6858       fromX = cl.ff;
6859       fromY = cl.rf;
6860       *x = cl.ft;
6861       *y = cl.rt;
6862       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6863       return TRUE;
6864     }
6865     return FALSE;
6866 }
6867
6868 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6869 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6870 int lastLoadGameUseList = FALSE;
6871 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6872 ChessMove lastLoadGameStart = EndOfFile;
6873 int doubleClick;
6874 Boolean addToBookFlag;
6875
6876 void
6877 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6878 {
6879     ChessMove moveType;
6880     ChessSquare pup;
6881     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6882
6883     /* Check if the user is playing in turn.  This is complicated because we
6884        let the user "pick up" a piece before it is his turn.  So the piece he
6885        tried to pick up may have been captured by the time he puts it down!
6886        Therefore we use the color the user is supposed to be playing in this
6887        test, not the color of the piece that is currently on the starting
6888        square---except in EditGame mode, where the user is playing both
6889        sides; fortunately there the capture race can't happen.  (It can
6890        now happen in IcsExamining mode, but that's just too bad.  The user
6891        will get a somewhat confusing message in that case.)
6892        */
6893
6894     switch (gameMode) {
6895       case AnalyzeFile:
6896       case TwoMachinesPlay:
6897       case EndOfGame:
6898       case IcsObserving:
6899       case IcsIdle:
6900         /* We switched into a game mode where moves are not accepted,
6901            perhaps while the mouse button was down. */
6902         return;
6903
6904       case MachinePlaysWhite:
6905         /* User is moving for Black */
6906         if (WhiteOnMove(currentMove)) {
6907             DisplayMoveError(_("It is White's turn"));
6908             return;
6909         }
6910         break;
6911
6912       case MachinePlaysBlack:
6913         /* User is moving for White */
6914         if (!WhiteOnMove(currentMove)) {
6915             DisplayMoveError(_("It is Black's turn"));
6916             return;
6917         }
6918         break;
6919
6920       case PlayFromGameFile:
6921             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6922       case EditGame:
6923       case IcsExamining:
6924       case BeginningOfGame:
6925       case AnalyzeMode:
6926       case Training:
6927         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6928         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6929             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6930             /* User is moving for Black */
6931             if (WhiteOnMove(currentMove)) {
6932                 DisplayMoveError(_("It is White's turn"));
6933                 return;
6934             }
6935         } else {
6936             /* User is moving for White */
6937             if (!WhiteOnMove(currentMove)) {
6938                 DisplayMoveError(_("It is Black's turn"));
6939                 return;
6940             }
6941         }
6942         break;
6943
6944       case IcsPlayingBlack:
6945         /* User is moving for Black */
6946         if (WhiteOnMove(currentMove)) {
6947             if (!appData.premove) {
6948                 DisplayMoveError(_("It is White's turn"));
6949             } else if (toX >= 0 && toY >= 0) {
6950                 premoveToX = toX;
6951                 premoveToY = toY;
6952                 premoveFromX = fromX;
6953                 premoveFromY = fromY;
6954                 premovePromoChar = promoChar;
6955                 gotPremove = 1;
6956                 if (appData.debugMode)
6957                     fprintf(debugFP, "Got premove: fromX %d,"
6958                             "fromY %d, toX %d, toY %d\n",
6959                             fromX, fromY, toX, toY);
6960             }
6961             return;
6962         }
6963         break;
6964
6965       case IcsPlayingWhite:
6966         /* User is moving for White */
6967         if (!WhiteOnMove(currentMove)) {
6968             if (!appData.premove) {
6969                 DisplayMoveError(_("It is Black's turn"));
6970             } else if (toX >= 0 && toY >= 0) {
6971                 premoveToX = toX;
6972                 premoveToY = toY;
6973                 premoveFromX = fromX;
6974                 premoveFromY = fromY;
6975                 premovePromoChar = promoChar;
6976                 gotPremove = 1;
6977                 if (appData.debugMode)
6978                     fprintf(debugFP, "Got premove: fromX %d,"
6979                             "fromY %d, toX %d, toY %d\n",
6980                             fromX, fromY, toX, toY);
6981             }
6982             return;
6983         }
6984         break;
6985
6986       default:
6987         break;
6988
6989       case EditPosition:
6990         /* EditPosition, empty square, or different color piece;
6991            click-click move is possible */
6992         if (toX == -2 || toY == -2) {
6993             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6994             DrawPosition(FALSE, boards[currentMove]);
6995             return;
6996         } else if (toX >= 0 && toY >= 0) {
6997             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6998                 ChessSquare q, p = boards[0][rf][ff];
6999                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7000                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
7001                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7002                 if(PieceToChar(q) == '+') gatingPiece = p;
7003             }
7004             boards[0][toY][toX] = boards[0][fromY][fromX];
7005             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7006                 if(boards[0][fromY][0] != EmptySquare) {
7007                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7008                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7009                 }
7010             } else
7011             if(fromX == BOARD_RGHT+1) {
7012                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7013                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7014                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7015                 }
7016             } else
7017             boards[0][fromY][fromX] = gatingPiece;
7018             DrawPosition(FALSE, boards[currentMove]);
7019             return;
7020         }
7021         return;
7022     }
7023
7024     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7025     pup = boards[currentMove][toY][toX];
7026
7027     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7028     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7029          if( pup != EmptySquare ) return;
7030          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7031            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7032                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7033            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7034            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7035            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7036            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7037          fromY = DROP_RANK;
7038     }
7039
7040     /* [HGM] always test for legality, to get promotion info */
7041     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7042                                          fromY, fromX, toY, toX, promoChar);
7043
7044     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7045
7046     /* [HGM] but possibly ignore an IllegalMove result */
7047     if (appData.testLegality) {
7048         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7049             DisplayMoveError(_("Illegal move"));
7050             return;
7051         }
7052     }
7053
7054     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7055         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7056              ClearPremoveHighlights(); // was included
7057         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7058         return;
7059     }
7060
7061     if(addToBookFlag) { // adding moves to book
7062         char buf[MSG_SIZ], move[MSG_SIZ];
7063         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7064         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7065         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7066         AddBookMove(buf);
7067         addToBookFlag = FALSE;
7068         ClearHighlights();
7069         return;
7070     }
7071
7072     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7073 }
7074
7075 /* Common tail of UserMoveEvent and DropMenuEvent */
7076 int
7077 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7078 {
7079     char *bookHit = 0;
7080
7081     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7082         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7083         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7084         if(WhiteOnMove(currentMove)) {
7085             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7086         } else {
7087             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7088         }
7089     }
7090
7091     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7092        move type in caller when we know the move is a legal promotion */
7093     if(moveType == NormalMove && promoChar)
7094         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7095
7096     /* [HGM] <popupFix> The following if has been moved here from
7097        UserMoveEvent(). Because it seemed to belong here (why not allow
7098        piece drops in training games?), and because it can only be
7099        performed after it is known to what we promote. */
7100     if (gameMode == Training) {
7101       /* compare the move played on the board to the next move in the
7102        * game. If they match, display the move and the opponent's response.
7103        * If they don't match, display an error message.
7104        */
7105       int saveAnimate;
7106       Board testBoard;
7107       CopyBoard(testBoard, boards[currentMove]);
7108       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7109
7110       if (CompareBoards(testBoard, boards[currentMove+1])) {
7111         ForwardInner(currentMove+1);
7112
7113         /* Autoplay the opponent's response.
7114          * if appData.animate was TRUE when Training mode was entered,
7115          * the response will be animated.
7116          */
7117         saveAnimate = appData.animate;
7118         appData.animate = animateTraining;
7119         ForwardInner(currentMove+1);
7120         appData.animate = saveAnimate;
7121
7122         /* check for the end of the game */
7123         if (currentMove >= forwardMostMove) {
7124           gameMode = PlayFromGameFile;
7125           ModeHighlight();
7126           SetTrainingModeOff();
7127           DisplayInformation(_("End of game"));
7128         }
7129       } else {
7130         DisplayError(_("Incorrect move"), 0);
7131       }
7132       return 1;
7133     }
7134
7135   /* Ok, now we know that the move is good, so we can kill
7136      the previous line in Analysis Mode */
7137   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7138                                 && currentMove < forwardMostMove) {
7139     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7140     else forwardMostMove = currentMove;
7141   }
7142
7143   ClearMap();
7144
7145   /* If we need the chess program but it's dead, restart it */
7146   ResurrectChessProgram();
7147
7148   /* A user move restarts a paused game*/
7149   if (pausing)
7150     PauseEvent();
7151
7152   thinkOutput[0] = NULLCHAR;
7153
7154   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7155
7156   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7157     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7158     return 1;
7159   }
7160
7161   if (gameMode == BeginningOfGame) {
7162     if (appData.noChessProgram) {
7163       gameMode = EditGame;
7164       SetGameInfo();
7165     } else {
7166       char buf[MSG_SIZ];
7167       gameMode = MachinePlaysBlack;
7168       StartClocks();
7169       SetGameInfo();
7170       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7171       DisplayTitle(buf);
7172       if (first.sendName) {
7173         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7174         SendToProgram(buf, &first);
7175       }
7176       StartClocks();
7177     }
7178     ModeHighlight();
7179   }
7180
7181   /* Relay move to ICS or chess engine */
7182   if (appData.icsActive) {
7183     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7184         gameMode == IcsExamining) {
7185       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7186         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7187         SendToICS("draw ");
7188         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7189       }
7190       // also send plain move, in case ICS does not understand atomic claims
7191       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7192       ics_user_moved = 1;
7193     }
7194   } else {
7195     if (first.sendTime && (gameMode == BeginningOfGame ||
7196                            gameMode == MachinePlaysWhite ||
7197                            gameMode == MachinePlaysBlack)) {
7198       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7199     }
7200     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7201          // [HGM] book: if program might be playing, let it use book
7202         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7203         first.maybeThinking = TRUE;
7204     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7205         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7206         SendBoard(&first, currentMove+1);
7207         if(second.analyzing) {
7208             if(!second.useSetboard) SendToProgram("undo\n", &second);
7209             SendBoard(&second, currentMove+1);
7210         }
7211     } else {
7212         SendMoveToProgram(forwardMostMove-1, &first);
7213         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7214     }
7215     if (currentMove == cmailOldMove + 1) {
7216       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7217     }
7218   }
7219
7220   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7221
7222   switch (gameMode) {
7223   case EditGame:
7224     if(appData.testLegality)
7225     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7226     case MT_NONE:
7227     case MT_CHECK:
7228       break;
7229     case MT_CHECKMATE:
7230     case MT_STAINMATE:
7231       if (WhiteOnMove(currentMove)) {
7232         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7233       } else {
7234         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7235       }
7236       break;
7237     case MT_STALEMATE:
7238       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7239       break;
7240     }
7241     break;
7242
7243   case MachinePlaysBlack:
7244   case MachinePlaysWhite:
7245     /* disable certain menu options while machine is thinking */
7246     SetMachineThinkingEnables();
7247     break;
7248
7249   default:
7250     break;
7251   }
7252
7253   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7254   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7255
7256   if(bookHit) { // [HGM] book: simulate book reply
7257         static char bookMove[MSG_SIZ]; // a bit generous?
7258
7259         programStats.nodes = programStats.depth = programStats.time =
7260         programStats.score = programStats.got_only_move = 0;
7261         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7262
7263         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7264         strcat(bookMove, bookHit);
7265         HandleMachineMove(bookMove, &first);
7266   }
7267   return 1;
7268 }
7269
7270 void
7271 MarkByFEN(char *fen)
7272 {
7273         int r, f;
7274         if(!appData.markers || !appData.highlightDragging) return;
7275         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7276         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7277         while(*fen) {
7278             int s = 0;
7279             marker[r][f] = 0;
7280             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7281             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7282             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7283             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7284             if(*fen == 'T') marker[r][f++] = 0; else
7285             if(*fen == 'Y') marker[r][f++] = 1; else
7286             if(*fen == 'G') marker[r][f++] = 3; else
7287             if(*fen == 'B') marker[r][f++] = 4; else
7288             if(*fen == 'C') marker[r][f++] = 5; else
7289             if(*fen == 'M') marker[r][f++] = 6; else
7290             if(*fen == 'W') marker[r][f++] = 7; else
7291             if(*fen == 'D') marker[r][f++] = 8; else
7292             if(*fen == 'R') marker[r][f++] = 2; else {
7293                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7294               f += s; fen -= s>0;
7295             }
7296             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7297             if(r < 0) break;
7298             fen++;
7299         }
7300         DrawPosition(TRUE, NULL);
7301 }
7302
7303 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7304
7305 void
7306 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7307 {
7308     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7309     Markers *m = (Markers *) closure;
7310     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7311         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7312                          || kind == WhiteCapturesEnPassant
7313                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7314     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7315 }
7316
7317 static int hoverSavedValid;
7318
7319 void
7320 MarkTargetSquares (int clear)
7321 {
7322   int x, y, sum=0;
7323   if(clear) { // no reason to ever suppress clearing
7324     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7325     hoverSavedValid = 0;
7326     if(!sum) return; // nothing was cleared,no redraw needed
7327   } else {
7328     int capt = 0;
7329     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7330        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7331     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7332     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7333       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7334       if(capt)
7335       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7336     }
7337   }
7338   DrawPosition(FALSE, NULL);
7339 }
7340
7341 int
7342 Explode (Board board, int fromX, int fromY, int toX, int toY)
7343 {
7344     if(gameInfo.variant == VariantAtomic &&
7345        (board[toY][toX] != EmptySquare ||                     // capture?
7346         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7347                          board[fromY][fromX] == BlackPawn   )
7348       )) {
7349         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7350         return TRUE;
7351     }
7352     return FALSE;
7353 }
7354
7355 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7356
7357 int
7358 CanPromote (ChessSquare piece, int y)
7359 {
7360         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7361         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7362         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7363         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7364            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7365            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7366          gameInfo.variant == VariantMakruk) return FALSE;
7367         return (piece == BlackPawn && y <= zone ||
7368                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7369                 piece == BlackLance && y <= zone ||
7370                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7371 }
7372
7373 void
7374 HoverEvent (int xPix, int yPix, int x, int y)
7375 {
7376         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7377         int r, f;
7378         if(!first.highlight) return;
7379         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7380         if(x == oldX && y == oldY) return; // only do something if we enter new square
7381         oldFromX = fromX; oldFromY = fromY;
7382         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7383           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7384             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7385           hoverSavedValid = 1;
7386         } else if(oldX != x || oldY != y) {
7387           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7388           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7389           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7390             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7391           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7392             char buf[MSG_SIZ];
7393             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7394             SendToProgram(buf, &first);
7395           }
7396           oldX = x; oldY = y;
7397 //        SetHighlights(fromX, fromY, x, y);
7398         }
7399 }
7400
7401 void ReportClick(char *action, int x, int y)
7402 {
7403         char buf[MSG_SIZ]; // Inform engine of what user does
7404         int r, f;
7405         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7406           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7407             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7408         if(!first.highlight || gameMode == EditPosition) return;
7409         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7410         SendToProgram(buf, &first);
7411 }
7412
7413 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7414
7415 void
7416 LeftClick (ClickType clickType, int xPix, int yPix)
7417 {
7418     int x, y;
7419     Boolean saveAnimate;
7420     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7421     char promoChoice = NULLCHAR;
7422     ChessSquare piece;
7423     static TimeMark lastClickTime, prevClickTime;
7424
7425     x = EventToSquare(xPix, BOARD_WIDTH);
7426     y = EventToSquare(yPix, BOARD_HEIGHT);
7427     if (!flipView && y >= 0) {
7428         y = BOARD_HEIGHT - 1 - y;
7429     }
7430     if (flipView && x >= 0) {
7431         x = BOARD_WIDTH - 1 - x;
7432     }
7433
7434     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7435         static int dummy;
7436         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7437         right = TRUE;
7438         return;
7439     }
7440
7441     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7442
7443     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7444
7445     if (clickType == Press) ErrorPopDown();
7446     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7447
7448     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7449         defaultPromoChoice = promoSweep;
7450         promoSweep = EmptySquare;   // terminate sweep
7451         promoDefaultAltered = TRUE;
7452         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7453     }
7454
7455     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7456         if(clickType == Release) return; // ignore upclick of click-click destination
7457         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7458         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7459         if(gameInfo.holdingsWidth &&
7460                 (WhiteOnMove(currentMove)
7461                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7462                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7463             // click in right holdings, for determining promotion piece
7464             ChessSquare p = boards[currentMove][y][x];
7465             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7466             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7467             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7468                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7469                 fromX = fromY = -1;
7470                 return;
7471             }
7472         }
7473         DrawPosition(FALSE, boards[currentMove]);
7474         return;
7475     }
7476
7477     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7478     if(clickType == Press
7479             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7480               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7481               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7482         return;
7483
7484     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7485         // could be static click on premove from-square: abort premove
7486         gotPremove = 0;
7487         ClearPremoveHighlights();
7488     }
7489
7490     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7491         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7492
7493     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7494         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7495                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7496         defaultPromoChoice = DefaultPromoChoice(side);
7497     }
7498
7499     autoQueen = appData.alwaysPromoteToQueen;
7500
7501     if (fromX == -1) {
7502       int originalY = y;
7503       gatingPiece = EmptySquare;
7504       if (clickType != Press) {
7505         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7506             DragPieceEnd(xPix, yPix); dragging = 0;
7507             DrawPosition(FALSE, NULL);
7508         }
7509         return;
7510       }
7511       doubleClick = FALSE;
7512       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7513         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7514       }
7515       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7516       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7517          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7518          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7519             /* First square */
7520             if (OKToStartUserMove(fromX, fromY)) {
7521                 second = 0;
7522                 ReportClick("lift", x, y);
7523                 MarkTargetSquares(0);
7524                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7525                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7526                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7527                     promoSweep = defaultPromoChoice;
7528                     selectFlag = 0; lastX = xPix; lastY = yPix;
7529                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7530                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7531                 }
7532                 if (appData.highlightDragging) {
7533                     SetHighlights(fromX, fromY, -1, -1);
7534                 } else {
7535                     ClearHighlights();
7536                 }
7537             } else fromX = fromY = -1;
7538             return;
7539         }
7540     }
7541 printf("to click %d,%d\n",x,y);
7542     /* fromX != -1 */
7543     if (clickType == Press && gameMode != EditPosition) {
7544         ChessSquare fromP;
7545         ChessSquare toP;
7546         int frc;
7547
7548         // ignore off-board to clicks
7549         if(y < 0 || x < 0) return;
7550
7551         /* Check if clicking again on the same color piece */
7552         fromP = boards[currentMove][fromY][fromX];
7553         toP = boards[currentMove][y][x];
7554         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7555         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7556             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7557            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7558              WhitePawn <= toP && toP <= WhiteKing &&
7559              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7560              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7561             (BlackPawn <= fromP && fromP <= BlackKing &&
7562              BlackPawn <= toP && toP <= BlackKing &&
7563              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7564              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7565             /* Clicked again on same color piece -- changed his mind */
7566             second = (x == fromX && y == fromY);
7567             killX = killY = -1;
7568             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7569                 second = FALSE; // first double-click rather than scond click
7570                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7571             }
7572             promoDefaultAltered = FALSE;
7573             MarkTargetSquares(1);
7574            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7575             if (appData.highlightDragging) {
7576                 SetHighlights(x, y, -1, -1);
7577             } else {
7578                 ClearHighlights();
7579             }
7580             if (OKToStartUserMove(x, y)) {
7581                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7582                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7583                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7584                  gatingPiece = boards[currentMove][fromY][fromX];
7585                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7586                 fromX = x;
7587                 fromY = y; dragging = 1;
7588                 if(!second) ReportClick("lift", x, y);
7589                 MarkTargetSquares(0);
7590                 DragPieceBegin(xPix, yPix, FALSE);
7591                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7592                     promoSweep = defaultPromoChoice;
7593                     selectFlag = 0; lastX = xPix; lastY = yPix;
7594                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7595                 }
7596             }
7597            }
7598            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7599            second = FALSE;
7600         }
7601         // ignore clicks on holdings
7602         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7603     }
7604 printf("A type=%d\n",clickType);
7605
7606     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7607         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7608         return;
7609     }
7610
7611     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7612         DragPieceEnd(xPix, yPix); dragging = 0;
7613         if(clearFlag) {
7614             // a deferred attempt to click-click move an empty square on top of a piece
7615             boards[currentMove][y][x] = EmptySquare;
7616             ClearHighlights();
7617             DrawPosition(FALSE, boards[currentMove]);
7618             fromX = fromY = -1; clearFlag = 0;
7619             return;
7620         }
7621         if (appData.animateDragging) {
7622             /* Undo animation damage if any */
7623             DrawPosition(FALSE, NULL);
7624         }
7625         if (second) {
7626             /* Second up/down in same square; just abort move */
7627             second = 0;
7628             fromX = fromY = -1;
7629             gatingPiece = EmptySquare;
7630             MarkTargetSquares(1);
7631             ClearHighlights();
7632             gotPremove = 0;
7633             ClearPremoveHighlights();
7634         } else {
7635             /* First upclick in same square; start click-click mode */
7636             SetHighlights(x, y, -1, -1);
7637         }
7638         return;
7639     }
7640
7641     clearFlag = 0;
7642 printf("B\n");
7643     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7644        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7645         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7646         DisplayMessage(_("only marked squares are legal"),"");
7647         DrawPosition(TRUE, NULL);
7648         return; // ignore to-click
7649     }
7650 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7651     /* we now have a different from- and (possibly off-board) to-square */
7652     /* Completed move */
7653     if(!sweepSelecting) {
7654         toX = x;
7655         toY = y;
7656     }
7657
7658     piece = boards[currentMove][fromY][fromX];
7659
7660     saveAnimate = appData.animate;
7661     if (clickType == Press) {
7662         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7663         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7664             // must be Edit Position mode with empty-square selected
7665             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7666             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7667             return;
7668         }
7669         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7670             return;
7671         }
7672         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7673             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7674         } else
7675         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7676         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7677           if(appData.sweepSelect) {
7678             promoSweep = defaultPromoChoice;
7679             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7680             selectFlag = 0; lastX = xPix; lastY = yPix;
7681             Sweep(0); // Pawn that is going to promote: preview promotion piece
7682             sweepSelecting = 1;
7683             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7684             MarkTargetSquares(1);
7685           }
7686           return; // promo popup appears on up-click
7687         }
7688         /* Finish clickclick move */
7689         if (appData.animate || appData.highlightLastMove) {
7690             SetHighlights(fromX, fromY, toX, toY);
7691         } else {
7692             ClearHighlights();
7693         }
7694     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7695         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7696         if (appData.animate || appData.highlightLastMove) {
7697             SetHighlights(fromX, fromY, toX, toY);
7698         } else {
7699             ClearHighlights();
7700         }
7701     } else {
7702 #if 0
7703 // [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
7704         /* Finish drag move */
7705         if (appData.highlightLastMove) {
7706             SetHighlights(fromX, fromY, toX, toY);
7707         } else {
7708             ClearHighlights();
7709         }
7710 #endif
7711         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7712         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7713           dragging *= 2;            // flag button-less dragging if we are dragging
7714           MarkTargetSquares(1);
7715           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7716           else {
7717             kill2X = killX; kill2Y = killY;
7718             killX = x; killY = y;     //remeber this square as intermediate
7719             ReportClick("put", x, y); // and inform engine
7720             ReportClick("lift", x, y);
7721             MarkTargetSquares(0);
7722             return;
7723           }
7724         }
7725         DragPieceEnd(xPix, yPix); dragging = 0;
7726         /* Don't animate move and drag both */
7727         appData.animate = FALSE;
7728     }
7729
7730     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7731     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7732         ChessSquare piece = boards[currentMove][fromY][fromX];
7733         if(gameMode == EditPosition && piece != EmptySquare &&
7734            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7735             int n;
7736
7737             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7738                 n = PieceToNumber(piece - (int)BlackPawn);
7739                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7740                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7741                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7742             } else
7743             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7744                 n = PieceToNumber(piece);
7745                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7746                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7747                 boards[currentMove][n][BOARD_WIDTH-2]++;
7748             }
7749             boards[currentMove][fromY][fromX] = EmptySquare;
7750         }
7751         ClearHighlights();
7752         fromX = fromY = -1;
7753         MarkTargetSquares(1);
7754         DrawPosition(TRUE, boards[currentMove]);
7755         return;
7756     }
7757
7758     // off-board moves should not be highlighted
7759     if(x < 0 || y < 0) ClearHighlights();
7760     else ReportClick("put", x, y);
7761
7762     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7763
7764     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7765
7766     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7767         SetHighlights(fromX, fromY, toX, toY);
7768         MarkTargetSquares(1);
7769         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7770             // [HGM] super: promotion to captured piece selected from holdings
7771             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7772             promotionChoice = TRUE;
7773             // kludge follows to temporarily execute move on display, without promoting yet
7774             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7775             boards[currentMove][toY][toX] = p;
7776             DrawPosition(FALSE, boards[currentMove]);
7777             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7778             boards[currentMove][toY][toX] = q;
7779             DisplayMessage("Click in holdings to choose piece", "");
7780             return;
7781         }
7782         PromotionPopUp(promoChoice);
7783     } else {
7784         int oldMove = currentMove;
7785         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7786         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7787         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7788         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7789            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7790             DrawPosition(TRUE, boards[currentMove]);
7791         MarkTargetSquares(1);
7792         fromX = fromY = -1;
7793     }
7794     appData.animate = saveAnimate;
7795     if (appData.animate || appData.animateDragging) {
7796         /* Undo animation damage if needed */
7797         DrawPosition(FALSE, NULL);
7798     }
7799 }
7800
7801 int
7802 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7803 {   // front-end-free part taken out of PieceMenuPopup
7804     int whichMenu; int xSqr, ySqr;
7805
7806     if(seekGraphUp) { // [HGM] seekgraph
7807         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7808         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7809         return -2;
7810     }
7811
7812     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7813          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7814         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7815         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7816         if(action == Press)   {
7817             originalFlip = flipView;
7818             flipView = !flipView; // temporarily flip board to see game from partners perspective
7819             DrawPosition(TRUE, partnerBoard);
7820             DisplayMessage(partnerStatus, "");
7821             partnerUp = TRUE;
7822         } else if(action == Release) {
7823             flipView = originalFlip;
7824             DrawPosition(TRUE, boards[currentMove]);
7825             partnerUp = FALSE;
7826         }
7827         return -2;
7828     }
7829
7830     xSqr = EventToSquare(x, BOARD_WIDTH);
7831     ySqr = EventToSquare(y, BOARD_HEIGHT);
7832     if (action == Release) {
7833         if(pieceSweep != EmptySquare) {
7834             EditPositionMenuEvent(pieceSweep, toX, toY);
7835             pieceSweep = EmptySquare;
7836         } else UnLoadPV(); // [HGM] pv
7837     }
7838     if (action != Press) return -2; // return code to be ignored
7839     switch (gameMode) {
7840       case IcsExamining:
7841         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7842       case EditPosition:
7843         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7844         if (xSqr < 0 || ySqr < 0) return -1;
7845         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7846         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7847         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7848         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7849         NextPiece(0);
7850         return 2; // grab
7851       case IcsObserving:
7852         if(!appData.icsEngineAnalyze) return -1;
7853       case IcsPlayingWhite:
7854       case IcsPlayingBlack:
7855         if(!appData.zippyPlay) goto noZip;
7856       case AnalyzeMode:
7857       case AnalyzeFile:
7858       case MachinePlaysWhite:
7859       case MachinePlaysBlack:
7860       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7861         if (!appData.dropMenu) {
7862           LoadPV(x, y);
7863           return 2; // flag front-end to grab mouse events
7864         }
7865         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7866            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7867       case EditGame:
7868       noZip:
7869         if (xSqr < 0 || ySqr < 0) return -1;
7870         if (!appData.dropMenu || appData.testLegality &&
7871             gameInfo.variant != VariantBughouse &&
7872             gameInfo.variant != VariantCrazyhouse) return -1;
7873         whichMenu = 1; // drop menu
7874         break;
7875       default:
7876         return -1;
7877     }
7878
7879     if (((*fromX = xSqr) < 0) ||
7880         ((*fromY = ySqr) < 0)) {
7881         *fromX = *fromY = -1;
7882         return -1;
7883     }
7884     if (flipView)
7885       *fromX = BOARD_WIDTH - 1 - *fromX;
7886     else
7887       *fromY = BOARD_HEIGHT - 1 - *fromY;
7888
7889     return whichMenu;
7890 }
7891
7892 void
7893 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7894 {
7895 //    char * hint = lastHint;
7896     FrontEndProgramStats stats;
7897
7898     stats.which = cps == &first ? 0 : 1;
7899     stats.depth = cpstats->depth;
7900     stats.nodes = cpstats->nodes;
7901     stats.score = cpstats->score;
7902     stats.time = cpstats->time;
7903     stats.pv = cpstats->movelist;
7904     stats.hint = lastHint;
7905     stats.an_move_index = 0;
7906     stats.an_move_count = 0;
7907
7908     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7909         stats.hint = cpstats->move_name;
7910         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7911         stats.an_move_count = cpstats->nr_moves;
7912     }
7913
7914     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
7915
7916     SetProgramStats( &stats );
7917 }
7918
7919 void
7920 ClearEngineOutputPane (int which)
7921 {
7922     static FrontEndProgramStats dummyStats;
7923     dummyStats.which = which;
7924     dummyStats.pv = "#";
7925     SetProgramStats( &dummyStats );
7926 }
7927
7928 #define MAXPLAYERS 500
7929
7930 char *
7931 TourneyStandings (int display)
7932 {
7933     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7934     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7935     char result, *p, *names[MAXPLAYERS];
7936
7937     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7938         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7939     names[0] = p = strdup(appData.participants);
7940     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7941
7942     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7943
7944     while(result = appData.results[nr]) {
7945         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7946         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7947         wScore = bScore = 0;
7948         switch(result) {
7949           case '+': wScore = 2; break;
7950           case '-': bScore = 2; break;
7951           case '=': wScore = bScore = 1; break;
7952           case ' ':
7953           case '*': return strdup("busy"); // tourney not finished
7954         }
7955         score[w] += wScore;
7956         score[b] += bScore;
7957         games[w]++;
7958         games[b]++;
7959         nr++;
7960     }
7961     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7962     for(w=0; w<nPlayers; w++) {
7963         bScore = -1;
7964         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7965         ranking[w] = b; points[w] = bScore; score[b] = -2;
7966     }
7967     p = malloc(nPlayers*34+1);
7968     for(w=0; w<nPlayers && w<display; w++)
7969         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7970     free(names[0]);
7971     return p;
7972 }
7973
7974 void
7975 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7976 {       // count all piece types
7977         int p, f, r;
7978         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7979         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7980         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7981                 p = board[r][f];
7982                 pCnt[p]++;
7983                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7984                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7985                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7986                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7987                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7988                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7989         }
7990 }
7991
7992 int
7993 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7994 {
7995         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7996         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7997
7998         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7999         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8000         if(myPawns == 2 && nMine == 3) // KPP
8001             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8002         if(myPawns == 1 && nMine == 2) // KP
8003             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8004         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8005             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8006         if(myPawns) return FALSE;
8007         if(pCnt[WhiteRook+side])
8008             return pCnt[BlackRook-side] ||
8009                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8010                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8011                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8012         if(pCnt[WhiteCannon+side]) {
8013             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8014             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8015         }
8016         if(pCnt[WhiteKnight+side])
8017             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8018         return FALSE;
8019 }
8020
8021 int
8022 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8023 {
8024         VariantClass v = gameInfo.variant;
8025
8026         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8027         if(v == VariantShatranj) return TRUE; // always winnable through baring
8028         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8029         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8030
8031         if(v == VariantXiangqi) {
8032                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8033
8034                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8035                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8036                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8037                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8038                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8039                 if(stale) // we have at least one last-rank P plus perhaps C
8040                     return majors // KPKX
8041                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8042                 else // KCA*E*
8043                     return pCnt[WhiteFerz+side] // KCAK
8044                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8045                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8046                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8047
8048         } else if(v == VariantKnightmate) {
8049                 if(nMine == 1) return FALSE;
8050                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8051         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8052                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8053
8054                 if(nMine == 1) return FALSE; // bare King
8055                 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
8056                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8057                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8058                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8059                 if(pCnt[WhiteKnight+side])
8060                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8061                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8062                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8063                 if(nBishops)
8064                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8065                 if(pCnt[WhiteAlfil+side])
8066                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8067                 if(pCnt[WhiteWazir+side])
8068                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8069         }
8070
8071         return TRUE;
8072 }
8073
8074 int
8075 CompareWithRights (Board b1, Board b2)
8076 {
8077     int rights = 0;
8078     if(!CompareBoards(b1, b2)) return FALSE;
8079     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8080     /* compare castling rights */
8081     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8082            rights++; /* King lost rights, while rook still had them */
8083     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8084         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8085            rights++; /* but at least one rook lost them */
8086     }
8087     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8088            rights++;
8089     if( b1[CASTLING][5] != NoRights ) {
8090         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8091            rights++;
8092     }
8093     return rights == 0;
8094 }
8095
8096 int
8097 Adjudicate (ChessProgramState *cps)
8098 {       // [HGM] some adjudications useful with buggy engines
8099         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8100         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8101         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8102         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8103         int k, drop, count = 0; static int bare = 1;
8104         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8105         Boolean canAdjudicate = !appData.icsActive;
8106
8107         // most tests only when we understand the game, i.e. legality-checking on
8108             if( appData.testLegality )
8109             {   /* [HGM] Some more adjudications for obstinate engines */
8110                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8111                 static int moveCount = 6;
8112                 ChessMove result;
8113                 char *reason = NULL;
8114
8115                 /* Count what is on board. */
8116                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8117
8118                 /* Some material-based adjudications that have to be made before stalemate test */
8119                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8120                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8121                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8122                      if(canAdjudicate && appData.checkMates) {
8123                          if(engineOpponent)
8124                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8125                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8126                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8127                          return 1;
8128                      }
8129                 }
8130
8131                 /* Bare King in Shatranj (loses) or Losers (wins) */
8132                 if( nrW == 1 || nrB == 1) {
8133                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8134                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8135                      if(canAdjudicate && appData.checkMates) {
8136                          if(engineOpponent)
8137                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8138                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8139                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8140                          return 1;
8141                      }
8142                   } else
8143                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8144                   {    /* bare King */
8145                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8146                         if(canAdjudicate && appData.checkMates) {
8147                             /* but only adjudicate if adjudication enabled */
8148                             if(engineOpponent)
8149                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8150                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8151                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8152                             return 1;
8153                         }
8154                   }
8155                 } else bare = 1;
8156
8157
8158             // don't wait for engine to announce game end if we can judge ourselves
8159             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8160               case MT_CHECK:
8161                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8162                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8163                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8164                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8165                             checkCnt++;
8166                         if(checkCnt >= 2) {
8167                             reason = "Xboard adjudication: 3rd check";
8168                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8169                             break;
8170                         }
8171                     }
8172                 }
8173               case MT_NONE:
8174               default:
8175                 break;
8176               case MT_STEALMATE:
8177               case MT_STALEMATE:
8178               case MT_STAINMATE:
8179                 reason = "Xboard adjudication: Stalemate";
8180                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8181                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8182                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8183                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8184                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8185                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8186                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8187                                                                         EP_CHECKMATE : EP_WINS);
8188                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8189                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8190                 }
8191                 break;
8192               case MT_CHECKMATE:
8193                 reason = "Xboard adjudication: Checkmate";
8194                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8195                 if(gameInfo.variant == VariantShogi) {
8196                     if(forwardMostMove > backwardMostMove
8197                        && moveList[forwardMostMove-1][1] == '@'
8198                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8199                         reason = "XBoard adjudication: pawn-drop mate";
8200                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8201                     }
8202                 }
8203                 break;
8204             }
8205
8206                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8207                     case EP_STALEMATE:
8208                         result = GameIsDrawn; break;
8209                     case EP_CHECKMATE:
8210                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8211                     case EP_WINS:
8212                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8213                     default:
8214                         result = EndOfFile;
8215                 }
8216                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8217                     if(engineOpponent)
8218                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8219                     GameEnds( result, reason, GE_XBOARD );
8220                     return 1;
8221                 }
8222
8223                 /* Next absolutely insufficient mating material. */
8224                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8225                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8226                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8227
8228                      /* always flag draws, for judging claims */
8229                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8230
8231                      if(canAdjudicate && appData.materialDraws) {
8232                          /* but only adjudicate them if adjudication enabled */
8233                          if(engineOpponent) {
8234                            SendToProgram("force\n", engineOpponent); // suppress reply
8235                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8236                          }
8237                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8238                          return 1;
8239                      }
8240                 }
8241
8242                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8243                 if(gameInfo.variant == VariantXiangqi ?
8244                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8245                  : nrW + nrB == 4 &&
8246                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8247                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8248                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8249                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8250                    ) ) {
8251                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8252                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8253                           if(engineOpponent) {
8254                             SendToProgram("force\n", engineOpponent); // suppress reply
8255                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8256                           }
8257                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8258                           return 1;
8259                      }
8260                 } else moveCount = 6;
8261             }
8262
8263         // Repetition draws and 50-move rule can be applied independently of legality testing
8264
8265                 /* Check for rep-draws */
8266                 count = 0;
8267                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8268                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8269                 for(k = forwardMostMove-2;
8270                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8271                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8272                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8273                     k-=2)
8274                 {   int rights=0;
8275                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8276                         /* compare castling rights */
8277                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8278                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8279                                 rights++; /* King lost rights, while rook still had them */
8280                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8281                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8282                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8283                                    rights++; /* but at least one rook lost them */
8284                         }
8285                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8286                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8287                                 rights++;
8288                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8289                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8290                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8291                                    rights++;
8292                         }
8293                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8294                             && appData.drawRepeats > 1) {
8295                              /* adjudicate after user-specified nr of repeats */
8296                              int result = GameIsDrawn;
8297                              char *details = "XBoard adjudication: repetition draw";
8298                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8299                                 // [HGM] xiangqi: check for forbidden perpetuals
8300                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8301                                 for(m=forwardMostMove; m>k; m-=2) {
8302                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8303                                         ourPerpetual = 0; // the current mover did not always check
8304                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8305                                         hisPerpetual = 0; // the opponent did not always check
8306                                 }
8307                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8308                                                                         ourPerpetual, hisPerpetual);
8309                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8310                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8311                                     details = "Xboard adjudication: perpetual checking";
8312                                 } else
8313                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8314                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8315                                 } else
8316                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8317                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8318                                         result = BlackWins;
8319                                         details = "Xboard adjudication: repetition";
8320                                     }
8321                                 } else // it must be XQ
8322                                 // Now check for perpetual chases
8323                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8324                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8325                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8326                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8327                                         static char resdet[MSG_SIZ];
8328                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8329                                         details = resdet;
8330                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8331                                     } else
8332                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8333                                         break; // Abort repetition-checking loop.
8334                                 }
8335                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8336                              }
8337                              if(engineOpponent) {
8338                                SendToProgram("force\n", engineOpponent); // suppress reply
8339                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8340                              }
8341                              GameEnds( result, details, GE_XBOARD );
8342                              return 1;
8343                         }
8344                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8345                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8346                     }
8347                 }
8348
8349                 /* Now we test for 50-move draws. Determine ply count */
8350                 count = forwardMostMove;
8351                 /* look for last irreversble move */
8352                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8353                     count--;
8354                 /* if we hit starting position, add initial plies */
8355                 if( count == backwardMostMove )
8356                     count -= initialRulePlies;
8357                 count = forwardMostMove - count;
8358                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8359                         // adjust reversible move counter for checks in Xiangqi
8360                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8361                         if(i < backwardMostMove) i = backwardMostMove;
8362                         while(i <= forwardMostMove) {
8363                                 lastCheck = inCheck; // check evasion does not count
8364                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8365                                 if(inCheck || lastCheck) count--; // check does not count
8366                                 i++;
8367                         }
8368                 }
8369                 if( count >= 100)
8370                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8371                          /* this is used to judge if draw claims are legal */
8372                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8373                          if(engineOpponent) {
8374                            SendToProgram("force\n", engineOpponent); // suppress reply
8375                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8376                          }
8377                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8378                          return 1;
8379                 }
8380
8381                 /* if draw offer is pending, treat it as a draw claim
8382                  * when draw condition present, to allow engines a way to
8383                  * claim draws before making their move to avoid a race
8384                  * condition occurring after their move
8385                  */
8386                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8387                          char *p = NULL;
8388                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8389                              p = "Draw claim: 50-move rule";
8390                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8391                              p = "Draw claim: 3-fold repetition";
8392                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8393                              p = "Draw claim: insufficient mating material";
8394                          if( p != NULL && canAdjudicate) {
8395                              if(engineOpponent) {
8396                                SendToProgram("force\n", engineOpponent); // suppress reply
8397                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8398                              }
8399                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8400                              return 1;
8401                          }
8402                 }
8403
8404                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8405                     if(engineOpponent) {
8406                       SendToProgram("force\n", engineOpponent); // suppress reply
8407                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8408                     }
8409                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8410                     return 1;
8411                 }
8412         return 0;
8413 }
8414
8415 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8416 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8417 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8418
8419 static int
8420 BitbaseProbe ()
8421 {
8422     int pieces[10], squares[10], cnt=0, r, f, res;
8423     static int loaded;
8424     static PPROBE_EGBB probeBB;
8425     if(!appData.testLegality) return 10;
8426     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8427     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8428     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8429     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8430         ChessSquare piece = boards[forwardMostMove][r][f];
8431         int black = (piece >= BlackPawn);
8432         int type = piece - black*BlackPawn;
8433         if(piece == EmptySquare) continue;
8434         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8435         if(type == WhiteKing) type = WhiteQueen + 1;
8436         type = egbbCode[type];
8437         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8438         pieces[cnt] = type + black*6;
8439         if(++cnt > 5) return 11;
8440     }
8441     pieces[cnt] = squares[cnt] = 0;
8442     // probe EGBB
8443     if(loaded == 2) return 13; // loading failed before
8444     if(loaded == 0) {
8445         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8446         HMODULE lib;
8447         PLOAD_EGBB loadBB;
8448         loaded = 2; // prepare for failure
8449         if(!path) return 13; // no egbb installed
8450         strncpy(buf, path + 8, MSG_SIZ);
8451         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8452         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8453         lib = LoadLibrary(buf);
8454         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8455         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8456         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8457         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8458         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8459         loaded = 1; // success!
8460     }
8461     res = probeBB(forwardMostMove & 1, pieces, squares);
8462     return res > 0 ? 1 : res < 0 ? -1 : 0;
8463 }
8464
8465 char *
8466 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8467 {   // [HGM] book: this routine intercepts moves to simulate book replies
8468     char *bookHit = NULL;
8469
8470     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8471         char buf[MSG_SIZ];
8472         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8473         SendToProgram(buf, cps);
8474     }
8475     //first determine if the incoming move brings opponent into his book
8476     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8477         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8478     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8479     if(bookHit != NULL && !cps->bookSuspend) {
8480         // make sure opponent is not going to reply after receiving move to book position
8481         SendToProgram("force\n", cps);
8482         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8483     }
8484     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8485     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8486     // now arrange restart after book miss
8487     if(bookHit) {
8488         // after a book hit we never send 'go', and the code after the call to this routine
8489         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8490         char buf[MSG_SIZ], *move = bookHit;
8491         if(cps->useSAN) {
8492             int fromX, fromY, toX, toY;
8493             char promoChar;
8494             ChessMove moveType;
8495             move = buf + 30;
8496             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8497                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8498                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8499                                     PosFlags(forwardMostMove),
8500                                     fromY, fromX, toY, toX, promoChar, move);
8501             } else {
8502                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8503                 bookHit = NULL;
8504             }
8505         }
8506         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8507         SendToProgram(buf, cps);
8508         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8509     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8510         SendToProgram("go\n", cps);
8511         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8512     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8513         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8514             SendToProgram("go\n", cps);
8515         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8516     }
8517     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8518 }
8519
8520 int
8521 LoadError (char *errmess, ChessProgramState *cps)
8522 {   // unloads engine and switches back to -ncp mode if it was first
8523     if(cps->initDone) return FALSE;
8524     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8525     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8526     cps->pr = NoProc;
8527     if(cps == &first) {
8528         appData.noChessProgram = TRUE;
8529         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8530         gameMode = BeginningOfGame; ModeHighlight();
8531         SetNCPMode();
8532     }
8533     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8534     DisplayMessage("", ""); // erase waiting message
8535     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8536     return TRUE;
8537 }
8538
8539 char *savedMessage;
8540 ChessProgramState *savedState;
8541 void
8542 DeferredBookMove (void)
8543 {
8544         if(savedState->lastPing != savedState->lastPong)
8545                     ScheduleDelayedEvent(DeferredBookMove, 10);
8546         else
8547         HandleMachineMove(savedMessage, savedState);
8548 }
8549
8550 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8551 static ChessProgramState *stalledEngine;
8552 static char stashedInputMove[MSG_SIZ];
8553
8554 void
8555 HandleMachineMove (char *message, ChessProgramState *cps)
8556 {
8557     static char firstLeg[20];
8558     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8559     char realname[MSG_SIZ];
8560     int fromX, fromY, toX, toY;
8561     ChessMove moveType;
8562     char promoChar, roar;
8563     char *p, *pv=buf1;
8564     int machineWhite, oldError;
8565     char *bookHit;
8566
8567     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8568         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8569         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8570             DisplayError(_("Invalid pairing from pairing engine"), 0);
8571             return;
8572         }
8573         pairingReceived = 1;
8574         NextMatchGame();
8575         return; // Skim the pairing messages here.
8576     }
8577
8578     oldError = cps->userError; cps->userError = 0;
8579
8580 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8581     /*
8582      * Kludge to ignore BEL characters
8583      */
8584     while (*message == '\007') message++;
8585
8586     /*
8587      * [HGM] engine debug message: ignore lines starting with '#' character
8588      */
8589     if(cps->debug && *message == '#') return;
8590
8591     /*
8592      * Look for book output
8593      */
8594     if (cps == &first && bookRequested) {
8595         if (message[0] == '\t' || message[0] == ' ') {
8596             /* Part of the book output is here; append it */
8597             strcat(bookOutput, message);
8598             strcat(bookOutput, "  \n");
8599             return;
8600         } else if (bookOutput[0] != NULLCHAR) {
8601             /* All of book output has arrived; display it */
8602             char *p = bookOutput;
8603             while (*p != NULLCHAR) {
8604                 if (*p == '\t') *p = ' ';
8605                 p++;
8606             }
8607             DisplayInformation(bookOutput);
8608             bookRequested = FALSE;
8609             /* Fall through to parse the current output */
8610         }
8611     }
8612
8613     /*
8614      * Look for machine move.
8615      */
8616     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8617         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8618     {
8619         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8620             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8621             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8622             stalledEngine = cps;
8623             if(appData.ponderNextMove) { // bring opponent out of ponder
8624                 if(gameMode == TwoMachinesPlay) {
8625                     if(cps->other->pause)
8626                         PauseEngine(cps->other);
8627                     else
8628                         SendToProgram("easy\n", cps->other);
8629                 }
8630             }
8631             StopClocks();
8632             return;
8633         }
8634
8635         /* This method is only useful on engines that support ping */
8636         if (cps->lastPing != cps->lastPong) {
8637           if (gameMode == BeginningOfGame) {
8638             /* Extra move from before last new; ignore */
8639             if (appData.debugMode) {
8640                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8641             }
8642           } else {
8643             if (appData.debugMode) {
8644                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8645                         cps->which, gameMode);
8646             }
8647
8648             SendToProgram("undo\n", cps);
8649           }
8650           return;
8651         }
8652
8653         switch (gameMode) {
8654           case BeginningOfGame:
8655             /* Extra move from before last reset; ignore */
8656             if (appData.debugMode) {
8657                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8658             }
8659             return;
8660
8661           case EndOfGame:
8662           case IcsIdle:
8663           default:
8664             /* Extra move after we tried to stop.  The mode test is
8665                not a reliable way of detecting this problem, but it's
8666                the best we can do on engines that don't support ping.
8667             */
8668             if (appData.debugMode) {
8669                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8670                         cps->which, gameMode);
8671             }
8672             SendToProgram("undo\n", cps);
8673             return;
8674
8675           case MachinePlaysWhite:
8676           case IcsPlayingWhite:
8677             machineWhite = TRUE;
8678             break;
8679
8680           case MachinePlaysBlack:
8681           case IcsPlayingBlack:
8682             machineWhite = FALSE;
8683             break;
8684
8685           case TwoMachinesPlay:
8686             machineWhite = (cps->twoMachinesColor[0] == 'w');
8687             break;
8688         }
8689         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8690             if (appData.debugMode) {
8691                 fprintf(debugFP,
8692                         "Ignoring move out of turn by %s, gameMode %d"
8693                         ", forwardMost %d\n",
8694                         cps->which, gameMode, forwardMostMove);
8695             }
8696             return;
8697         }
8698
8699         if(cps->alphaRank) AlphaRank(machineMove, 4);
8700
8701         // [HGM] lion: (some very limited) support for Alien protocol
8702         killX = killY = kill2X = kill2Y = -1;
8703         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8704             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8705             return;
8706         }
8707         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8708             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8709             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8710         }
8711         if(firstLeg[0]) { // there was a previous leg;
8712             // only support case where same piece makes two step
8713             char buf[20], *p = machineMove+1, *q = buf+1, f;
8714             safeStrCpy(buf, machineMove, 20);
8715             while(isdigit(*q)) q++; // find start of to-square
8716             safeStrCpy(machineMove, firstLeg, 20);
8717             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8718             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8719             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8720             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8721             firstLeg[0] = NULLCHAR;
8722         }
8723
8724         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8725                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8726             /* Machine move could not be parsed; ignore it. */
8727           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8728                     machineMove, _(cps->which));
8729             DisplayMoveError(buf1);
8730             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8731                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8732             if (gameMode == TwoMachinesPlay) {
8733               GameEnds(machineWhite ? BlackWins : WhiteWins,
8734                        buf1, GE_XBOARD);
8735             }
8736             return;
8737         }
8738
8739         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8740         /* So we have to redo legality test with true e.p. status here,  */
8741         /* to make sure an illegal e.p. capture does not slip through,   */
8742         /* to cause a forfeit on a justified illegal-move complaint      */
8743         /* of the opponent.                                              */
8744         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8745            ChessMove moveType;
8746            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8747                              fromY, fromX, toY, toX, promoChar);
8748             if(moveType == IllegalMove) {
8749               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8750                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8751                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8752                            buf1, GE_XBOARD);
8753                 return;
8754            } else if(!appData.fischerCastling)
8755            /* [HGM] Kludge to handle engines that send FRC-style castling
8756               when they shouldn't (like TSCP-Gothic) */
8757            switch(moveType) {
8758              case WhiteASideCastleFR:
8759              case BlackASideCastleFR:
8760                toX+=2;
8761                currentMoveString[2]++;
8762                break;
8763              case WhiteHSideCastleFR:
8764              case BlackHSideCastleFR:
8765                toX--;
8766                currentMoveString[2]--;
8767                break;
8768              default: ; // nothing to do, but suppresses warning of pedantic compilers
8769            }
8770         }
8771         hintRequested = FALSE;
8772         lastHint[0] = NULLCHAR;
8773         bookRequested = FALSE;
8774         /* Program may be pondering now */
8775         cps->maybeThinking = TRUE;
8776         if (cps->sendTime == 2) cps->sendTime = 1;
8777         if (cps->offeredDraw) cps->offeredDraw--;
8778
8779         /* [AS] Save move info*/
8780         pvInfoList[ forwardMostMove ].score = programStats.score;
8781         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8782         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8783
8784         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8785
8786         /* Test suites abort the 'game' after one move */
8787         if(*appData.finger) {
8788            static FILE *f;
8789            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8790            if(!f) f = fopen(appData.finger, "w");
8791            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8792            else { DisplayFatalError("Bad output file", errno, 0); return; }
8793            free(fen);
8794            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8795         }
8796
8797         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8798         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8799             int count = 0;
8800
8801             while( count < adjudicateLossPlies ) {
8802                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8803
8804                 if( count & 1 ) {
8805                     score = -score; /* Flip score for winning side */
8806                 }
8807
8808                 if( score > appData.adjudicateLossThreshold ) {
8809                     break;
8810                 }
8811
8812                 count++;
8813             }
8814
8815             if( count >= adjudicateLossPlies ) {
8816                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8817
8818                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8819                     "Xboard adjudication",
8820                     GE_XBOARD );
8821
8822                 return;
8823             }
8824         }
8825
8826         if(Adjudicate(cps)) {
8827             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8828             return; // [HGM] adjudicate: for all automatic game ends
8829         }
8830
8831 #if ZIPPY
8832         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8833             first.initDone) {
8834           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8835                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8836                 SendToICS("draw ");
8837                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8838           }
8839           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8840           ics_user_moved = 1;
8841           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8842                 char buf[3*MSG_SIZ];
8843
8844                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8845                         programStats.score / 100.,
8846                         programStats.depth,
8847                         programStats.time / 100.,
8848                         (unsigned int)programStats.nodes,
8849                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8850                         programStats.movelist);
8851                 SendToICS(buf);
8852           }
8853         }
8854 #endif
8855
8856         /* [AS] Clear stats for next move */
8857         ClearProgramStats();
8858         thinkOutput[0] = NULLCHAR;
8859         hiddenThinkOutputState = 0;
8860
8861         bookHit = NULL;
8862         if (gameMode == TwoMachinesPlay) {
8863             /* [HGM] relaying draw offers moved to after reception of move */
8864             /* and interpreting offer as claim if it brings draw condition */
8865             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8866                 SendToProgram("draw\n", cps->other);
8867             }
8868             if (cps->other->sendTime) {
8869                 SendTimeRemaining(cps->other,
8870                                   cps->other->twoMachinesColor[0] == 'w');
8871             }
8872             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8873             if (firstMove && !bookHit) {
8874                 firstMove = FALSE;
8875                 if (cps->other->useColors) {
8876                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8877                 }
8878                 SendToProgram("go\n", cps->other);
8879             }
8880             cps->other->maybeThinking = TRUE;
8881         }
8882
8883         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8884
8885         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8886
8887         if (!pausing && appData.ringBellAfterMoves) {
8888             if(!roar) RingBell();
8889         }
8890
8891         /*
8892          * Reenable menu items that were disabled while
8893          * machine was thinking
8894          */
8895         if (gameMode != TwoMachinesPlay)
8896             SetUserThinkingEnables();
8897
8898         // [HGM] book: after book hit opponent has received move and is now in force mode
8899         // force the book reply into it, and then fake that it outputted this move by jumping
8900         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8901         if(bookHit) {
8902                 static char bookMove[MSG_SIZ]; // a bit generous?
8903
8904                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8905                 strcat(bookMove, bookHit);
8906                 message = bookMove;
8907                 cps = cps->other;
8908                 programStats.nodes = programStats.depth = programStats.time =
8909                 programStats.score = programStats.got_only_move = 0;
8910                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8911
8912                 if(cps->lastPing != cps->lastPong) {
8913                     savedMessage = message; // args for deferred call
8914                     savedState = cps;
8915                     ScheduleDelayedEvent(DeferredBookMove, 10);
8916                     return;
8917                 }
8918                 goto FakeBookMove;
8919         }
8920
8921         return;
8922     }
8923
8924     /* Set special modes for chess engines.  Later something general
8925      *  could be added here; for now there is just one kludge feature,
8926      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8927      *  when "xboard" is given as an interactive command.
8928      */
8929     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8930         cps->useSigint = FALSE;
8931         cps->useSigterm = FALSE;
8932     }
8933     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8934       ParseFeatures(message+8, cps);
8935       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8936     }
8937
8938     if (!strncmp(message, "setup ", 6) && 
8939         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8940           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8941                                         ) { // [HGM] allow first engine to define opening position
8942       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8943       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8944       *buf = NULLCHAR;
8945       if(sscanf(message, "setup (%s", buf) == 1) {
8946         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
8947         ASSIGN(appData.pieceToCharTable, buf);
8948       }
8949       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8950       if(dummy >= 3) {
8951         while(message[s] && message[s++] != ' ');
8952         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8953            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8954             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8955             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8956           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8957           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
8958           startedFromSetupPosition = FALSE;
8959         }
8960       }
8961       if(startedFromSetupPosition) return;
8962       ParseFEN(boards[0], &dummy, message+s, FALSE);
8963       DrawPosition(TRUE, boards[0]);
8964       CopyBoard(initialPosition, boards[0]);
8965       startedFromSetupPosition = TRUE;
8966       return;
8967     }
8968     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8969       ChessSquare piece = WhitePawn;
8970       char *p=buf2, *q, *s = SUFFIXES, ID = *p;
8971       if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
8972       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
8973       piece += CharToPiece(ID) - WhitePawn;
8974       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8975       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8976       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8977       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8978       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8979       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8980                                                && gameInfo.variant != VariantGreat
8981                                                && gameInfo.variant != VariantFairy    ) return;
8982       if(piece < EmptySquare) {
8983         pieceDefs = TRUE;
8984         ASSIGN(pieceDesc[piece], buf1);
8985         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8986       }
8987       return;
8988     }
8989     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8990      * want this, I was asked to put it in, and obliged.
8991      */
8992     if (!strncmp(message, "setboard ", 9)) {
8993         Board initial_position;
8994
8995         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8996
8997         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8998             DisplayError(_("Bad FEN received from engine"), 0);
8999             return ;
9000         } else {
9001            Reset(TRUE, FALSE);
9002            CopyBoard(boards[0], initial_position);
9003            initialRulePlies = FENrulePlies;
9004            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9005            else gameMode = MachinePlaysBlack;
9006            DrawPosition(FALSE, boards[currentMove]);
9007         }
9008         return;
9009     }
9010
9011     /*
9012      * Look for communication commands
9013      */
9014     if (!strncmp(message, "telluser ", 9)) {
9015         if(message[9] == '\\' && message[10] == '\\')
9016             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9017         PlayTellSound();
9018         DisplayNote(message + 9);
9019         return;
9020     }
9021     if (!strncmp(message, "tellusererror ", 14)) {
9022         cps->userError = 1;
9023         if(message[14] == '\\' && message[15] == '\\')
9024             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9025         PlayTellSound();
9026         DisplayError(message + 14, 0);
9027         return;
9028     }
9029     if (!strncmp(message, "tellopponent ", 13)) {
9030       if (appData.icsActive) {
9031         if (loggedOn) {
9032           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9033           SendToICS(buf1);
9034         }
9035       } else {
9036         DisplayNote(message + 13);
9037       }
9038       return;
9039     }
9040     if (!strncmp(message, "tellothers ", 11)) {
9041       if (appData.icsActive) {
9042         if (loggedOn) {
9043           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9044           SendToICS(buf1);
9045         }
9046       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9047       return;
9048     }
9049     if (!strncmp(message, "tellall ", 8)) {
9050       if (appData.icsActive) {
9051         if (loggedOn) {
9052           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9053           SendToICS(buf1);
9054         }
9055       } else {
9056         DisplayNote(message + 8);
9057       }
9058       return;
9059     }
9060     if (strncmp(message, "warning", 7) == 0) {
9061         /* Undocumented feature, use tellusererror in new code */
9062         DisplayError(message, 0);
9063         return;
9064     }
9065     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9066         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9067         strcat(realname, " query");
9068         AskQuestion(realname, buf2, buf1, cps->pr);
9069         return;
9070     }
9071     /* Commands from the engine directly to ICS.  We don't allow these to be
9072      *  sent until we are logged on. Crafty kibitzes have been known to
9073      *  interfere with the login process.
9074      */
9075     if (loggedOn) {
9076         if (!strncmp(message, "tellics ", 8)) {
9077             SendToICS(message + 8);
9078             SendToICS("\n");
9079             return;
9080         }
9081         if (!strncmp(message, "tellicsnoalias ", 15)) {
9082             SendToICS(ics_prefix);
9083             SendToICS(message + 15);
9084             SendToICS("\n");
9085             return;
9086         }
9087         /* The following are for backward compatibility only */
9088         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9089             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9090             SendToICS(ics_prefix);
9091             SendToICS(message);
9092             SendToICS("\n");
9093             return;
9094         }
9095     }
9096     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9097         if(initPing == cps->lastPong) {
9098             if(gameInfo.variant == VariantUnknown) {
9099                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9100                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9101                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9102             }
9103             initPing = -1;
9104         }
9105         return;
9106     }
9107     if(!strncmp(message, "highlight ", 10)) {
9108         if(appData.testLegality && !*engineVariant && appData.markers) return;
9109         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9110         return;
9111     }
9112     if(!strncmp(message, "click ", 6)) {
9113         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9114         if(appData.testLegality || !appData.oneClick) return;
9115         sscanf(message+6, "%c%d%c", &f, &y, &c);
9116         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9117         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9118         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9119         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9120         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9121         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9122             LeftClick(Release, lastLeftX, lastLeftY);
9123         controlKey  = (c == ',');
9124         LeftClick(Press, x, y);
9125         LeftClick(Release, x, y);
9126         first.highlight = f;
9127         return;
9128     }
9129     /*
9130      * If the move is illegal, cancel it and redraw the board.
9131      * Also deal with other error cases.  Matching is rather loose
9132      * here to accommodate engines written before the spec.
9133      */
9134     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9135         strncmp(message, "Error", 5) == 0) {
9136         if (StrStr(message, "name") ||
9137             StrStr(message, "rating") || StrStr(message, "?") ||
9138             StrStr(message, "result") || StrStr(message, "board") ||
9139             StrStr(message, "bk") || StrStr(message, "computer") ||
9140             StrStr(message, "variant") || StrStr(message, "hint") ||
9141             StrStr(message, "random") || StrStr(message, "depth") ||
9142             StrStr(message, "accepted")) {
9143             return;
9144         }
9145         if (StrStr(message, "protover")) {
9146           /* Program is responding to input, so it's apparently done
9147              initializing, and this error message indicates it is
9148              protocol version 1.  So we don't need to wait any longer
9149              for it to initialize and send feature commands. */
9150           FeatureDone(cps, 1);
9151           cps->protocolVersion = 1;
9152           return;
9153         }
9154         cps->maybeThinking = FALSE;
9155
9156         if (StrStr(message, "draw")) {
9157             /* Program doesn't have "draw" command */
9158             cps->sendDrawOffers = 0;
9159             return;
9160         }
9161         if (cps->sendTime != 1 &&
9162             (StrStr(message, "time") || StrStr(message, "otim"))) {
9163           /* Program apparently doesn't have "time" or "otim" command */
9164           cps->sendTime = 0;
9165           return;
9166         }
9167         if (StrStr(message, "analyze")) {
9168             cps->analysisSupport = FALSE;
9169             cps->analyzing = FALSE;
9170 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9171             EditGameEvent(); // [HGM] try to preserve loaded game
9172             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9173             DisplayError(buf2, 0);
9174             return;
9175         }
9176         if (StrStr(message, "(no matching move)st")) {
9177           /* Special kludge for GNU Chess 4 only */
9178           cps->stKludge = TRUE;
9179           SendTimeControl(cps, movesPerSession, timeControl,
9180                           timeIncrement, appData.searchDepth,
9181                           searchTime);
9182           return;
9183         }
9184         if (StrStr(message, "(no matching move)sd")) {
9185           /* Special kludge for GNU Chess 4 only */
9186           cps->sdKludge = TRUE;
9187           SendTimeControl(cps, movesPerSession, timeControl,
9188                           timeIncrement, appData.searchDepth,
9189                           searchTime);
9190           return;
9191         }
9192         if (!StrStr(message, "llegal")) {
9193             return;
9194         }
9195         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9196             gameMode == IcsIdle) return;
9197         if (forwardMostMove <= backwardMostMove) return;
9198         if (pausing) PauseEvent();
9199       if(appData.forceIllegal) {
9200             // [HGM] illegal: machine refused move; force position after move into it
9201           SendToProgram("force\n", cps);
9202           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9203                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9204                 // when black is to move, while there might be nothing on a2 or black
9205                 // might already have the move. So send the board as if white has the move.
9206                 // But first we must change the stm of the engine, as it refused the last move
9207                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9208                 if(WhiteOnMove(forwardMostMove)) {
9209                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9210                     SendBoard(cps, forwardMostMove); // kludgeless board
9211                 } else {
9212                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9213                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9214                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9215                 }
9216           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9217             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9218                  gameMode == TwoMachinesPlay)
9219               SendToProgram("go\n", cps);
9220             return;
9221       } else
9222         if (gameMode == PlayFromGameFile) {
9223             /* Stop reading this game file */
9224             gameMode = EditGame;
9225             ModeHighlight();
9226         }
9227         /* [HGM] illegal-move claim should forfeit game when Xboard */
9228         /* only passes fully legal moves                            */
9229         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9230             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9231                                 "False illegal-move claim", GE_XBOARD );
9232             return; // do not take back move we tested as valid
9233         }
9234         currentMove = forwardMostMove-1;
9235         DisplayMove(currentMove-1); /* before DisplayMoveError */
9236         SwitchClocks(forwardMostMove-1); // [HGM] race
9237         DisplayBothClocks();
9238         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9239                 parseList[currentMove], _(cps->which));
9240         DisplayMoveError(buf1);
9241         DrawPosition(FALSE, boards[currentMove]);
9242
9243         SetUserThinkingEnables();
9244         return;
9245     }
9246     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9247         /* Program has a broken "time" command that
9248            outputs a string not ending in newline.
9249            Don't use it. */
9250         cps->sendTime = 0;
9251     }
9252     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9253         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9254             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9255     }
9256
9257     /*
9258      * If chess program startup fails, exit with an error message.
9259      * Attempts to recover here are futile. [HGM] Well, we try anyway
9260      */
9261     if ((StrStr(message, "unknown host") != NULL)
9262         || (StrStr(message, "No remote directory") != NULL)
9263         || (StrStr(message, "not found") != NULL)
9264         || (StrStr(message, "No such file") != NULL)
9265         || (StrStr(message, "can't alloc") != NULL)
9266         || (StrStr(message, "Permission denied") != NULL)) {
9267
9268         cps->maybeThinking = FALSE;
9269         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9270                 _(cps->which), cps->program, cps->host, message);
9271         RemoveInputSource(cps->isr);
9272         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9273             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9274             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9275         }
9276         return;
9277     }
9278
9279     /*
9280      * Look for hint output
9281      */
9282     if (sscanf(message, "Hint: %s", buf1) == 1) {
9283         if (cps == &first && hintRequested) {
9284             hintRequested = FALSE;
9285             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9286                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9287                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9288                                     PosFlags(forwardMostMove),
9289                                     fromY, fromX, toY, toX, promoChar, buf1);
9290                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9291                 DisplayInformation(buf2);
9292             } else {
9293                 /* Hint move could not be parsed!? */
9294               snprintf(buf2, sizeof(buf2),
9295                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9296                         buf1, _(cps->which));
9297                 DisplayError(buf2, 0);
9298             }
9299         } else {
9300           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9301         }
9302         return;
9303     }
9304
9305     /*
9306      * Ignore other messages if game is not in progress
9307      */
9308     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9309         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9310
9311     /*
9312      * look for win, lose, draw, or draw offer
9313      */
9314     if (strncmp(message, "1-0", 3) == 0) {
9315         char *p, *q, *r = "";
9316         p = strchr(message, '{');
9317         if (p) {
9318             q = strchr(p, '}');
9319             if (q) {
9320                 *q = NULLCHAR;
9321                 r = p + 1;
9322             }
9323         }
9324         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9325         return;
9326     } else if (strncmp(message, "0-1", 3) == 0) {
9327         char *p, *q, *r = "";
9328         p = strchr(message, '{');
9329         if (p) {
9330             q = strchr(p, '}');
9331             if (q) {
9332                 *q = NULLCHAR;
9333                 r = p + 1;
9334             }
9335         }
9336         /* Kludge for Arasan 4.1 bug */
9337         if (strcmp(r, "Black resigns") == 0) {
9338             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9339             return;
9340         }
9341         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9342         return;
9343     } else if (strncmp(message, "1/2", 3) == 0) {
9344         char *p, *q, *r = "";
9345         p = strchr(message, '{');
9346         if (p) {
9347             q = strchr(p, '}');
9348             if (q) {
9349                 *q = NULLCHAR;
9350                 r = p + 1;
9351             }
9352         }
9353
9354         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9355         return;
9356
9357     } else if (strncmp(message, "White resign", 12) == 0) {
9358         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9359         return;
9360     } else if (strncmp(message, "Black resign", 12) == 0) {
9361         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9362         return;
9363     } else if (strncmp(message, "White matches", 13) == 0 ||
9364                strncmp(message, "Black matches", 13) == 0   ) {
9365         /* [HGM] ignore GNUShogi noises */
9366         return;
9367     } else if (strncmp(message, "White", 5) == 0 &&
9368                message[5] != '(' &&
9369                StrStr(message, "Black") == NULL) {
9370         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9371         return;
9372     } else if (strncmp(message, "Black", 5) == 0 &&
9373                message[5] != '(') {
9374         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9375         return;
9376     } else if (strcmp(message, "resign") == 0 ||
9377                strcmp(message, "computer resigns") == 0) {
9378         switch (gameMode) {
9379           case MachinePlaysBlack:
9380           case IcsPlayingBlack:
9381             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9382             break;
9383           case MachinePlaysWhite:
9384           case IcsPlayingWhite:
9385             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9386             break;
9387           case TwoMachinesPlay:
9388             if (cps->twoMachinesColor[0] == 'w')
9389               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9390             else
9391               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9392             break;
9393           default:
9394             /* can't happen */
9395             break;
9396         }
9397         return;
9398     } else if (strncmp(message, "opponent mates", 14) == 0) {
9399         switch (gameMode) {
9400           case MachinePlaysBlack:
9401           case IcsPlayingBlack:
9402             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9403             break;
9404           case MachinePlaysWhite:
9405           case IcsPlayingWhite:
9406             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9407             break;
9408           case TwoMachinesPlay:
9409             if (cps->twoMachinesColor[0] == 'w')
9410               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9411             else
9412               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9413             break;
9414           default:
9415             /* can't happen */
9416             break;
9417         }
9418         return;
9419     } else if (strncmp(message, "computer mates", 14) == 0) {
9420         switch (gameMode) {
9421           case MachinePlaysBlack:
9422           case IcsPlayingBlack:
9423             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9424             break;
9425           case MachinePlaysWhite:
9426           case IcsPlayingWhite:
9427             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9428             break;
9429           case TwoMachinesPlay:
9430             if (cps->twoMachinesColor[0] == 'w')
9431               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9432             else
9433               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9434             break;
9435           default:
9436             /* can't happen */
9437             break;
9438         }
9439         return;
9440     } else if (strncmp(message, "checkmate", 9) == 0) {
9441         if (WhiteOnMove(forwardMostMove)) {
9442             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9443         } else {
9444             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9445         }
9446         return;
9447     } else if (strstr(message, "Draw") != NULL ||
9448                strstr(message, "game is a draw") != NULL) {
9449         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9450         return;
9451     } else if (strstr(message, "offer") != NULL &&
9452                strstr(message, "draw") != NULL) {
9453 #if ZIPPY
9454         if (appData.zippyPlay && first.initDone) {
9455             /* Relay offer to ICS */
9456             SendToICS(ics_prefix);
9457             SendToICS("draw\n");
9458         }
9459 #endif
9460         cps->offeredDraw = 2; /* valid until this engine moves twice */
9461         if (gameMode == TwoMachinesPlay) {
9462             if (cps->other->offeredDraw) {
9463                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9464             /* [HGM] in two-machine mode we delay relaying draw offer      */
9465             /* until after we also have move, to see if it is really claim */
9466             }
9467         } else if (gameMode == MachinePlaysWhite ||
9468                    gameMode == MachinePlaysBlack) {
9469           if (userOfferedDraw) {
9470             DisplayInformation(_("Machine accepts your draw offer"));
9471             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9472           } else {
9473             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9474           }
9475         }
9476     }
9477
9478
9479     /*
9480      * Look for thinking output
9481      */
9482     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9483           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9484                                 ) {
9485         int plylev, mvleft, mvtot, curscore, time;
9486         char mvname[MOVE_LEN];
9487         u64 nodes; // [DM]
9488         char plyext;
9489         int ignore = FALSE;
9490         int prefixHint = FALSE;
9491         mvname[0] = NULLCHAR;
9492
9493         switch (gameMode) {
9494           case MachinePlaysBlack:
9495           case IcsPlayingBlack:
9496             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9497             break;
9498           case MachinePlaysWhite:
9499           case IcsPlayingWhite:
9500             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9501             break;
9502           case AnalyzeMode:
9503           case AnalyzeFile:
9504             break;
9505           case IcsObserving: /* [DM] icsEngineAnalyze */
9506             if (!appData.icsEngineAnalyze) ignore = TRUE;
9507             break;
9508           case TwoMachinesPlay:
9509             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9510                 ignore = TRUE;
9511             }
9512             break;
9513           default:
9514             ignore = TRUE;
9515             break;
9516         }
9517
9518         if (!ignore) {
9519             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9520             buf1[0] = NULLCHAR;
9521             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9522                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9523
9524                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9525                     nodes += u64Const(0x100000000);
9526
9527                 if (plyext != ' ' && plyext != '\t') {
9528                     time *= 100;
9529                 }
9530
9531                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9532                 if( cps->scoreIsAbsolute &&
9533                     ( gameMode == MachinePlaysBlack ||
9534                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9535                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9536                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9537                      !WhiteOnMove(currentMove)
9538                     ) )
9539                 {
9540                     curscore = -curscore;
9541                 }
9542
9543                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9544
9545                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9546                         char buf[MSG_SIZ];
9547                         FILE *f;
9548                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9549                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9550                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9551                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9552                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9553                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9554                                 fclose(f);
9555                         }
9556                         else
9557                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9558                           DisplayError(_("failed writing PV"), 0);
9559                 }
9560
9561                 tempStats.depth = plylev;
9562                 tempStats.nodes = nodes;
9563                 tempStats.time = time;
9564                 tempStats.score = curscore;
9565                 tempStats.got_only_move = 0;
9566
9567                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9568                         int ticklen;
9569
9570                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9571                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9572                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9573                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9574                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9575                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9576                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9577                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9578                 }
9579
9580                 /* Buffer overflow protection */
9581                 if (pv[0] != NULLCHAR) {
9582                     if (strlen(pv) >= sizeof(tempStats.movelist)
9583                         && appData.debugMode) {
9584                         fprintf(debugFP,
9585                                 "PV is too long; using the first %u bytes.\n",
9586                                 (unsigned) sizeof(tempStats.movelist) - 1);
9587                     }
9588
9589                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9590                 } else {
9591                     sprintf(tempStats.movelist, " no PV\n");
9592                 }
9593
9594                 if (tempStats.seen_stat) {
9595                     tempStats.ok_to_send = 1;
9596                 }
9597
9598                 if (strchr(tempStats.movelist, '(') != NULL) {
9599                     tempStats.line_is_book = 1;
9600                     tempStats.nr_moves = 0;
9601                     tempStats.moves_left = 0;
9602                 } else {
9603                     tempStats.line_is_book = 0;
9604                 }
9605
9606                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9607                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9608
9609                 SendProgramStatsToFrontend( cps, &tempStats );
9610
9611                 /*
9612                     [AS] Protect the thinkOutput buffer from overflow... this
9613                     is only useful if buf1 hasn't overflowed first!
9614                 */
9615                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9616                          plylev,
9617                          (gameMode == TwoMachinesPlay ?
9618                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9619                          ((double) curscore) / 100.0,
9620                          prefixHint ? lastHint : "",
9621                          prefixHint ? " " : "" );
9622
9623                 if( buf1[0] != NULLCHAR ) {
9624                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9625
9626                     if( strlen(pv) > max_len ) {
9627                         if( appData.debugMode) {
9628                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9629                         }
9630                         pv[max_len+1] = '\0';
9631                     }
9632
9633                     strcat( thinkOutput, pv);
9634                 }
9635
9636                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9637                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9638                     DisplayMove(currentMove - 1);
9639                 }
9640                 return;
9641
9642             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9643                 /* crafty (9.25+) says "(only move) <move>"
9644                  * if there is only 1 legal move
9645                  */
9646                 sscanf(p, "(only move) %s", buf1);
9647                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9648                 sprintf(programStats.movelist, "%s (only move)", buf1);
9649                 programStats.depth = 1;
9650                 programStats.nr_moves = 1;
9651                 programStats.moves_left = 1;
9652                 programStats.nodes = 1;
9653                 programStats.time = 1;
9654                 programStats.got_only_move = 1;
9655
9656                 /* Not really, but we also use this member to
9657                    mean "line isn't going to change" (Crafty
9658                    isn't searching, so stats won't change) */
9659                 programStats.line_is_book = 1;
9660
9661                 SendProgramStatsToFrontend( cps, &programStats );
9662
9663                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9664                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9665                     DisplayMove(currentMove - 1);
9666                 }
9667                 return;
9668             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9669                               &time, &nodes, &plylev, &mvleft,
9670                               &mvtot, mvname) >= 5) {
9671                 /* The stat01: line is from Crafty (9.29+) in response
9672                    to the "." command */
9673                 programStats.seen_stat = 1;
9674                 cps->maybeThinking = TRUE;
9675
9676                 if (programStats.got_only_move || !appData.periodicUpdates)
9677                   return;
9678
9679                 programStats.depth = plylev;
9680                 programStats.time = time;
9681                 programStats.nodes = nodes;
9682                 programStats.moves_left = mvleft;
9683                 programStats.nr_moves = mvtot;
9684                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9685                 programStats.ok_to_send = 1;
9686                 programStats.movelist[0] = '\0';
9687
9688                 SendProgramStatsToFrontend( cps, &programStats );
9689
9690                 return;
9691
9692             } else if (strncmp(message,"++",2) == 0) {
9693                 /* Crafty 9.29+ outputs this */
9694                 programStats.got_fail = 2;
9695                 return;
9696
9697             } else if (strncmp(message,"--",2) == 0) {
9698                 /* Crafty 9.29+ outputs this */
9699                 programStats.got_fail = 1;
9700                 return;
9701
9702             } else if (thinkOutput[0] != NULLCHAR &&
9703                        strncmp(message, "    ", 4) == 0) {
9704                 unsigned message_len;
9705
9706                 p = message;
9707                 while (*p && *p == ' ') p++;
9708
9709                 message_len = strlen( p );
9710
9711                 /* [AS] Avoid buffer overflow */
9712                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9713                     strcat(thinkOutput, " ");
9714                     strcat(thinkOutput, p);
9715                 }
9716
9717                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9718                     strcat(programStats.movelist, " ");
9719                     strcat(programStats.movelist, p);
9720                 }
9721
9722                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9723                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9724                     DisplayMove(currentMove - 1);
9725                 }
9726                 return;
9727             }
9728         }
9729         else {
9730             buf1[0] = NULLCHAR;
9731
9732             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9733                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9734             {
9735                 ChessProgramStats cpstats;
9736
9737                 if (plyext != ' ' && plyext != '\t') {
9738                     time *= 100;
9739                 }
9740
9741                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9742                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9743                     curscore = -curscore;
9744                 }
9745
9746                 cpstats.depth = plylev;
9747                 cpstats.nodes = nodes;
9748                 cpstats.time = time;
9749                 cpstats.score = curscore;
9750                 cpstats.got_only_move = 0;
9751                 cpstats.movelist[0] = '\0';
9752
9753                 if (buf1[0] != NULLCHAR) {
9754                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9755                 }
9756
9757                 cpstats.ok_to_send = 0;
9758                 cpstats.line_is_book = 0;
9759                 cpstats.nr_moves = 0;
9760                 cpstats.moves_left = 0;
9761
9762                 SendProgramStatsToFrontend( cps, &cpstats );
9763             }
9764         }
9765     }
9766 }
9767
9768
9769 /* Parse a game score from the character string "game", and
9770    record it as the history of the current game.  The game
9771    score is NOT assumed to start from the standard position.
9772    The display is not updated in any way.
9773    */
9774 void
9775 ParseGameHistory (char *game)
9776 {
9777     ChessMove moveType;
9778     int fromX, fromY, toX, toY, boardIndex;
9779     char promoChar;
9780     char *p, *q;
9781     char buf[MSG_SIZ];
9782
9783     if (appData.debugMode)
9784       fprintf(debugFP, "Parsing game history: %s\n", game);
9785
9786     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9787     gameInfo.site = StrSave(appData.icsHost);
9788     gameInfo.date = PGNDate();
9789     gameInfo.round = StrSave("-");
9790
9791     /* Parse out names of players */
9792     while (*game == ' ') game++;
9793     p = buf;
9794     while (*game != ' ') *p++ = *game++;
9795     *p = NULLCHAR;
9796     gameInfo.white = StrSave(buf);
9797     while (*game == ' ') game++;
9798     p = buf;
9799     while (*game != ' ' && *game != '\n') *p++ = *game++;
9800     *p = NULLCHAR;
9801     gameInfo.black = StrSave(buf);
9802
9803     /* Parse moves */
9804     boardIndex = blackPlaysFirst ? 1 : 0;
9805     yynewstr(game);
9806     for (;;) {
9807         yyboardindex = boardIndex;
9808         moveType = (ChessMove) Myylex();
9809         switch (moveType) {
9810           case IllegalMove:             /* maybe suicide chess, etc. */
9811   if (appData.debugMode) {
9812     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9813     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9814     setbuf(debugFP, NULL);
9815   }
9816           case WhitePromotion:
9817           case BlackPromotion:
9818           case WhiteNonPromotion:
9819           case BlackNonPromotion:
9820           case NormalMove:
9821           case FirstLeg:
9822           case WhiteCapturesEnPassant:
9823           case BlackCapturesEnPassant:
9824           case WhiteKingSideCastle:
9825           case WhiteQueenSideCastle:
9826           case BlackKingSideCastle:
9827           case BlackQueenSideCastle:
9828           case WhiteKingSideCastleWild:
9829           case WhiteQueenSideCastleWild:
9830           case BlackKingSideCastleWild:
9831           case BlackQueenSideCastleWild:
9832           /* PUSH Fabien */
9833           case WhiteHSideCastleFR:
9834           case WhiteASideCastleFR:
9835           case BlackHSideCastleFR:
9836           case BlackASideCastleFR:
9837           /* POP Fabien */
9838             fromX = currentMoveString[0] - AAA;
9839             fromY = currentMoveString[1] - ONE;
9840             toX = currentMoveString[2] - AAA;
9841             toY = currentMoveString[3] - ONE;
9842             promoChar = currentMoveString[4];
9843             break;
9844           case WhiteDrop:
9845           case BlackDrop:
9846             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9847             fromX = moveType == WhiteDrop ?
9848               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9849             (int) CharToPiece(ToLower(currentMoveString[0]));
9850             fromY = DROP_RANK;
9851             toX = currentMoveString[2] - AAA;
9852             toY = currentMoveString[3] - ONE;
9853             promoChar = NULLCHAR;
9854             break;
9855           case AmbiguousMove:
9856             /* bug? */
9857             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9858   if (appData.debugMode) {
9859     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9860     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9861     setbuf(debugFP, NULL);
9862   }
9863             DisplayError(buf, 0);
9864             return;
9865           case ImpossibleMove:
9866             /* bug? */
9867             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9868   if (appData.debugMode) {
9869     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9870     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9871     setbuf(debugFP, NULL);
9872   }
9873             DisplayError(buf, 0);
9874             return;
9875           case EndOfFile:
9876             if (boardIndex < backwardMostMove) {
9877                 /* Oops, gap.  How did that happen? */
9878                 DisplayError(_("Gap in move list"), 0);
9879                 return;
9880             }
9881             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9882             if (boardIndex > forwardMostMove) {
9883                 forwardMostMove = boardIndex;
9884             }
9885             return;
9886           case ElapsedTime:
9887             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9888                 strcat(parseList[boardIndex-1], " ");
9889                 strcat(parseList[boardIndex-1], yy_text);
9890             }
9891             continue;
9892           case Comment:
9893           case PGNTag:
9894           case NAG:
9895           default:
9896             /* ignore */
9897             continue;
9898           case WhiteWins:
9899           case BlackWins:
9900           case GameIsDrawn:
9901           case GameUnfinished:
9902             if (gameMode == IcsExamining) {
9903                 if (boardIndex < backwardMostMove) {
9904                     /* Oops, gap.  How did that happen? */
9905                     return;
9906                 }
9907                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9908                 return;
9909             }
9910             gameInfo.result = moveType;
9911             p = strchr(yy_text, '{');
9912             if (p == NULL) p = strchr(yy_text, '(');
9913             if (p == NULL) {
9914                 p = yy_text;
9915                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9916             } else {
9917                 q = strchr(p, *p == '{' ? '}' : ')');
9918                 if (q != NULL) *q = NULLCHAR;
9919                 p++;
9920             }
9921             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9922             gameInfo.resultDetails = StrSave(p);
9923             continue;
9924         }
9925         if (boardIndex >= forwardMostMove &&
9926             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9927             backwardMostMove = blackPlaysFirst ? 1 : 0;
9928             return;
9929         }
9930         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9931                                  fromY, fromX, toY, toX, promoChar,
9932                                  parseList[boardIndex]);
9933         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9934         /* currentMoveString is set as a side-effect of yylex */
9935         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9936         strcat(moveList[boardIndex], "\n");
9937         boardIndex++;
9938         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9939         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9940           case MT_NONE:
9941           case MT_STALEMATE:
9942           default:
9943             break;
9944           case MT_CHECK:
9945             if(!IS_SHOGI(gameInfo.variant))
9946                 strcat(parseList[boardIndex - 1], "+");
9947             break;
9948           case MT_CHECKMATE:
9949           case MT_STAINMATE:
9950             strcat(parseList[boardIndex - 1], "#");
9951             break;
9952         }
9953     }
9954 }
9955
9956
9957 /* Apply a move to the given board  */
9958 void
9959 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9960 {
9961   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
9962   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9963
9964     /* [HGM] compute & store e.p. status and castling rights for new position */
9965     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9966
9967       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9968       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9969       board[EP_STATUS] = EP_NONE;
9970       board[EP_FILE] = board[EP_RANK] = 100;
9971
9972   if (fromY == DROP_RANK) {
9973         /* must be first */
9974         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9975             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9976             return;
9977         }
9978         piece = board[toY][toX] = (ChessSquare) fromX;
9979   } else {
9980 //      ChessSquare victim;
9981       int i;
9982
9983       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
9984 //           victim = board[killY][killX],
9985            killed = board[killY][killX],
9986            board[killY][killX] = EmptySquare,
9987            board[EP_STATUS] = EP_CAPTURE;
9988            if( kill2X >= 0 && kill2Y >= 0)
9989              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
9990       }
9991
9992       if( board[toY][toX] != EmptySquare ) {
9993            board[EP_STATUS] = EP_CAPTURE;
9994            if( (fromX != toX || fromY != toY) && // not igui!
9995                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9996                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9997                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9998            }
9999       }
10000
10001       pawn = board[fromY][fromX];
10002       if( pawn == WhiteLance || pawn == BlackLance ) {
10003            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10004                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10005                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10006            }
10007       }
10008       if( pawn == WhitePawn ) {
10009            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10010                board[EP_STATUS] = EP_PAWN_MOVE;
10011            if( toY-fromY>=2) {
10012                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10013                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10014                         gameInfo.variant != VariantBerolina || toX < fromX)
10015                       board[EP_STATUS] = toX | berolina;
10016                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10017                         gameInfo.variant != VariantBerolina || toX > fromX)
10018                       board[EP_STATUS] = toX;
10019            }
10020       } else
10021       if( pawn == BlackPawn ) {
10022            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10023                board[EP_STATUS] = EP_PAWN_MOVE;
10024            if( toY-fromY<= -2) {
10025                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10026                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10027                         gameInfo.variant != VariantBerolina || toX < fromX)
10028                       board[EP_STATUS] = toX | berolina;
10029                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10030                         gameInfo.variant != VariantBerolina || toX > fromX)
10031                       board[EP_STATUS] = toX;
10032            }
10033        }
10034
10035        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10036        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10037        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10038        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10039
10040        for(i=0; i<nrCastlingRights; i++) {
10041            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10042               board[CASTLING][i] == toX   && castlingRank[i] == toY
10043              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10044        }
10045
10046        if(gameInfo.variant == VariantSChess) { // update virginity
10047            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10048            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10049            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10050            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10051        }
10052
10053      if (fromX == toX && fromY == toY) return;
10054
10055      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10056      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10057      if(gameInfo.variant == VariantKnightmate)
10058          king += (int) WhiteUnicorn - (int) WhiteKing;
10059
10060     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10061        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10062         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10063         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10064         board[EP_STATUS] = EP_NONE; // capture was fake!
10065     } else
10066     /* Code added by Tord: */
10067     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10068     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10069         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10070       board[EP_STATUS] = EP_NONE; // capture was fake!
10071       board[fromY][fromX] = EmptySquare;
10072       board[toY][toX] = EmptySquare;
10073       if((toX > fromX) != (piece == WhiteRook)) {
10074         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10075       } else {
10076         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10077       }
10078     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10079                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10080       board[EP_STATUS] = EP_NONE;
10081       board[fromY][fromX] = EmptySquare;
10082       board[toY][toX] = EmptySquare;
10083       if((toX > fromX) != (piece == BlackRook)) {
10084         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10085       } else {
10086         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10087       }
10088     /* End of code added by Tord */
10089
10090     } else if (board[fromY][fromX] == king
10091         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10092         && toY == fromY && toX > fromX+1) {
10093         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10094         board[fromY][toX-1] = board[fromY][rookX];
10095         board[fromY][rookX] = EmptySquare;
10096         board[fromY][fromX] = EmptySquare;
10097         board[toY][toX] = king;
10098     } else if (board[fromY][fromX] == king
10099         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10100                && toY == fromY && toX < fromX-1) {
10101         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10102         board[fromY][toX+1] = board[fromY][rookX];
10103         board[fromY][rookX] = EmptySquare;
10104         board[fromY][fromX] = EmptySquare;
10105         board[toY][toX] = king;
10106     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10107                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10108                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10109                ) {
10110         /* white pawn promotion */
10111         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10112         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10113             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10114         board[fromY][fromX] = EmptySquare;
10115     } else if ((fromY >= BOARD_HEIGHT>>1)
10116                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10117                && (toX != fromX)
10118                && gameInfo.variant != VariantXiangqi
10119                && gameInfo.variant != VariantBerolina
10120                && (pawn == WhitePawn)
10121                && (board[toY][toX] == EmptySquare)) {
10122         board[fromY][fromX] = EmptySquare;
10123         board[toY][toX] = piece;
10124         if(toY == epRank - 128 + 1)
10125             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10126         else
10127             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10128     } else if ((fromY == BOARD_HEIGHT-4)
10129                && (toX == fromX)
10130                && gameInfo.variant == VariantBerolina
10131                && (board[fromY][fromX] == WhitePawn)
10132                && (board[toY][toX] == EmptySquare)) {
10133         board[fromY][fromX] = EmptySquare;
10134         board[toY][toX] = WhitePawn;
10135         if(oldEP & EP_BEROLIN_A) {
10136                 captured = board[fromY][fromX-1];
10137                 board[fromY][fromX-1] = EmptySquare;
10138         }else{  captured = board[fromY][fromX+1];
10139                 board[fromY][fromX+1] = EmptySquare;
10140         }
10141     } else if (board[fromY][fromX] == king
10142         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10143                && toY == fromY && toX > fromX+1) {
10144         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10145         board[fromY][toX-1] = board[fromY][rookX];
10146         board[fromY][rookX] = EmptySquare;
10147         board[fromY][fromX] = EmptySquare;
10148         board[toY][toX] = king;
10149     } else if (board[fromY][fromX] == king
10150         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10151                && toY == fromY && toX < fromX-1) {
10152         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10153         board[fromY][toX+1] = board[fromY][rookX];
10154         board[fromY][rookX] = EmptySquare;
10155         board[fromY][fromX] = EmptySquare;
10156         board[toY][toX] = king;
10157     } else if (fromY == 7 && fromX == 3
10158                && board[fromY][fromX] == BlackKing
10159                && toY == 7 && toX == 5) {
10160         board[fromY][fromX] = EmptySquare;
10161         board[toY][toX] = BlackKing;
10162         board[fromY][7] = EmptySquare;
10163         board[toY][4] = BlackRook;
10164     } else if (fromY == 7 && fromX == 3
10165                && board[fromY][fromX] == BlackKing
10166                && toY == 7 && toX == 1) {
10167         board[fromY][fromX] = EmptySquare;
10168         board[toY][toX] = BlackKing;
10169         board[fromY][0] = EmptySquare;
10170         board[toY][2] = BlackRook;
10171     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10172                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10173                && toY < promoRank && promoChar
10174                ) {
10175         /* black pawn promotion */
10176         board[toY][toX] = CharToPiece(ToLower(promoChar));
10177         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10178             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10179         board[fromY][fromX] = EmptySquare;
10180     } else if ((fromY < BOARD_HEIGHT>>1)
10181                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10182                && (toX != fromX)
10183                && gameInfo.variant != VariantXiangqi
10184                && gameInfo.variant != VariantBerolina
10185                && (pawn == BlackPawn)
10186                && (board[toY][toX] == EmptySquare)) {
10187         board[fromY][fromX] = EmptySquare;
10188         board[toY][toX] = piece;
10189         if(toY == epRank - 128 - 1)
10190             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10191         else
10192             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10193     } else if ((fromY == 3)
10194                && (toX == fromX)
10195                && gameInfo.variant == VariantBerolina
10196                && (board[fromY][fromX] == BlackPawn)
10197                && (board[toY][toX] == EmptySquare)) {
10198         board[fromY][fromX] = EmptySquare;
10199         board[toY][toX] = BlackPawn;
10200         if(oldEP & EP_BEROLIN_A) {
10201                 captured = board[fromY][fromX-1];
10202                 board[fromY][fromX-1] = EmptySquare;
10203         }else{  captured = board[fromY][fromX+1];
10204                 board[fromY][fromX+1] = EmptySquare;
10205         }
10206     } else {
10207         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10208         board[fromY][fromX] = EmptySquare;
10209         board[toY][toX] = piece;
10210     }
10211   }
10212
10213     if (gameInfo.holdingsWidth != 0) {
10214
10215       /* !!A lot more code needs to be written to support holdings  */
10216       /* [HGM] OK, so I have written it. Holdings are stored in the */
10217       /* penultimate board files, so they are automaticlly stored   */
10218       /* in the game history.                                       */
10219       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10220                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10221         /* Delete from holdings, by decreasing count */
10222         /* and erasing image if necessary            */
10223         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10224         if(p < (int) BlackPawn) { /* white drop */
10225              p -= (int)WhitePawn;
10226                  p = PieceToNumber((ChessSquare)p);
10227              if(p >= gameInfo.holdingsSize) p = 0;
10228              if(--board[p][BOARD_WIDTH-2] <= 0)
10229                   board[p][BOARD_WIDTH-1] = EmptySquare;
10230              if((int)board[p][BOARD_WIDTH-2] < 0)
10231                         board[p][BOARD_WIDTH-2] = 0;
10232         } else {                  /* black drop */
10233              p -= (int)BlackPawn;
10234                  p = PieceToNumber((ChessSquare)p);
10235              if(p >= gameInfo.holdingsSize) p = 0;
10236              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10237                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10238              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10239                         board[BOARD_HEIGHT-1-p][1] = 0;
10240         }
10241       }
10242       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10243           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10244         /* [HGM] holdings: Add to holdings, if holdings exist */
10245         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10246                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10247                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10248         }
10249         p = (int) captured;
10250         if (p >= (int) BlackPawn) {
10251           p -= (int)BlackPawn;
10252           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10253                   /* Restore shogi-promoted piece to its original  first */
10254                   captured = (ChessSquare) (DEMOTED captured);
10255                   p = DEMOTED p;
10256           }
10257           p = PieceToNumber((ChessSquare)p);
10258           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10259           board[p][BOARD_WIDTH-2]++;
10260           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10261         } else {
10262           p -= (int)WhitePawn;
10263           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10264                   captured = (ChessSquare) (DEMOTED captured);
10265                   p = DEMOTED p;
10266           }
10267           p = PieceToNumber((ChessSquare)p);
10268           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10269           board[BOARD_HEIGHT-1-p][1]++;
10270           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10271         }
10272       }
10273     } else if (gameInfo.variant == VariantAtomic) {
10274       if (captured != EmptySquare) {
10275         int y, x;
10276         for (y = toY-1; y <= toY+1; y++) {
10277           for (x = toX-1; x <= toX+1; x++) {
10278             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10279                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10280               board[y][x] = EmptySquare;
10281             }
10282           }
10283         }
10284         board[toY][toX] = EmptySquare;
10285       }
10286     }
10287
10288     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10289         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10290     } else
10291     if(promoChar == '+') {
10292         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10293         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10294         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10295           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10296     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10297         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10298         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10299            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10300         board[toY][toX] = newPiece;
10301     }
10302     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10303                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10304         // [HGM] superchess: take promotion piece out of holdings
10305         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10306         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10307             if(!--board[k][BOARD_WIDTH-2])
10308                 board[k][BOARD_WIDTH-1] = EmptySquare;
10309         } else {
10310             if(!--board[BOARD_HEIGHT-1-k][1])
10311                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10312         }
10313     }
10314 }
10315
10316 /* Updates forwardMostMove */
10317 void
10318 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10319 {
10320     int x = toX, y = toY;
10321     char *s = parseList[forwardMostMove];
10322     ChessSquare p = boards[forwardMostMove][toY][toX];
10323 //    forwardMostMove++; // [HGM] bare: moved downstream
10324
10325     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10326     (void) CoordsToAlgebraic(boards[forwardMostMove],
10327                              PosFlags(forwardMostMove),
10328                              fromY, fromX, y, x, promoChar,
10329                              s);
10330     if(killX >= 0 && killY >= 0)
10331         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10332
10333     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10334         int timeLeft; static int lastLoadFlag=0; int king, piece;
10335         piece = boards[forwardMostMove][fromY][fromX];
10336         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10337         if(gameInfo.variant == VariantKnightmate)
10338             king += (int) WhiteUnicorn - (int) WhiteKing;
10339         if(forwardMostMove == 0) {
10340             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10341                 fprintf(serverMoves, "%s;", UserName());
10342             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10343                 fprintf(serverMoves, "%s;", second.tidy);
10344             fprintf(serverMoves, "%s;", first.tidy);
10345             if(gameMode == MachinePlaysWhite)
10346                 fprintf(serverMoves, "%s;", UserName());
10347             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10348                 fprintf(serverMoves, "%s;", second.tidy);
10349         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10350         lastLoadFlag = loadFlag;
10351         // print base move
10352         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10353         // print castling suffix
10354         if( toY == fromY && piece == king ) {
10355             if(toX-fromX > 1)
10356                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10357             if(fromX-toX >1)
10358                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10359         }
10360         // e.p. suffix
10361         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10362              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10363              boards[forwardMostMove][toY][toX] == EmptySquare
10364              && fromX != toX && fromY != toY)
10365                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10366         // promotion suffix
10367         if(promoChar != NULLCHAR) {
10368             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10369                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10370                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10371             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10372         }
10373         if(!loadFlag) {
10374                 char buf[MOVE_LEN*2], *p; int len;
10375             fprintf(serverMoves, "/%d/%d",
10376                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10377             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10378             else                      timeLeft = blackTimeRemaining/1000;
10379             fprintf(serverMoves, "/%d", timeLeft);
10380                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10381                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10382                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10383                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10384             fprintf(serverMoves, "/%s", buf);
10385         }
10386         fflush(serverMoves);
10387     }
10388
10389     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10390         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10391       return;
10392     }
10393     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10394     if (commentList[forwardMostMove+1] != NULL) {
10395         free(commentList[forwardMostMove+1]);
10396         commentList[forwardMostMove+1] = NULL;
10397     }
10398     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10399     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10400     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10401     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10402     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10403     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10404     adjustedClock = FALSE;
10405     gameInfo.result = GameUnfinished;
10406     if (gameInfo.resultDetails != NULL) {
10407         free(gameInfo.resultDetails);
10408         gameInfo.resultDetails = NULL;
10409     }
10410     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10411                               moveList[forwardMostMove - 1]);
10412     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10413       case MT_NONE:
10414       case MT_STALEMATE:
10415       default:
10416         break;
10417       case MT_CHECK:
10418         if(!IS_SHOGI(gameInfo.variant))
10419             strcat(parseList[forwardMostMove - 1], "+");
10420         break;
10421       case MT_CHECKMATE:
10422       case MT_STAINMATE:
10423         strcat(parseList[forwardMostMove - 1], "#");
10424         break;
10425     }
10426 }
10427
10428 /* Updates currentMove if not pausing */
10429 void
10430 ShowMove (int fromX, int fromY, int toX, int toY)
10431 {
10432     int instant = (gameMode == PlayFromGameFile) ?
10433         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10434     if(appData.noGUI) return;
10435     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10436         if (!instant) {
10437             if (forwardMostMove == currentMove + 1) {
10438                 AnimateMove(boards[forwardMostMove - 1],
10439                             fromX, fromY, toX, toY);
10440             }
10441         }
10442         currentMove = forwardMostMove;
10443     }
10444
10445     killX = killY = -1; // [HGM] lion: used up
10446
10447     if (instant) return;
10448
10449     DisplayMove(currentMove - 1);
10450     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10451             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10452                 SetHighlights(fromX, fromY, toX, toY);
10453             }
10454     }
10455     DrawPosition(FALSE, boards[currentMove]);
10456     DisplayBothClocks();
10457     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10458 }
10459
10460 void
10461 SendEgtPath (ChessProgramState *cps)
10462 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10463         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10464
10465         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10466
10467         while(*p) {
10468             char c, *q = name+1, *r, *s;
10469
10470             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10471             while(*p && *p != ',') *q++ = *p++;
10472             *q++ = ':'; *q = 0;
10473             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10474                 strcmp(name, ",nalimov:") == 0 ) {
10475                 // take nalimov path from the menu-changeable option first, if it is defined
10476               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10477                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10478             } else
10479             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10480                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10481                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10482                 s = r = StrStr(s, ":") + 1; // beginning of path info
10483                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10484                 c = *r; *r = 0;             // temporarily null-terminate path info
10485                     *--q = 0;               // strip of trailig ':' from name
10486                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10487                 *r = c;
10488                 SendToProgram(buf,cps);     // send egtbpath command for this format
10489             }
10490             if(*p == ',') p++; // read away comma to position for next format name
10491         }
10492 }
10493
10494 static int
10495 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10496 {
10497       int width = 8, height = 8, holdings = 0;             // most common sizes
10498       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10499       // correct the deviations default for each variant
10500       if( v == VariantXiangqi ) width = 9,  height = 10;
10501       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10502       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10503       if( v == VariantCapablanca || v == VariantCapaRandom ||
10504           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10505                                 width = 10;
10506       if( v == VariantCourier ) width = 12;
10507       if( v == VariantSuper )                            holdings = 8;
10508       if( v == VariantGreat )   width = 10,              holdings = 8;
10509       if( v == VariantSChess )                           holdings = 7;
10510       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10511       if( v == VariantChuChess) width = 10, height = 10;
10512       if( v == VariantChu )     width = 12, height = 12;
10513       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10514              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10515              holdingsSize >= 0 && holdingsSize != holdings;
10516 }
10517
10518 char variantError[MSG_SIZ];
10519
10520 char *
10521 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10522 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10523       char *p, *variant = VariantName(v);
10524       static char b[MSG_SIZ];
10525       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10526            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10527                                                holdingsSize, variant); // cook up sized variant name
10528            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10529            if(StrStr(list, b) == NULL) {
10530                // specific sized variant not known, check if general sizing allowed
10531                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10532                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10533                             boardWidth, boardHeight, holdingsSize, engine);
10534                    return NULL;
10535                }
10536                /* [HGM] here we really should compare with the maximum supported board size */
10537            }
10538       } else snprintf(b, MSG_SIZ,"%s", variant);
10539       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10540       p = StrStr(list, b);
10541       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10542       if(p == NULL) {
10543           // occurs not at all in list, or only as sub-string
10544           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10545           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10546               int l = strlen(variantError);
10547               char *q;
10548               while(p != list && p[-1] != ',') p--;
10549               q = strchr(p, ',');
10550               if(q) *q = NULLCHAR;
10551               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10552               if(q) *q= ',';
10553           }
10554           return NULL;
10555       }
10556       return b;
10557 }
10558
10559 void
10560 InitChessProgram (ChessProgramState *cps, int setup)
10561 /* setup needed to setup FRC opening position */
10562 {
10563     char buf[MSG_SIZ], *b;
10564     if (appData.noChessProgram) return;
10565     hintRequested = FALSE;
10566     bookRequested = FALSE;
10567
10568     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10569     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10570     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10571     if(cps->memSize) { /* [HGM] memory */
10572       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10573         SendToProgram(buf, cps);
10574     }
10575     SendEgtPath(cps); /* [HGM] EGT */
10576     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10577       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10578         SendToProgram(buf, cps);
10579     }
10580
10581     setboardSpoiledMachineBlack = FALSE;
10582     SendToProgram(cps->initString, cps);
10583     if (gameInfo.variant != VariantNormal &&
10584         gameInfo.variant != VariantLoadable
10585         /* [HGM] also send variant if board size non-standard */
10586         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10587
10588       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10589                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10590       if (b == NULL) {
10591         VariantClass v;
10592         char c, *q = cps->variants, *p = strchr(q, ',');
10593         if(p) *p = NULLCHAR;
10594         v = StringToVariant(q);
10595         DisplayError(variantError, 0);
10596         if(v != VariantUnknown && cps == &first) {
10597             int w, h, s;
10598             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10599                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10600             ASSIGN(appData.variant, q);
10601             Reset(TRUE, FALSE);
10602         }
10603         if(p) *p = ',';
10604         return;
10605       }
10606
10607       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10608       SendToProgram(buf, cps);
10609     }
10610     currentlyInitializedVariant = gameInfo.variant;
10611
10612     /* [HGM] send opening position in FRC to first engine */
10613     if(setup) {
10614           SendToProgram("force\n", cps);
10615           SendBoard(cps, 0);
10616           /* engine is now in force mode! Set flag to wake it up after first move. */
10617           setboardSpoiledMachineBlack = 1;
10618     }
10619
10620     if (cps->sendICS) {
10621       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10622       SendToProgram(buf, cps);
10623     }
10624     cps->maybeThinking = FALSE;
10625     cps->offeredDraw = 0;
10626     if (!appData.icsActive) {
10627         SendTimeControl(cps, movesPerSession, timeControl,
10628                         timeIncrement, appData.searchDepth,
10629                         searchTime);
10630     }
10631     if (appData.showThinking
10632         // [HGM] thinking: four options require thinking output to be sent
10633         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10634                                 ) {
10635         SendToProgram("post\n", cps);
10636     }
10637     SendToProgram("hard\n", cps);
10638     if (!appData.ponderNextMove) {
10639         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10640            it without being sure what state we are in first.  "hard"
10641            is not a toggle, so that one is OK.
10642          */
10643         SendToProgram("easy\n", cps);
10644     }
10645     if (cps->usePing) {
10646       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10647       SendToProgram(buf, cps);
10648     }
10649     cps->initDone = TRUE;
10650     ClearEngineOutputPane(cps == &second);
10651 }
10652
10653
10654 void
10655 ResendOptions (ChessProgramState *cps)
10656 { // send the stored value of the options
10657   int i;
10658   char buf[MSG_SIZ];
10659   Option *opt = cps->option;
10660   for(i=0; i<cps->nrOptions; i++, opt++) {
10661       switch(opt->type) {
10662         case Spin:
10663         case Slider:
10664         case CheckBox:
10665             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10666           break;
10667         case ComboBox:
10668           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10669           break;
10670         default:
10671             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10672           break;
10673         case Button:
10674         case SaveButton:
10675           continue;
10676       }
10677       SendToProgram(buf, cps);
10678   }
10679 }
10680
10681 void
10682 StartChessProgram (ChessProgramState *cps)
10683 {
10684     char buf[MSG_SIZ];
10685     int err;
10686
10687     if (appData.noChessProgram) return;
10688     cps->initDone = FALSE;
10689
10690     if (strcmp(cps->host, "localhost") == 0) {
10691         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10692     } else if (*appData.remoteShell == NULLCHAR) {
10693         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10694     } else {
10695         if (*appData.remoteUser == NULLCHAR) {
10696           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10697                     cps->program);
10698         } else {
10699           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10700                     cps->host, appData.remoteUser, cps->program);
10701         }
10702         err = StartChildProcess(buf, "", &cps->pr);
10703     }
10704
10705     if (err != 0) {
10706       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10707         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10708         if(cps != &first) return;
10709         appData.noChessProgram = TRUE;
10710         ThawUI();
10711         SetNCPMode();
10712 //      DisplayFatalError(buf, err, 1);
10713 //      cps->pr = NoProc;
10714 //      cps->isr = NULL;
10715         return;
10716     }
10717
10718     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10719     if (cps->protocolVersion > 1) {
10720       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10721       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10722         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10723         cps->comboCnt = 0;  //                and values of combo boxes
10724       }
10725       SendToProgram(buf, cps);
10726       if(cps->reload) ResendOptions(cps);
10727     } else {
10728       SendToProgram("xboard\n", cps);
10729     }
10730 }
10731
10732 void
10733 TwoMachinesEventIfReady P((void))
10734 {
10735   static int curMess = 0;
10736   if (first.lastPing != first.lastPong) {
10737     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10738     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10739     return;
10740   }
10741   if (second.lastPing != second.lastPong) {
10742     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10743     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10744     return;
10745   }
10746   DisplayMessage("", ""); curMess = 0;
10747   TwoMachinesEvent();
10748 }
10749
10750 char *
10751 MakeName (char *template)
10752 {
10753     time_t clock;
10754     struct tm *tm;
10755     static char buf[MSG_SIZ];
10756     char *p = buf;
10757     int i;
10758
10759     clock = time((time_t *)NULL);
10760     tm = localtime(&clock);
10761
10762     while(*p++ = *template++) if(p[-1] == '%') {
10763         switch(*template++) {
10764           case 0:   *p = 0; return buf;
10765           case 'Y': i = tm->tm_year+1900; break;
10766           case 'y': i = tm->tm_year-100; break;
10767           case 'M': i = tm->tm_mon+1; break;
10768           case 'd': i = tm->tm_mday; break;
10769           case 'h': i = tm->tm_hour; break;
10770           case 'm': i = tm->tm_min; break;
10771           case 's': i = tm->tm_sec; break;
10772           default:  i = 0;
10773         }
10774         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10775     }
10776     return buf;
10777 }
10778
10779 int
10780 CountPlayers (char *p)
10781 {
10782     int n = 0;
10783     while(p = strchr(p, '\n')) p++, n++; // count participants
10784     return n;
10785 }
10786
10787 FILE *
10788 WriteTourneyFile (char *results, FILE *f)
10789 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10790     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10791     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10792         // create a file with tournament description
10793         fprintf(f, "-participants {%s}\n", appData.participants);
10794         fprintf(f, "-seedBase %d\n", appData.seedBase);
10795         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10796         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10797         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10798         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10799         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10800         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10801         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10802         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10803         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10804         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10805         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10806         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10807         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10808         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10809         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10810         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10811         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10812         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10813         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10814         fprintf(f, "-smpCores %d\n", appData.smpCores);
10815         if(searchTime > 0)
10816                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10817         else {
10818                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10819                 fprintf(f, "-tc %s\n", appData.timeControl);
10820                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10821         }
10822         fprintf(f, "-results \"%s\"\n", results);
10823     }
10824     return f;
10825 }
10826
10827 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10828
10829 void
10830 Substitute (char *participants, int expunge)
10831 {
10832     int i, changed, changes=0, nPlayers=0;
10833     char *p, *q, *r, buf[MSG_SIZ];
10834     if(participants == NULL) return;
10835     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10836     r = p = participants; q = appData.participants;
10837     while(*p && *p == *q) {
10838         if(*p == '\n') r = p+1, nPlayers++;
10839         p++; q++;
10840     }
10841     if(*p) { // difference
10842         while(*p && *p++ != '\n');
10843         while(*q && *q++ != '\n');
10844       changed = nPlayers;
10845         changes = 1 + (strcmp(p, q) != 0);
10846     }
10847     if(changes == 1) { // a single engine mnemonic was changed
10848         q = r; while(*q) nPlayers += (*q++ == '\n');
10849         p = buf; while(*r && (*p = *r++) != '\n') p++;
10850         *p = NULLCHAR;
10851         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10852         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10853         if(mnemonic[i]) { // The substitute is valid
10854             FILE *f;
10855             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10856                 flock(fileno(f), LOCK_EX);
10857                 ParseArgsFromFile(f);
10858                 fseek(f, 0, SEEK_SET);
10859                 FREE(appData.participants); appData.participants = participants;
10860                 if(expunge) { // erase results of replaced engine
10861                     int len = strlen(appData.results), w, b, dummy;
10862                     for(i=0; i<len; i++) {
10863                         Pairing(i, nPlayers, &w, &b, &dummy);
10864                         if((w == changed || b == changed) && appData.results[i] == '*') {
10865                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10866                             fclose(f);
10867                             return;
10868                         }
10869                     }
10870                     for(i=0; i<len; i++) {
10871                         Pairing(i, nPlayers, &w, &b, &dummy);
10872                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10873                     }
10874                 }
10875                 WriteTourneyFile(appData.results, f);
10876                 fclose(f); // release lock
10877                 return;
10878             }
10879         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10880     }
10881     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10882     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10883     free(participants);
10884     return;
10885 }
10886
10887 int
10888 CheckPlayers (char *participants)
10889 {
10890         int i;
10891         char buf[MSG_SIZ], *p;
10892         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10893         while(p = strchr(participants, '\n')) {
10894             *p = NULLCHAR;
10895             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10896             if(!mnemonic[i]) {
10897                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10898                 *p = '\n';
10899                 DisplayError(buf, 0);
10900                 return 1;
10901             }
10902             *p = '\n';
10903             participants = p + 1;
10904         }
10905         return 0;
10906 }
10907
10908 int
10909 CreateTourney (char *name)
10910 {
10911         FILE *f;
10912         if(matchMode && strcmp(name, appData.tourneyFile)) {
10913              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10914         }
10915         if(name[0] == NULLCHAR) {
10916             if(appData.participants[0])
10917                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10918             return 0;
10919         }
10920         f = fopen(name, "r");
10921         if(f) { // file exists
10922             ASSIGN(appData.tourneyFile, name);
10923             ParseArgsFromFile(f); // parse it
10924         } else {
10925             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10926             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10927                 DisplayError(_("Not enough participants"), 0);
10928                 return 0;
10929             }
10930             if(CheckPlayers(appData.participants)) return 0;
10931             ASSIGN(appData.tourneyFile, name);
10932             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10933             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10934         }
10935         fclose(f);
10936         appData.noChessProgram = FALSE;
10937         appData.clockMode = TRUE;
10938         SetGNUMode();
10939         return 1;
10940 }
10941
10942 int
10943 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10944 {
10945     char buf[MSG_SIZ], *p, *q;
10946     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10947     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10948     skip = !all && group[0]; // if group requested, we start in skip mode
10949     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10950         p = names; q = buf; header = 0;
10951         while(*p && *p != '\n') *q++ = *p++;
10952         *q = 0;
10953         if(*p == '\n') p++;
10954         if(buf[0] == '#') {
10955             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10956             depth++; // we must be entering a new group
10957             if(all) continue; // suppress printing group headers when complete list requested
10958             header = 1;
10959             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10960         }
10961         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10962         if(engineList[i]) free(engineList[i]);
10963         engineList[i] = strdup(buf);
10964         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10965         if(engineMnemonic[i]) free(engineMnemonic[i]);
10966         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10967             strcat(buf, " (");
10968             sscanf(q + 8, "%s", buf + strlen(buf));
10969             strcat(buf, ")");
10970         }
10971         engineMnemonic[i] = strdup(buf);
10972         i++;
10973     }
10974     engineList[i] = engineMnemonic[i] = NULL;
10975     return i;
10976 }
10977
10978 // following implemented as macro to avoid type limitations
10979 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10980
10981 void
10982 SwapEngines (int n)
10983 {   // swap settings for first engine and other engine (so far only some selected options)
10984     int h;
10985     char *p;
10986     if(n == 0) return;
10987     SWAP(directory, p)
10988     SWAP(chessProgram, p)
10989     SWAP(isUCI, h)
10990     SWAP(hasOwnBookUCI, h)
10991     SWAP(protocolVersion, h)
10992     SWAP(reuse, h)
10993     SWAP(scoreIsAbsolute, h)
10994     SWAP(timeOdds, h)
10995     SWAP(logo, p)
10996     SWAP(pgnName, p)
10997     SWAP(pvSAN, h)
10998     SWAP(engOptions, p)
10999     SWAP(engInitString, p)
11000     SWAP(computerString, p)
11001     SWAP(features, p)
11002     SWAP(fenOverride, p)
11003     SWAP(NPS, h)
11004     SWAP(accumulateTC, h)
11005     SWAP(drawDepth, h)
11006     SWAP(host, p)
11007     SWAP(pseudo, h)
11008 }
11009
11010 int
11011 GetEngineLine (char *s, int n)
11012 {
11013     int i;
11014     char buf[MSG_SIZ];
11015     extern char *icsNames;
11016     if(!s || !*s) return 0;
11017     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11018     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11019     if(!mnemonic[i]) return 0;
11020     if(n == 11) return 1; // just testing if there was a match
11021     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11022     if(n == 1) SwapEngines(n);
11023     ParseArgsFromString(buf);
11024     if(n == 1) SwapEngines(n);
11025     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11026         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11027         ParseArgsFromString(buf);
11028     }
11029     return 1;
11030 }
11031
11032 int
11033 SetPlayer (int player, char *p)
11034 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11035     int i;
11036     char buf[MSG_SIZ], *engineName;
11037     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11038     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11039     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11040     if(mnemonic[i]) {
11041         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11042         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11043         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11044         ParseArgsFromString(buf);
11045     } else { // no engine with this nickname is installed!
11046         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11047         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11048         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11049         ModeHighlight();
11050         DisplayError(buf, 0);
11051         return 0;
11052     }
11053     free(engineName);
11054     return i;
11055 }
11056
11057 char *recentEngines;
11058
11059 void
11060 RecentEngineEvent (int nr)
11061 {
11062     int n;
11063 //    SwapEngines(1); // bump first to second
11064 //    ReplaceEngine(&second, 1); // and load it there
11065     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11066     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11067     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11068         ReplaceEngine(&first, 0);
11069         FloatToFront(&appData.recentEngineList, command[n]);
11070     }
11071 }
11072
11073 int
11074 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11075 {   // determine players from game number
11076     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11077
11078     if(appData.tourneyType == 0) {
11079         roundsPerCycle = (nPlayers - 1) | 1;
11080         pairingsPerRound = nPlayers / 2;
11081     } else if(appData.tourneyType > 0) {
11082         roundsPerCycle = nPlayers - appData.tourneyType;
11083         pairingsPerRound = appData.tourneyType;
11084     }
11085     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11086     gamesPerCycle = gamesPerRound * roundsPerCycle;
11087     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11088     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11089     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11090     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11091     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11092     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11093
11094     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11095     if(appData.roundSync) *syncInterval = gamesPerRound;
11096
11097     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11098
11099     if(appData.tourneyType == 0) {
11100         if(curPairing == (nPlayers-1)/2 ) {
11101             *whitePlayer = curRound;
11102             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11103         } else {
11104             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11105             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11106             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11107             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11108         }
11109     } else if(appData.tourneyType > 1) {
11110         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11111         *whitePlayer = curRound + appData.tourneyType;
11112     } else if(appData.tourneyType > 0) {
11113         *whitePlayer = curPairing;
11114         *blackPlayer = curRound + appData.tourneyType;
11115     }
11116
11117     // take care of white/black alternation per round.
11118     // For cycles and games this is already taken care of by default, derived from matchGame!
11119     return curRound & 1;
11120 }
11121
11122 int
11123 NextTourneyGame (int nr, int *swapColors)
11124 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11125     char *p, *q;
11126     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11127     FILE *tf;
11128     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11129     tf = fopen(appData.tourneyFile, "r");
11130     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11131     ParseArgsFromFile(tf); fclose(tf);
11132     InitTimeControls(); // TC might be altered from tourney file
11133
11134     nPlayers = CountPlayers(appData.participants); // count participants
11135     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11136     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11137
11138     if(syncInterval) {
11139         p = q = appData.results;
11140         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11141         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11142             DisplayMessage(_("Waiting for other game(s)"),"");
11143             waitingForGame = TRUE;
11144             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11145             return 0;
11146         }
11147         waitingForGame = FALSE;
11148     }
11149
11150     if(appData.tourneyType < 0) {
11151         if(nr>=0 && !pairingReceived) {
11152             char buf[1<<16];
11153             if(pairing.pr == NoProc) {
11154                 if(!appData.pairingEngine[0]) {
11155                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11156                     return 0;
11157                 }
11158                 StartChessProgram(&pairing); // starts the pairing engine
11159             }
11160             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11161             SendToProgram(buf, &pairing);
11162             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11163             SendToProgram(buf, &pairing);
11164             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11165         }
11166         pairingReceived = 0;                              // ... so we continue here
11167         *swapColors = 0;
11168         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11169         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11170         matchGame = 1; roundNr = nr / syncInterval + 1;
11171     }
11172
11173     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11174
11175     // redefine engines, engine dir, etc.
11176     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11177     if(first.pr == NoProc) {
11178       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11179       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11180     }
11181     if(second.pr == NoProc) {
11182       SwapEngines(1);
11183       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11184       SwapEngines(1);         // and make that valid for second engine by swapping
11185       InitEngine(&second, 1);
11186     }
11187     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11188     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11189     return OK;
11190 }
11191
11192 void
11193 NextMatchGame ()
11194 {   // performs game initialization that does not invoke engines, and then tries to start the game
11195     int res, firstWhite, swapColors = 0;
11196     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11197     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
11198         char buf[MSG_SIZ];
11199         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11200         if(strcmp(buf, currentDebugFile)) { // name has changed
11201             FILE *f = fopen(buf, "w");
11202             if(f) { // if opening the new file failed, just keep using the old one
11203                 ASSIGN(currentDebugFile, buf);
11204                 fclose(debugFP);
11205                 debugFP = f;
11206             }
11207             if(appData.serverFileName) {
11208                 if(serverFP) fclose(serverFP);
11209                 serverFP = fopen(appData.serverFileName, "w");
11210                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11211                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11212             }
11213         }
11214     }
11215     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11216     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11217     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11218     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11219     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11220     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11221     Reset(FALSE, first.pr != NoProc);
11222     res = LoadGameOrPosition(matchGame); // setup game
11223     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11224     if(!res) return; // abort when bad game/pos file
11225     TwoMachinesEvent();
11226 }
11227
11228 void
11229 UserAdjudicationEvent (int result)
11230 {
11231     ChessMove gameResult = GameIsDrawn;
11232
11233     if( result > 0 ) {
11234         gameResult = WhiteWins;
11235     }
11236     else if( result < 0 ) {
11237         gameResult = BlackWins;
11238     }
11239
11240     if( gameMode == TwoMachinesPlay ) {
11241         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11242     }
11243 }
11244
11245
11246 // [HGM] save: calculate checksum of game to make games easily identifiable
11247 int
11248 StringCheckSum (char *s)
11249 {
11250         int i = 0;
11251         if(s==NULL) return 0;
11252         while(*s) i = i*259 + *s++;
11253         return i;
11254 }
11255
11256 int
11257 GameCheckSum ()
11258 {
11259         int i, sum=0;
11260         for(i=backwardMostMove; i<forwardMostMove; i++) {
11261                 sum += pvInfoList[i].depth;
11262                 sum += StringCheckSum(parseList[i]);
11263                 sum += StringCheckSum(commentList[i]);
11264                 sum *= 261;
11265         }
11266         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11267         return sum + StringCheckSum(commentList[i]);
11268 } // end of save patch
11269
11270 void
11271 GameEnds (ChessMove result, char *resultDetails, int whosays)
11272 {
11273     GameMode nextGameMode;
11274     int isIcsGame;
11275     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11276
11277     if(endingGame) return; /* [HGM] crash: forbid recursion */
11278     endingGame = 1;
11279     if(twoBoards) { // [HGM] dual: switch back to one board
11280         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11281         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11282     }
11283     if (appData.debugMode) {
11284       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11285               result, resultDetails ? resultDetails : "(null)", whosays);
11286     }
11287
11288     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11289
11290     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11291
11292     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11293         /* If we are playing on ICS, the server decides when the
11294            game is over, but the engine can offer to draw, claim
11295            a draw, or resign.
11296          */
11297 #if ZIPPY
11298         if (appData.zippyPlay && first.initDone) {
11299             if (result == GameIsDrawn) {
11300                 /* In case draw still needs to be claimed */
11301                 SendToICS(ics_prefix);
11302                 SendToICS("draw\n");
11303             } else if (StrCaseStr(resultDetails, "resign")) {
11304                 SendToICS(ics_prefix);
11305                 SendToICS("resign\n");
11306             }
11307         }
11308 #endif
11309         endingGame = 0; /* [HGM] crash */
11310         return;
11311     }
11312
11313     /* If we're loading the game from a file, stop */
11314     if (whosays == GE_FILE) {
11315       (void) StopLoadGameTimer();
11316       gameFileFP = NULL;
11317     }
11318
11319     /* Cancel draw offers */
11320     first.offeredDraw = second.offeredDraw = 0;
11321
11322     /* If this is an ICS game, only ICS can really say it's done;
11323        if not, anyone can. */
11324     isIcsGame = (gameMode == IcsPlayingWhite ||
11325                  gameMode == IcsPlayingBlack ||
11326                  gameMode == IcsObserving    ||
11327                  gameMode == IcsExamining);
11328
11329     if (!isIcsGame || whosays == GE_ICS) {
11330         /* OK -- not an ICS game, or ICS said it was done */
11331         StopClocks();
11332         if (!isIcsGame && !appData.noChessProgram)
11333           SetUserThinkingEnables();
11334
11335         /* [HGM] if a machine claims the game end we verify this claim */
11336         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11337             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11338                 char claimer;
11339                 ChessMove trueResult = (ChessMove) -1;
11340
11341                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11342                                             first.twoMachinesColor[0] :
11343                                             second.twoMachinesColor[0] ;
11344
11345                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11346                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11347                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11348                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11349                 } else
11350                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11351                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11352                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11353                 } else
11354                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11355                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11356                 }
11357
11358                 // now verify win claims, but not in drop games, as we don't understand those yet
11359                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11360                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11361                     (result == WhiteWins && claimer == 'w' ||
11362                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11363                       if (appData.debugMode) {
11364                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11365                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11366                       }
11367                       if(result != trueResult) {
11368                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11369                               result = claimer == 'w' ? BlackWins : WhiteWins;
11370                               resultDetails = buf;
11371                       }
11372                 } else
11373                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11374                     && (forwardMostMove <= backwardMostMove ||
11375                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11376                         (claimer=='b')==(forwardMostMove&1))
11377                                                                                   ) {
11378                       /* [HGM] verify: draws that were not flagged are false claims */
11379                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11380                       result = claimer == 'w' ? BlackWins : WhiteWins;
11381                       resultDetails = buf;
11382                 }
11383                 /* (Claiming a loss is accepted no questions asked!) */
11384             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11385                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11386                 result = GameUnfinished;
11387                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11388             }
11389             /* [HGM] bare: don't allow bare King to win */
11390             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11391                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11392                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11393                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11394                && result != GameIsDrawn)
11395             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11396                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11397                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11398                         if(p >= 0 && p <= (int)WhiteKing) k++;
11399                 }
11400                 if (appData.debugMode) {
11401                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11402                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11403                 }
11404                 if(k <= 1) {
11405                         result = GameIsDrawn;
11406                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11407                         resultDetails = buf;
11408                 }
11409             }
11410         }
11411
11412
11413         if(serverMoves != NULL && !loadFlag) { char c = '=';
11414             if(result==WhiteWins) c = '+';
11415             if(result==BlackWins) c = '-';
11416             if(resultDetails != NULL)
11417                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11418         }
11419         if (resultDetails != NULL) {
11420             gameInfo.result = result;
11421             gameInfo.resultDetails = StrSave(resultDetails);
11422
11423             /* display last move only if game was not loaded from file */
11424             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11425                 DisplayMove(currentMove - 1);
11426
11427             if (forwardMostMove != 0) {
11428                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11429                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11430                                                                 ) {
11431                     if (*appData.saveGameFile != NULLCHAR) {
11432                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11433                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11434                         else
11435                         SaveGameToFile(appData.saveGameFile, TRUE);
11436                     } else if (appData.autoSaveGames) {
11437                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11438                     }
11439                     if (*appData.savePositionFile != NULLCHAR) {
11440                         SavePositionToFile(appData.savePositionFile);
11441                     }
11442                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11443                 }
11444             }
11445
11446             /* Tell program how game ended in case it is learning */
11447             /* [HGM] Moved this to after saving the PGN, just in case */
11448             /* engine died and we got here through time loss. In that */
11449             /* case we will get a fatal error writing the pipe, which */
11450             /* would otherwise lose us the PGN.                       */
11451             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11452             /* output during GameEnds should never be fatal anymore   */
11453             if (gameMode == MachinePlaysWhite ||
11454                 gameMode == MachinePlaysBlack ||
11455                 gameMode == TwoMachinesPlay ||
11456                 gameMode == IcsPlayingWhite ||
11457                 gameMode == IcsPlayingBlack ||
11458                 gameMode == BeginningOfGame) {
11459                 char buf[MSG_SIZ];
11460                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11461                         resultDetails);
11462                 if (first.pr != NoProc) {
11463                     SendToProgram(buf, &first);
11464                 }
11465                 if (second.pr != NoProc &&
11466                     gameMode == TwoMachinesPlay) {
11467                     SendToProgram(buf, &second);
11468                 }
11469             }
11470         }
11471
11472         if (appData.icsActive) {
11473             if (appData.quietPlay &&
11474                 (gameMode == IcsPlayingWhite ||
11475                  gameMode == IcsPlayingBlack)) {
11476                 SendToICS(ics_prefix);
11477                 SendToICS("set shout 1\n");
11478             }
11479             nextGameMode = IcsIdle;
11480             ics_user_moved = FALSE;
11481             /* clean up premove.  It's ugly when the game has ended and the
11482              * premove highlights are still on the board.
11483              */
11484             if (gotPremove) {
11485               gotPremove = FALSE;
11486               ClearPremoveHighlights();
11487               DrawPosition(FALSE, boards[currentMove]);
11488             }
11489             if (whosays == GE_ICS) {
11490                 switch (result) {
11491                 case WhiteWins:
11492                     if (gameMode == IcsPlayingWhite)
11493                         PlayIcsWinSound();
11494                     else if(gameMode == IcsPlayingBlack)
11495                         PlayIcsLossSound();
11496                     break;
11497                 case BlackWins:
11498                     if (gameMode == IcsPlayingBlack)
11499                         PlayIcsWinSound();
11500                     else if(gameMode == IcsPlayingWhite)
11501                         PlayIcsLossSound();
11502                     break;
11503                 case GameIsDrawn:
11504                     PlayIcsDrawSound();
11505                     break;
11506                 default:
11507                     PlayIcsUnfinishedSound();
11508                 }
11509             }
11510             if(appData.quitNext) { ExitEvent(0); return; }
11511         } else if (gameMode == EditGame ||
11512                    gameMode == PlayFromGameFile ||
11513                    gameMode == AnalyzeMode ||
11514                    gameMode == AnalyzeFile) {
11515             nextGameMode = gameMode;
11516         } else {
11517             nextGameMode = EndOfGame;
11518         }
11519         pausing = FALSE;
11520         ModeHighlight();
11521     } else {
11522         nextGameMode = gameMode;
11523     }
11524
11525     if (appData.noChessProgram) {
11526         gameMode = nextGameMode;
11527         ModeHighlight();
11528         endingGame = 0; /* [HGM] crash */
11529         return;
11530     }
11531
11532     if (first.reuse) {
11533         /* Put first chess program into idle state */
11534         if (first.pr != NoProc &&
11535             (gameMode == MachinePlaysWhite ||
11536              gameMode == MachinePlaysBlack ||
11537              gameMode == TwoMachinesPlay ||
11538              gameMode == IcsPlayingWhite ||
11539              gameMode == IcsPlayingBlack ||
11540              gameMode == BeginningOfGame)) {
11541             SendToProgram("force\n", &first);
11542             if (first.usePing) {
11543               char buf[MSG_SIZ];
11544               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11545               SendToProgram(buf, &first);
11546             }
11547         }
11548     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11549         /* Kill off first chess program */
11550         if (first.isr != NULL)
11551           RemoveInputSource(first.isr);
11552         first.isr = NULL;
11553
11554         if (first.pr != NoProc) {
11555             ExitAnalyzeMode();
11556             DoSleep( appData.delayBeforeQuit );
11557             SendToProgram("quit\n", &first);
11558             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11559             first.reload = TRUE;
11560         }
11561         first.pr = NoProc;
11562     }
11563     if (second.reuse) {
11564         /* Put second chess program into idle state */
11565         if (second.pr != NoProc &&
11566             gameMode == TwoMachinesPlay) {
11567             SendToProgram("force\n", &second);
11568             if (second.usePing) {
11569               char buf[MSG_SIZ];
11570               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11571               SendToProgram(buf, &second);
11572             }
11573         }
11574     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11575         /* Kill off second chess program */
11576         if (second.isr != NULL)
11577           RemoveInputSource(second.isr);
11578         second.isr = NULL;
11579
11580         if (second.pr != NoProc) {
11581             DoSleep( appData.delayBeforeQuit );
11582             SendToProgram("quit\n", &second);
11583             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11584             second.reload = TRUE;
11585         }
11586         second.pr = NoProc;
11587     }
11588
11589     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11590         char resChar = '=';
11591         switch (result) {
11592         case WhiteWins:
11593           resChar = '+';
11594           if (first.twoMachinesColor[0] == 'w') {
11595             first.matchWins++;
11596           } else {
11597             second.matchWins++;
11598           }
11599           break;
11600         case BlackWins:
11601           resChar = '-';
11602           if (first.twoMachinesColor[0] == 'b') {
11603             first.matchWins++;
11604           } else {
11605             second.matchWins++;
11606           }
11607           break;
11608         case GameUnfinished:
11609           resChar = ' ';
11610         default:
11611           break;
11612         }
11613
11614         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11615         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11616             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11617             ReserveGame(nextGame, resChar); // sets nextGame
11618             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11619             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11620         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11621
11622         if (nextGame <= appData.matchGames && !abortMatch) {
11623             gameMode = nextGameMode;
11624             matchGame = nextGame; // this will be overruled in tourney mode!
11625             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11626             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11627             endingGame = 0; /* [HGM] crash */
11628             return;
11629         } else {
11630             gameMode = nextGameMode;
11631             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11632                      first.tidy, second.tidy,
11633                      first.matchWins, second.matchWins,
11634                      appData.matchGames - (first.matchWins + second.matchWins));
11635             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11636             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11637             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11638             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11639                 first.twoMachinesColor = "black\n";
11640                 second.twoMachinesColor = "white\n";
11641             } else {
11642                 first.twoMachinesColor = "white\n";
11643                 second.twoMachinesColor = "black\n";
11644             }
11645         }
11646     }
11647     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11648         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11649       ExitAnalyzeMode();
11650     gameMode = nextGameMode;
11651     ModeHighlight();
11652     endingGame = 0;  /* [HGM] crash */
11653     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11654         if(matchMode == TRUE) { // match through command line: exit with or without popup
11655             if(ranking) {
11656                 ToNrEvent(forwardMostMove);
11657                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11658                 else ExitEvent(0);
11659             } else DisplayFatalError(buf, 0, 0);
11660         } else { // match through menu; just stop, with or without popup
11661             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11662             ModeHighlight();
11663             if(ranking){
11664                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11665             } else DisplayNote(buf);
11666       }
11667       if(ranking) free(ranking);
11668     }
11669 }
11670
11671 /* Assumes program was just initialized (initString sent).
11672    Leaves program in force mode. */
11673 void
11674 FeedMovesToProgram (ChessProgramState *cps, int upto)
11675 {
11676     int i;
11677
11678     if (appData.debugMode)
11679       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11680               startedFromSetupPosition ? "position and " : "",
11681               backwardMostMove, upto, cps->which);
11682     if(currentlyInitializedVariant != gameInfo.variant) {
11683       char buf[MSG_SIZ];
11684         // [HGM] variantswitch: make engine aware of new variant
11685         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11686                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11687                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11688         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11689         SendToProgram(buf, cps);
11690         currentlyInitializedVariant = gameInfo.variant;
11691     }
11692     SendToProgram("force\n", cps);
11693     if (startedFromSetupPosition) {
11694         SendBoard(cps, backwardMostMove);
11695     if (appData.debugMode) {
11696         fprintf(debugFP, "feedMoves\n");
11697     }
11698     }
11699     for (i = backwardMostMove; i < upto; i++) {
11700         SendMoveToProgram(i, cps);
11701     }
11702 }
11703
11704
11705 int
11706 ResurrectChessProgram ()
11707 {
11708      /* The chess program may have exited.
11709         If so, restart it and feed it all the moves made so far. */
11710     static int doInit = 0;
11711
11712     if (appData.noChessProgram) return 1;
11713
11714     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11715         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11716         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11717         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11718     } else {
11719         if (first.pr != NoProc) return 1;
11720         StartChessProgram(&first);
11721     }
11722     InitChessProgram(&first, FALSE);
11723     FeedMovesToProgram(&first, currentMove);
11724
11725     if (!first.sendTime) {
11726         /* can't tell gnuchess what its clock should read,
11727            so we bow to its notion. */
11728         ResetClocks();
11729         timeRemaining[0][currentMove] = whiteTimeRemaining;
11730         timeRemaining[1][currentMove] = blackTimeRemaining;
11731     }
11732
11733     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11734                 appData.icsEngineAnalyze) && first.analysisSupport) {
11735       SendToProgram("analyze\n", &first);
11736       first.analyzing = TRUE;
11737     }
11738     return 1;
11739 }
11740
11741 /*
11742  * Button procedures
11743  */
11744 void
11745 Reset (int redraw, int init)
11746 {
11747     int i;
11748
11749     if (appData.debugMode) {
11750         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11751                 redraw, init, gameMode);
11752     }
11753     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11754     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11755     CleanupTail(); // [HGM] vari: delete any stored variations
11756     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11757     pausing = pauseExamInvalid = FALSE;
11758     startedFromSetupPosition = blackPlaysFirst = FALSE;
11759     firstMove = TRUE;
11760     whiteFlag = blackFlag = FALSE;
11761     userOfferedDraw = FALSE;
11762     hintRequested = bookRequested = FALSE;
11763     first.maybeThinking = FALSE;
11764     second.maybeThinking = FALSE;
11765     first.bookSuspend = FALSE; // [HGM] book
11766     second.bookSuspend = FALSE;
11767     thinkOutput[0] = NULLCHAR;
11768     lastHint[0] = NULLCHAR;
11769     ClearGameInfo(&gameInfo);
11770     gameInfo.variant = StringToVariant(appData.variant);
11771     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11772     ics_user_moved = ics_clock_paused = FALSE;
11773     ics_getting_history = H_FALSE;
11774     ics_gamenum = -1;
11775     white_holding[0] = black_holding[0] = NULLCHAR;
11776     ClearProgramStats();
11777     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11778
11779     ResetFrontEnd();
11780     ClearHighlights();
11781     flipView = appData.flipView;
11782     ClearPremoveHighlights();
11783     gotPremove = FALSE;
11784     alarmSounded = FALSE;
11785     killX = killY = -1; // [HGM] lion
11786
11787     GameEnds(EndOfFile, NULL, GE_PLAYER);
11788     if(appData.serverMovesName != NULL) {
11789         /* [HGM] prepare to make moves file for broadcasting */
11790         clock_t t = clock();
11791         if(serverMoves != NULL) fclose(serverMoves);
11792         serverMoves = fopen(appData.serverMovesName, "r");
11793         if(serverMoves != NULL) {
11794             fclose(serverMoves);
11795             /* delay 15 sec before overwriting, so all clients can see end */
11796             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11797         }
11798         serverMoves = fopen(appData.serverMovesName, "w");
11799     }
11800
11801     ExitAnalyzeMode();
11802     gameMode = BeginningOfGame;
11803     ModeHighlight();
11804     if(appData.icsActive) gameInfo.variant = VariantNormal;
11805     currentMove = forwardMostMove = backwardMostMove = 0;
11806     MarkTargetSquares(1);
11807     InitPosition(redraw);
11808     for (i = 0; i < MAX_MOVES; i++) {
11809         if (commentList[i] != NULL) {
11810             free(commentList[i]);
11811             commentList[i] = NULL;
11812         }
11813     }
11814     ResetClocks();
11815     timeRemaining[0][0] = whiteTimeRemaining;
11816     timeRemaining[1][0] = blackTimeRemaining;
11817
11818     if (first.pr == NoProc) {
11819         StartChessProgram(&first);
11820     }
11821     if (init) {
11822             InitChessProgram(&first, startedFromSetupPosition);
11823     }
11824     DisplayTitle("");
11825     DisplayMessage("", "");
11826     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11827     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11828     ClearMap();        // [HGM] exclude: invalidate map
11829 }
11830
11831 void
11832 AutoPlayGameLoop ()
11833 {
11834     for (;;) {
11835         if (!AutoPlayOneMove())
11836           return;
11837         if (matchMode || appData.timeDelay == 0)
11838           continue;
11839         if (appData.timeDelay < 0)
11840           return;
11841         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11842         break;
11843     }
11844 }
11845
11846 void
11847 AnalyzeNextGame()
11848 {
11849     ReloadGame(1); // next game
11850 }
11851
11852 int
11853 AutoPlayOneMove ()
11854 {
11855     int fromX, fromY, toX, toY;
11856
11857     if (appData.debugMode) {
11858       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11859     }
11860
11861     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11862       return FALSE;
11863
11864     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11865       pvInfoList[currentMove].depth = programStats.depth;
11866       pvInfoList[currentMove].score = programStats.score;
11867       pvInfoList[currentMove].time  = 0;
11868       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11869       else { // append analysis of final position as comment
11870         char buf[MSG_SIZ];
11871         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11872         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11873       }
11874       programStats.depth = 0;
11875     }
11876
11877     if (currentMove >= forwardMostMove) {
11878       if(gameMode == AnalyzeFile) {
11879           if(appData.loadGameIndex == -1) {
11880             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11881           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11882           } else {
11883           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11884         }
11885       }
11886 //      gameMode = EndOfGame;
11887 //      ModeHighlight();
11888
11889       /* [AS] Clear current move marker at the end of a game */
11890       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11891
11892       return FALSE;
11893     }
11894
11895     toX = moveList[currentMove][2] - AAA;
11896     toY = moveList[currentMove][3] - ONE;
11897
11898     if (moveList[currentMove][1] == '@') {
11899         if (appData.highlightLastMove) {
11900             SetHighlights(-1, -1, toX, toY);
11901         }
11902     } else {
11903         int viaX = moveList[currentMove][5] - AAA;
11904         int viaY = moveList[currentMove][6] - ONE;
11905         fromX = moveList[currentMove][0] - AAA;
11906         fromY = moveList[currentMove][1] - ONE;
11907
11908         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11909
11910         if(moveList[currentMove][4] == ';') { // multi-leg
11911             ChessSquare piece = boards[currentMove][viaY][viaX];
11912             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11913             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11914             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11915             boards[currentMove][viaY][viaX] = piece;
11916         } else
11917         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11918
11919         if (appData.highlightLastMove) {
11920             SetHighlights(fromX, fromY, toX, toY);
11921         }
11922     }
11923     DisplayMove(currentMove);
11924     SendMoveToProgram(currentMove++, &first);
11925     DisplayBothClocks();
11926     DrawPosition(FALSE, boards[currentMove]);
11927     // [HGM] PV info: always display, routine tests if empty
11928     DisplayComment(currentMove - 1, commentList[currentMove]);
11929     return TRUE;
11930 }
11931
11932
11933 int
11934 LoadGameOneMove (ChessMove readAhead)
11935 {
11936     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11937     char promoChar = NULLCHAR;
11938     ChessMove moveType;
11939     char move[MSG_SIZ];
11940     char *p, *q;
11941
11942     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11943         gameMode != AnalyzeMode && gameMode != Training) {
11944         gameFileFP = NULL;
11945         return FALSE;
11946     }
11947
11948     yyboardindex = forwardMostMove;
11949     if (readAhead != EndOfFile) {
11950       moveType = readAhead;
11951     } else {
11952       if (gameFileFP == NULL)
11953           return FALSE;
11954       moveType = (ChessMove) Myylex();
11955     }
11956
11957     done = FALSE;
11958     switch (moveType) {
11959       case Comment:
11960         if (appData.debugMode)
11961           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11962         p = yy_text;
11963
11964         /* append the comment but don't display it */
11965         AppendComment(currentMove, p, FALSE);
11966         return TRUE;
11967
11968       case WhiteCapturesEnPassant:
11969       case BlackCapturesEnPassant:
11970       case WhitePromotion:
11971       case BlackPromotion:
11972       case WhiteNonPromotion:
11973       case BlackNonPromotion:
11974       case NormalMove:
11975       case FirstLeg:
11976       case WhiteKingSideCastle:
11977       case WhiteQueenSideCastle:
11978       case BlackKingSideCastle:
11979       case BlackQueenSideCastle:
11980       case WhiteKingSideCastleWild:
11981       case WhiteQueenSideCastleWild:
11982       case BlackKingSideCastleWild:
11983       case BlackQueenSideCastleWild:
11984       /* PUSH Fabien */
11985       case WhiteHSideCastleFR:
11986       case WhiteASideCastleFR:
11987       case BlackHSideCastleFR:
11988       case BlackASideCastleFR:
11989       /* POP Fabien */
11990         if (appData.debugMode)
11991           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11992         fromX = currentMoveString[0] - AAA;
11993         fromY = currentMoveString[1] - ONE;
11994         toX = currentMoveString[2] - AAA;
11995         toY = currentMoveString[3] - ONE;
11996         promoChar = currentMoveString[4];
11997         if(promoChar == ';') promoChar = NULLCHAR;
11998         break;
11999
12000       case WhiteDrop:
12001       case BlackDrop:
12002         if (appData.debugMode)
12003           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12004         fromX = moveType == WhiteDrop ?
12005           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12006         (int) CharToPiece(ToLower(currentMoveString[0]));
12007         fromY = DROP_RANK;
12008         toX = currentMoveString[2] - AAA;
12009         toY = currentMoveString[3] - ONE;
12010         break;
12011
12012       case WhiteWins:
12013       case BlackWins:
12014       case GameIsDrawn:
12015       case GameUnfinished:
12016         if (appData.debugMode)
12017           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12018         p = strchr(yy_text, '{');
12019         if (p == NULL) p = strchr(yy_text, '(');
12020         if (p == NULL) {
12021             p = yy_text;
12022             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12023         } else {
12024             q = strchr(p, *p == '{' ? '}' : ')');
12025             if (q != NULL) *q = NULLCHAR;
12026             p++;
12027         }
12028         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12029         GameEnds(moveType, p, GE_FILE);
12030         done = TRUE;
12031         if (cmailMsgLoaded) {
12032             ClearHighlights();
12033             flipView = WhiteOnMove(currentMove);
12034             if (moveType == GameUnfinished) flipView = !flipView;
12035             if (appData.debugMode)
12036               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12037         }
12038         break;
12039
12040       case EndOfFile:
12041         if (appData.debugMode)
12042           fprintf(debugFP, "Parser hit end of file\n");
12043         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12044           case MT_NONE:
12045           case MT_CHECK:
12046             break;
12047           case MT_CHECKMATE:
12048           case MT_STAINMATE:
12049             if (WhiteOnMove(currentMove)) {
12050                 GameEnds(BlackWins, "Black mates", GE_FILE);
12051             } else {
12052                 GameEnds(WhiteWins, "White mates", GE_FILE);
12053             }
12054             break;
12055           case MT_STALEMATE:
12056             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12057             break;
12058         }
12059         done = TRUE;
12060         break;
12061
12062       case MoveNumberOne:
12063         if (lastLoadGameStart == GNUChessGame) {
12064             /* GNUChessGames have numbers, but they aren't move numbers */
12065             if (appData.debugMode)
12066               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12067                       yy_text, (int) moveType);
12068             return LoadGameOneMove(EndOfFile); /* tail recursion */
12069         }
12070         /* else fall thru */
12071
12072       case XBoardGame:
12073       case GNUChessGame:
12074       case PGNTag:
12075         /* Reached start of next game in file */
12076         if (appData.debugMode)
12077           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12078         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12079           case MT_NONE:
12080           case MT_CHECK:
12081             break;
12082           case MT_CHECKMATE:
12083           case MT_STAINMATE:
12084             if (WhiteOnMove(currentMove)) {
12085                 GameEnds(BlackWins, "Black mates", GE_FILE);
12086             } else {
12087                 GameEnds(WhiteWins, "White mates", GE_FILE);
12088             }
12089             break;
12090           case MT_STALEMATE:
12091             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12092             break;
12093         }
12094         done = TRUE;
12095         break;
12096
12097       case PositionDiagram:     /* should not happen; ignore */
12098       case ElapsedTime:         /* ignore */
12099       case NAG:                 /* ignore */
12100         if (appData.debugMode)
12101           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12102                   yy_text, (int) moveType);
12103         return LoadGameOneMove(EndOfFile); /* tail recursion */
12104
12105       case IllegalMove:
12106         if (appData.testLegality) {
12107             if (appData.debugMode)
12108               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12109             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12110                     (forwardMostMove / 2) + 1,
12111                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12112             DisplayError(move, 0);
12113             done = TRUE;
12114         } else {
12115             if (appData.debugMode)
12116               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12117                       yy_text, currentMoveString);
12118             if(currentMoveString[1] == '@') {
12119                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12120                 fromY = DROP_RANK;
12121             } else {
12122                 fromX = currentMoveString[0] - AAA;
12123                 fromY = currentMoveString[1] - ONE;
12124             }
12125             toX = currentMoveString[2] - AAA;
12126             toY = currentMoveString[3] - ONE;
12127             promoChar = currentMoveString[4];
12128         }
12129         break;
12130
12131       case AmbiguousMove:
12132         if (appData.debugMode)
12133           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12134         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12135                 (forwardMostMove / 2) + 1,
12136                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12137         DisplayError(move, 0);
12138         done = TRUE;
12139         break;
12140
12141       default:
12142       case ImpossibleMove:
12143         if (appData.debugMode)
12144           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12145         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12146                 (forwardMostMove / 2) + 1,
12147                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12148         DisplayError(move, 0);
12149         done = TRUE;
12150         break;
12151     }
12152
12153     if (done) {
12154         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12155             DrawPosition(FALSE, boards[currentMove]);
12156             DisplayBothClocks();
12157             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12158               DisplayComment(currentMove - 1, commentList[currentMove]);
12159         }
12160         (void) StopLoadGameTimer();
12161         gameFileFP = NULL;
12162         cmailOldMove = forwardMostMove;
12163         return FALSE;
12164     } else {
12165         /* currentMoveString is set as a side-effect of yylex */
12166
12167         thinkOutput[0] = NULLCHAR;
12168         MakeMove(fromX, fromY, toX, toY, promoChar);
12169         killX = killY = -1; // [HGM] lion: used up
12170         currentMove = forwardMostMove;
12171         return TRUE;
12172     }
12173 }
12174
12175 /* Load the nth game from the given file */
12176 int
12177 LoadGameFromFile (char *filename, int n, char *title, int useList)
12178 {
12179     FILE *f;
12180     char buf[MSG_SIZ];
12181
12182     if (strcmp(filename, "-") == 0) {
12183         f = stdin;
12184         title = "stdin";
12185     } else {
12186         f = fopen(filename, "rb");
12187         if (f == NULL) {
12188           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12189             DisplayError(buf, errno);
12190             return FALSE;
12191         }
12192     }
12193     if (fseek(f, 0, 0) == -1) {
12194         /* f is not seekable; probably a pipe */
12195         useList = FALSE;
12196     }
12197     if (useList && n == 0) {
12198         int error = GameListBuild(f);
12199         if (error) {
12200             DisplayError(_("Cannot build game list"), error);
12201         } else if (!ListEmpty(&gameList) &&
12202                    ((ListGame *) gameList.tailPred)->number > 1) {
12203             GameListPopUp(f, title);
12204             return TRUE;
12205         }
12206         GameListDestroy();
12207         n = 1;
12208     }
12209     if (n == 0) n = 1;
12210     return LoadGame(f, n, title, FALSE);
12211 }
12212
12213
12214 void
12215 MakeRegisteredMove ()
12216 {
12217     int fromX, fromY, toX, toY;
12218     char promoChar;
12219     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12220         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12221           case CMAIL_MOVE:
12222           case CMAIL_DRAW:
12223             if (appData.debugMode)
12224               fprintf(debugFP, "Restoring %s for game %d\n",
12225                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12226
12227             thinkOutput[0] = NULLCHAR;
12228             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12229             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12230             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12231             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12232             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12233             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12234             MakeMove(fromX, fromY, toX, toY, promoChar);
12235             ShowMove(fromX, fromY, toX, toY);
12236
12237             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12238               case MT_NONE:
12239               case MT_CHECK:
12240                 break;
12241
12242               case MT_CHECKMATE:
12243               case MT_STAINMATE:
12244                 if (WhiteOnMove(currentMove)) {
12245                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12246                 } else {
12247                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12248                 }
12249                 break;
12250
12251               case MT_STALEMATE:
12252                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12253                 break;
12254             }
12255
12256             break;
12257
12258           case CMAIL_RESIGN:
12259             if (WhiteOnMove(currentMove)) {
12260                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12261             } else {
12262                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12263             }
12264             break;
12265
12266           case CMAIL_ACCEPT:
12267             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12268             break;
12269
12270           default:
12271             break;
12272         }
12273     }
12274
12275     return;
12276 }
12277
12278 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12279 int
12280 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12281 {
12282     int retVal;
12283
12284     if (gameNumber > nCmailGames) {
12285         DisplayError(_("No more games in this message"), 0);
12286         return FALSE;
12287     }
12288     if (f == lastLoadGameFP) {
12289         int offset = gameNumber - lastLoadGameNumber;
12290         if (offset == 0) {
12291             cmailMsg[0] = NULLCHAR;
12292             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12293                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12294                 nCmailMovesRegistered--;
12295             }
12296             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12297             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12298                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12299             }
12300         } else {
12301             if (! RegisterMove()) return FALSE;
12302         }
12303     }
12304
12305     retVal = LoadGame(f, gameNumber, title, useList);
12306
12307     /* Make move registered during previous look at this game, if any */
12308     MakeRegisteredMove();
12309
12310     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12311         commentList[currentMove]
12312           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12313         DisplayComment(currentMove - 1, commentList[currentMove]);
12314     }
12315
12316     return retVal;
12317 }
12318
12319 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12320 int
12321 ReloadGame (int offset)
12322 {
12323     int gameNumber = lastLoadGameNumber + offset;
12324     if (lastLoadGameFP == NULL) {
12325         DisplayError(_("No game has been loaded yet"), 0);
12326         return FALSE;
12327     }
12328     if (gameNumber <= 0) {
12329         DisplayError(_("Can't back up any further"), 0);
12330         return FALSE;
12331     }
12332     if (cmailMsgLoaded) {
12333         return CmailLoadGame(lastLoadGameFP, gameNumber,
12334                              lastLoadGameTitle, lastLoadGameUseList);
12335     } else {
12336         return LoadGame(lastLoadGameFP, gameNumber,
12337                         lastLoadGameTitle, lastLoadGameUseList);
12338     }
12339 }
12340
12341 int keys[EmptySquare+1];
12342
12343 int
12344 PositionMatches (Board b1, Board b2)
12345 {
12346     int r, f, sum=0;
12347     switch(appData.searchMode) {
12348         case 1: return CompareWithRights(b1, b2);
12349         case 2:
12350             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12351                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12352             }
12353             return TRUE;
12354         case 3:
12355             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12356               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12357                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12358             }
12359             return sum==0;
12360         case 4:
12361             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12362                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12363             }
12364             return sum==0;
12365     }
12366     return TRUE;
12367 }
12368
12369 #define Q_PROMO  4
12370 #define Q_EP     3
12371 #define Q_BCASTL 2
12372 #define Q_WCASTL 1
12373
12374 int pieceList[256], quickBoard[256];
12375 ChessSquare pieceType[256] = { EmptySquare };
12376 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12377 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12378 int soughtTotal, turn;
12379 Boolean epOK, flipSearch;
12380
12381 typedef struct {
12382     unsigned char piece, to;
12383 } Move;
12384
12385 #define DSIZE (250000)
12386
12387 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12388 Move *moveDatabase = initialSpace;
12389 unsigned int movePtr, dataSize = DSIZE;
12390
12391 int
12392 MakePieceList (Board board, int *counts)
12393 {
12394     int r, f, n=Q_PROMO, total=0;
12395     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12396     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12397         int sq = f + (r<<4);
12398         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12399             quickBoard[sq] = ++n;
12400             pieceList[n] = sq;
12401             pieceType[n] = board[r][f];
12402             counts[board[r][f]]++;
12403             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12404             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12405             total++;
12406         }
12407     }
12408     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12409     return total;
12410 }
12411
12412 void
12413 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12414 {
12415     int sq = fromX + (fromY<<4);
12416     int piece = quickBoard[sq], rook;
12417     quickBoard[sq] = 0;
12418     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12419     if(piece == pieceList[1] && fromY == toY) {
12420       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12421         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12422         moveDatabase[movePtr++].piece = Q_WCASTL;
12423         quickBoard[sq] = piece;
12424         piece = quickBoard[from]; quickBoard[from] = 0;
12425         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12426       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12427         quickBoard[sq] = 0; // remove Rook
12428         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12429         moveDatabase[movePtr++].piece = Q_WCASTL;
12430         quickBoard[sq] = pieceList[1]; // put King
12431         piece = rook;
12432         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12433       }
12434     } else
12435     if(piece == pieceList[2] && fromY == toY) {
12436       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12437         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12438         moveDatabase[movePtr++].piece = Q_BCASTL;
12439         quickBoard[sq] = piece;
12440         piece = quickBoard[from]; quickBoard[from] = 0;
12441         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12442       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12443         quickBoard[sq] = 0; // remove Rook
12444         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12445         moveDatabase[movePtr++].piece = Q_BCASTL;
12446         quickBoard[sq] = pieceList[2]; // put King
12447         piece = rook;
12448         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12449       }
12450     } else
12451     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12452         quickBoard[(fromY<<4)+toX] = 0;
12453         moveDatabase[movePtr].piece = Q_EP;
12454         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12455         moveDatabase[movePtr].to = sq;
12456     } else
12457     if(promoPiece != pieceType[piece]) {
12458         moveDatabase[movePtr++].piece = Q_PROMO;
12459         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12460     }
12461     moveDatabase[movePtr].piece = piece;
12462     quickBoard[sq] = piece;
12463     movePtr++;
12464 }
12465
12466 int
12467 PackGame (Board board)
12468 {
12469     Move *newSpace = NULL;
12470     moveDatabase[movePtr].piece = 0; // terminate previous game
12471     if(movePtr > dataSize) {
12472         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12473         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12474         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12475         if(newSpace) {
12476             int i;
12477             Move *p = moveDatabase, *q = newSpace;
12478             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12479             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12480             moveDatabase = newSpace;
12481         } else { // calloc failed, we must be out of memory. Too bad...
12482             dataSize = 0; // prevent calloc events for all subsequent games
12483             return 0;     // and signal this one isn't cached
12484         }
12485     }
12486     movePtr++;
12487     MakePieceList(board, counts);
12488     return movePtr;
12489 }
12490
12491 int
12492 QuickCompare (Board board, int *minCounts, int *maxCounts)
12493 {   // compare according to search mode
12494     int r, f;
12495     switch(appData.searchMode)
12496     {
12497       case 1: // exact position match
12498         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12499         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12500             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12501         }
12502         break;
12503       case 2: // can have extra material on empty squares
12504         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12505             if(board[r][f] == EmptySquare) continue;
12506             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12507         }
12508         break;
12509       case 3: // material with exact Pawn structure
12510         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12511             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12512             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12513         } // fall through to material comparison
12514       case 4: // exact material
12515         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12516         break;
12517       case 6: // material range with given imbalance
12518         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12519         // fall through to range comparison
12520       case 5: // material range
12521         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12522     }
12523     return TRUE;
12524 }
12525
12526 int
12527 QuickScan (Board board, Move *move)
12528 {   // reconstruct game,and compare all positions in it
12529     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12530     do {
12531         int piece = move->piece;
12532         int to = move->to, from = pieceList[piece];
12533         if(found < 0) { // if already found just scan to game end for final piece count
12534           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12535            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12536            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12537                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12538             ) {
12539             static int lastCounts[EmptySquare+1];
12540             int i;
12541             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12542             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12543           } else stretch = 0;
12544           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12545           if(found >= 0 && !appData.minPieces) return found;
12546         }
12547         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12548           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12549           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12550             piece = (++move)->piece;
12551             from = pieceList[piece];
12552             counts[pieceType[piece]]--;
12553             pieceType[piece] = (ChessSquare) move->to;
12554             counts[move->to]++;
12555           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12556             counts[pieceType[quickBoard[to]]]--;
12557             quickBoard[to] = 0; total--;
12558             move++;
12559             continue;
12560           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12561             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12562             from  = pieceList[piece]; // so this must be King
12563             quickBoard[from] = 0;
12564             pieceList[piece] = to;
12565             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12566             quickBoard[from] = 0; // rook
12567             quickBoard[to] = piece;
12568             to = move->to; piece = move->piece;
12569             goto aftercastle;
12570           }
12571         }
12572         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12573         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12574         quickBoard[from] = 0;
12575       aftercastle:
12576         quickBoard[to] = piece;
12577         pieceList[piece] = to;
12578         cnt++; turn ^= 3;
12579         move++;
12580     } while(1);
12581 }
12582
12583 void
12584 InitSearch ()
12585 {
12586     int r, f;
12587     flipSearch = FALSE;
12588     CopyBoard(soughtBoard, boards[currentMove]);
12589     soughtTotal = MakePieceList(soughtBoard, maxSought);
12590     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12591     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12592     CopyBoard(reverseBoard, boards[currentMove]);
12593     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12594         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12595         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12596         reverseBoard[r][f] = piece;
12597     }
12598     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12599     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12600     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12601                  || (boards[currentMove][CASTLING][2] == NoRights ||
12602                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12603                  && (boards[currentMove][CASTLING][5] == NoRights ||
12604                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12605       ) {
12606         flipSearch = TRUE;
12607         CopyBoard(flipBoard, soughtBoard);
12608         CopyBoard(rotateBoard, reverseBoard);
12609         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12610             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12611             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12612         }
12613     }
12614     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12615     if(appData.searchMode >= 5) {
12616         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12617         MakePieceList(soughtBoard, minSought);
12618         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12619     }
12620     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12621         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12622 }
12623
12624 GameInfo dummyInfo;
12625 static int creatingBook;
12626
12627 int
12628 GameContainsPosition (FILE *f, ListGame *lg)
12629 {
12630     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12631     int fromX, fromY, toX, toY;
12632     char promoChar;
12633     static int initDone=FALSE;
12634
12635     // weed out games based on numerical tag comparison
12636     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12637     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12638     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12639     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12640     if(!initDone) {
12641         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12642         initDone = TRUE;
12643     }
12644     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12645     else CopyBoard(boards[scratch], initialPosition); // default start position
12646     if(lg->moves) {
12647         turn = btm + 1;
12648         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12649         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12650     }
12651     if(btm) plyNr++;
12652     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12653     fseek(f, lg->offset, 0);
12654     yynewfile(f);
12655     while(1) {
12656         yyboardindex = scratch;
12657         quickFlag = plyNr+1;
12658         next = Myylex();
12659         quickFlag = 0;
12660         switch(next) {
12661             case PGNTag:
12662                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12663             default:
12664                 continue;
12665
12666             case XBoardGame:
12667             case GNUChessGame:
12668                 if(plyNr) return -1; // after we have seen moves, this is for new game
12669               continue;
12670
12671             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12672             case ImpossibleMove:
12673             case WhiteWins: // game ends here with these four
12674             case BlackWins:
12675             case GameIsDrawn:
12676             case GameUnfinished:
12677                 return -1;
12678
12679             case IllegalMove:
12680                 if(appData.testLegality) return -1;
12681             case WhiteCapturesEnPassant:
12682             case BlackCapturesEnPassant:
12683             case WhitePromotion:
12684             case BlackPromotion:
12685             case WhiteNonPromotion:
12686             case BlackNonPromotion:
12687             case NormalMove:
12688             case FirstLeg:
12689             case WhiteKingSideCastle:
12690             case WhiteQueenSideCastle:
12691             case BlackKingSideCastle:
12692             case BlackQueenSideCastle:
12693             case WhiteKingSideCastleWild:
12694             case WhiteQueenSideCastleWild:
12695             case BlackKingSideCastleWild:
12696             case BlackQueenSideCastleWild:
12697             case WhiteHSideCastleFR:
12698             case WhiteASideCastleFR:
12699             case BlackHSideCastleFR:
12700             case BlackASideCastleFR:
12701                 fromX = currentMoveString[0] - AAA;
12702                 fromY = currentMoveString[1] - ONE;
12703                 toX = currentMoveString[2] - AAA;
12704                 toY = currentMoveString[3] - ONE;
12705                 promoChar = currentMoveString[4];
12706                 break;
12707             case WhiteDrop:
12708             case BlackDrop:
12709                 fromX = next == WhiteDrop ?
12710                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12711                   (int) CharToPiece(ToLower(currentMoveString[0]));
12712                 fromY = DROP_RANK;
12713                 toX = currentMoveString[2] - AAA;
12714                 toY = currentMoveString[3] - ONE;
12715                 promoChar = 0;
12716                 break;
12717         }
12718         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12719         plyNr++;
12720         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12721         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12722         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12723         if(appData.findMirror) {
12724             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12725             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12726         }
12727     }
12728 }
12729
12730 /* Load the nth game from open file f */
12731 int
12732 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12733 {
12734     ChessMove cm;
12735     char buf[MSG_SIZ];
12736     int gn = gameNumber;
12737     ListGame *lg = NULL;
12738     int numPGNTags = 0;
12739     int err, pos = -1;
12740     GameMode oldGameMode;
12741     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12742     char oldName[MSG_SIZ];
12743
12744     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12745
12746     if (appData.debugMode)
12747         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12748
12749     if (gameMode == Training )
12750         SetTrainingModeOff();
12751
12752     oldGameMode = gameMode;
12753     if (gameMode != BeginningOfGame) {
12754       Reset(FALSE, TRUE);
12755     }
12756     killX = killY = -1; // [HGM] lion: in case we did not Reset
12757
12758     gameFileFP = f;
12759     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12760         fclose(lastLoadGameFP);
12761     }
12762
12763     if (useList) {
12764         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12765
12766         if (lg) {
12767             fseek(f, lg->offset, 0);
12768             GameListHighlight(gameNumber);
12769             pos = lg->position;
12770             gn = 1;
12771         }
12772         else {
12773             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12774               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12775             else
12776             DisplayError(_("Game number out of range"), 0);
12777             return FALSE;
12778         }
12779     } else {
12780         GameListDestroy();
12781         if (fseek(f, 0, 0) == -1) {
12782             if (f == lastLoadGameFP ?
12783                 gameNumber == lastLoadGameNumber + 1 :
12784                 gameNumber == 1) {
12785                 gn = 1;
12786             } else {
12787                 DisplayError(_("Can't seek on game file"), 0);
12788                 return FALSE;
12789             }
12790         }
12791     }
12792     lastLoadGameFP = f;
12793     lastLoadGameNumber = gameNumber;
12794     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12795     lastLoadGameUseList = useList;
12796
12797     yynewfile(f);
12798
12799     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12800       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12801                 lg->gameInfo.black);
12802             DisplayTitle(buf);
12803     } else if (*title != NULLCHAR) {
12804         if (gameNumber > 1) {
12805           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12806             DisplayTitle(buf);
12807         } else {
12808             DisplayTitle(title);
12809         }
12810     }
12811
12812     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12813         gameMode = PlayFromGameFile;
12814         ModeHighlight();
12815     }
12816
12817     currentMove = forwardMostMove = backwardMostMove = 0;
12818     CopyBoard(boards[0], initialPosition);
12819     StopClocks();
12820
12821     /*
12822      * Skip the first gn-1 games in the file.
12823      * Also skip over anything that precedes an identifiable
12824      * start of game marker, to avoid being confused by
12825      * garbage at the start of the file.  Currently
12826      * recognized start of game markers are the move number "1",
12827      * the pattern "gnuchess .* game", the pattern
12828      * "^[#;%] [^ ]* game file", and a PGN tag block.
12829      * A game that starts with one of the latter two patterns
12830      * will also have a move number 1, possibly
12831      * following a position diagram.
12832      * 5-4-02: Let's try being more lenient and allowing a game to
12833      * start with an unnumbered move.  Does that break anything?
12834      */
12835     cm = lastLoadGameStart = EndOfFile;
12836     while (gn > 0) {
12837         yyboardindex = forwardMostMove;
12838         cm = (ChessMove) Myylex();
12839         switch (cm) {
12840           case EndOfFile:
12841             if (cmailMsgLoaded) {
12842                 nCmailGames = CMAIL_MAX_GAMES - gn;
12843             } else {
12844                 Reset(TRUE, TRUE);
12845                 DisplayError(_("Game not found in file"), 0);
12846             }
12847             return FALSE;
12848
12849           case GNUChessGame:
12850           case XBoardGame:
12851             gn--;
12852             lastLoadGameStart = cm;
12853             break;
12854
12855           case MoveNumberOne:
12856             switch (lastLoadGameStart) {
12857               case GNUChessGame:
12858               case XBoardGame:
12859               case PGNTag:
12860                 break;
12861               case MoveNumberOne:
12862               case EndOfFile:
12863                 gn--;           /* count this game */
12864                 lastLoadGameStart = cm;
12865                 break;
12866               default:
12867                 /* impossible */
12868                 break;
12869             }
12870             break;
12871
12872           case PGNTag:
12873             switch (lastLoadGameStart) {
12874               case GNUChessGame:
12875               case PGNTag:
12876               case MoveNumberOne:
12877               case EndOfFile:
12878                 gn--;           /* count this game */
12879                 lastLoadGameStart = cm;
12880                 break;
12881               case XBoardGame:
12882                 lastLoadGameStart = cm; /* game counted already */
12883                 break;
12884               default:
12885                 /* impossible */
12886                 break;
12887             }
12888             if (gn > 0) {
12889                 do {
12890                     yyboardindex = forwardMostMove;
12891                     cm = (ChessMove) Myylex();
12892                 } while (cm == PGNTag || cm == Comment);
12893             }
12894             break;
12895
12896           case WhiteWins:
12897           case BlackWins:
12898           case GameIsDrawn:
12899             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12900                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12901                     != CMAIL_OLD_RESULT) {
12902                     nCmailResults ++ ;
12903                     cmailResult[  CMAIL_MAX_GAMES
12904                                 - gn - 1] = CMAIL_OLD_RESULT;
12905                 }
12906             }
12907             break;
12908
12909           case NormalMove:
12910           case FirstLeg:
12911             /* Only a NormalMove can be at the start of a game
12912              * without a position diagram. */
12913             if (lastLoadGameStart == EndOfFile ) {
12914               gn--;
12915               lastLoadGameStart = MoveNumberOne;
12916             }
12917             break;
12918
12919           default:
12920             break;
12921         }
12922     }
12923
12924     if (appData.debugMode)
12925       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12926
12927     if (cm == XBoardGame) {
12928         /* Skip any header junk before position diagram and/or move 1 */
12929         for (;;) {
12930             yyboardindex = forwardMostMove;
12931             cm = (ChessMove) Myylex();
12932
12933             if (cm == EndOfFile ||
12934                 cm == GNUChessGame || cm == XBoardGame) {
12935                 /* Empty game; pretend end-of-file and handle later */
12936                 cm = EndOfFile;
12937                 break;
12938             }
12939
12940             if (cm == MoveNumberOne || cm == PositionDiagram ||
12941                 cm == PGNTag || cm == Comment)
12942               break;
12943         }
12944     } else if (cm == GNUChessGame) {
12945         if (gameInfo.event != NULL) {
12946             free(gameInfo.event);
12947         }
12948         gameInfo.event = StrSave(yy_text);
12949     }
12950
12951     startedFromSetupPosition = FALSE;
12952     while (cm == PGNTag) {
12953         if (appData.debugMode)
12954           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12955         err = ParsePGNTag(yy_text, &gameInfo);
12956         if (!err) numPGNTags++;
12957
12958         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12959         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12960             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12961             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12962             InitPosition(TRUE);
12963             oldVariant = gameInfo.variant;
12964             if (appData.debugMode)
12965               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12966         }
12967
12968
12969         if (gameInfo.fen != NULL) {
12970           Board initial_position;
12971           startedFromSetupPosition = TRUE;
12972           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12973             Reset(TRUE, TRUE);
12974             DisplayError(_("Bad FEN position in file"), 0);
12975             return FALSE;
12976           }
12977           CopyBoard(boards[0], initial_position);
12978           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12979             CopyBoard(initialPosition, initial_position);
12980           if (blackPlaysFirst) {
12981             currentMove = forwardMostMove = backwardMostMove = 1;
12982             CopyBoard(boards[1], initial_position);
12983             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12984             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12985             timeRemaining[0][1] = whiteTimeRemaining;
12986             timeRemaining[1][1] = blackTimeRemaining;
12987             if (commentList[0] != NULL) {
12988               commentList[1] = commentList[0];
12989               commentList[0] = NULL;
12990             }
12991           } else {
12992             currentMove = forwardMostMove = backwardMostMove = 0;
12993           }
12994           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12995           {   int i;
12996               initialRulePlies = FENrulePlies;
12997               for( i=0; i< nrCastlingRights; i++ )
12998                   initialRights[i] = initial_position[CASTLING][i];
12999           }
13000           yyboardindex = forwardMostMove;
13001           free(gameInfo.fen);
13002           gameInfo.fen = NULL;
13003         }
13004
13005         yyboardindex = forwardMostMove;
13006         cm = (ChessMove) Myylex();
13007
13008         /* Handle comments interspersed among the tags */
13009         while (cm == Comment) {
13010             char *p;
13011             if (appData.debugMode)
13012               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13013             p = yy_text;
13014             AppendComment(currentMove, p, FALSE);
13015             yyboardindex = forwardMostMove;
13016             cm = (ChessMove) Myylex();
13017         }
13018     }
13019
13020     /* don't rely on existence of Event tag since if game was
13021      * pasted from clipboard the Event tag may not exist
13022      */
13023     if (numPGNTags > 0){
13024         char *tags;
13025         if (gameInfo.variant == VariantNormal) {
13026           VariantClass v = StringToVariant(gameInfo.event);
13027           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13028           if(v < VariantShogi) gameInfo.variant = v;
13029         }
13030         if (!matchMode) {
13031           if( appData.autoDisplayTags ) {
13032             tags = PGNTags(&gameInfo);
13033             TagsPopUp(tags, CmailMsg());
13034             free(tags);
13035           }
13036         }
13037     } else {
13038         /* Make something up, but don't display it now */
13039         SetGameInfo();
13040         TagsPopDown();
13041     }
13042
13043     if (cm == PositionDiagram) {
13044         int i, j;
13045         char *p;
13046         Board initial_position;
13047
13048         if (appData.debugMode)
13049           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13050
13051         if (!startedFromSetupPosition) {
13052             p = yy_text;
13053             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13054               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13055                 switch (*p) {
13056                   case '{':
13057                   case '[':
13058                   case '-':
13059                   case ' ':
13060                   case '\t':
13061                   case '\n':
13062                   case '\r':
13063                     break;
13064                   default:
13065                     initial_position[i][j++] = CharToPiece(*p);
13066                     break;
13067                 }
13068             while (*p == ' ' || *p == '\t' ||
13069                    *p == '\n' || *p == '\r') p++;
13070
13071             if (strncmp(p, "black", strlen("black"))==0)
13072               blackPlaysFirst = TRUE;
13073             else
13074               blackPlaysFirst = FALSE;
13075             startedFromSetupPosition = TRUE;
13076
13077             CopyBoard(boards[0], initial_position);
13078             if (blackPlaysFirst) {
13079                 currentMove = forwardMostMove = backwardMostMove = 1;
13080                 CopyBoard(boards[1], initial_position);
13081                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13082                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13083                 timeRemaining[0][1] = whiteTimeRemaining;
13084                 timeRemaining[1][1] = blackTimeRemaining;
13085                 if (commentList[0] != NULL) {
13086                     commentList[1] = commentList[0];
13087                     commentList[0] = NULL;
13088                 }
13089             } else {
13090                 currentMove = forwardMostMove = backwardMostMove = 0;
13091             }
13092         }
13093         yyboardindex = forwardMostMove;
13094         cm = (ChessMove) Myylex();
13095     }
13096
13097   if(!creatingBook) {
13098     if (first.pr == NoProc) {
13099         StartChessProgram(&first);
13100     }
13101     InitChessProgram(&first, FALSE);
13102     if(gameInfo.variant == VariantUnknown && *oldName) {
13103         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13104         gameInfo.variant = v;
13105     }
13106     SendToProgram("force\n", &first);
13107     if (startedFromSetupPosition) {
13108         SendBoard(&first, forwardMostMove);
13109     if (appData.debugMode) {
13110         fprintf(debugFP, "Load Game\n");
13111     }
13112         DisplayBothClocks();
13113     }
13114   }
13115
13116     /* [HGM] server: flag to write setup moves in broadcast file as one */
13117     loadFlag = appData.suppressLoadMoves;
13118
13119     while (cm == Comment) {
13120         char *p;
13121         if (appData.debugMode)
13122           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13123         p = yy_text;
13124         AppendComment(currentMove, p, FALSE);
13125         yyboardindex = forwardMostMove;
13126         cm = (ChessMove) Myylex();
13127     }
13128
13129     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13130         cm == WhiteWins || cm == BlackWins ||
13131         cm == GameIsDrawn || cm == GameUnfinished) {
13132         DisplayMessage("", _("No moves in game"));
13133         if (cmailMsgLoaded) {
13134             if (appData.debugMode)
13135               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13136             ClearHighlights();
13137             flipView = FALSE;
13138         }
13139         DrawPosition(FALSE, boards[currentMove]);
13140         DisplayBothClocks();
13141         gameMode = EditGame;
13142         ModeHighlight();
13143         gameFileFP = NULL;
13144         cmailOldMove = 0;
13145         return TRUE;
13146     }
13147
13148     // [HGM] PV info: routine tests if comment empty
13149     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13150         DisplayComment(currentMove - 1, commentList[currentMove]);
13151     }
13152     if (!matchMode && appData.timeDelay != 0)
13153       DrawPosition(FALSE, boards[currentMove]);
13154
13155     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13156       programStats.ok_to_send = 1;
13157     }
13158
13159     /* if the first token after the PGN tags is a move
13160      * and not move number 1, retrieve it from the parser
13161      */
13162     if (cm != MoveNumberOne)
13163         LoadGameOneMove(cm);
13164
13165     /* load the remaining moves from the file */
13166     while (LoadGameOneMove(EndOfFile)) {
13167       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13168       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13169     }
13170
13171     /* rewind to the start of the game */
13172     currentMove = backwardMostMove;
13173
13174     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13175
13176     if (oldGameMode == AnalyzeFile) {
13177       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13178       AnalyzeFileEvent();
13179     } else
13180     if (oldGameMode == AnalyzeMode) {
13181       AnalyzeFileEvent();
13182     }
13183
13184     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13185         long int w, b; // [HGM] adjourn: restore saved clock times
13186         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13187         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13188             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13189             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13190         }
13191     }
13192
13193     if(creatingBook) return TRUE;
13194     if (!matchMode && pos > 0) {
13195         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13196     } else
13197     if (matchMode || appData.timeDelay == 0) {
13198       ToEndEvent();
13199     } else if (appData.timeDelay > 0) {
13200       AutoPlayGameLoop();
13201     }
13202
13203     if (appData.debugMode)
13204         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13205
13206     loadFlag = 0; /* [HGM] true game starts */
13207     return TRUE;
13208 }
13209
13210 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13211 int
13212 ReloadPosition (int offset)
13213 {
13214     int positionNumber = lastLoadPositionNumber + offset;
13215     if (lastLoadPositionFP == NULL) {
13216         DisplayError(_("No position has been loaded yet"), 0);
13217         return FALSE;
13218     }
13219     if (positionNumber <= 0) {
13220         DisplayError(_("Can't back up any further"), 0);
13221         return FALSE;
13222     }
13223     return LoadPosition(lastLoadPositionFP, positionNumber,
13224                         lastLoadPositionTitle);
13225 }
13226
13227 /* Load the nth position from the given file */
13228 int
13229 LoadPositionFromFile (char *filename, int n, char *title)
13230 {
13231     FILE *f;
13232     char buf[MSG_SIZ];
13233
13234     if (strcmp(filename, "-") == 0) {
13235         return LoadPosition(stdin, n, "stdin");
13236     } else {
13237         f = fopen(filename, "rb");
13238         if (f == NULL) {
13239             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13240             DisplayError(buf, errno);
13241             return FALSE;
13242         } else {
13243             return LoadPosition(f, n, title);
13244         }
13245     }
13246 }
13247
13248 /* Load the nth position from the given open file, and close it */
13249 int
13250 LoadPosition (FILE *f, int positionNumber, char *title)
13251 {
13252     char *p, line[MSG_SIZ];
13253     Board initial_position;
13254     int i, j, fenMode, pn;
13255
13256     if (gameMode == Training )
13257         SetTrainingModeOff();
13258
13259     if (gameMode != BeginningOfGame) {
13260         Reset(FALSE, TRUE);
13261     }
13262     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13263         fclose(lastLoadPositionFP);
13264     }
13265     if (positionNumber == 0) positionNumber = 1;
13266     lastLoadPositionFP = f;
13267     lastLoadPositionNumber = positionNumber;
13268     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13269     if (first.pr == NoProc && !appData.noChessProgram) {
13270       StartChessProgram(&first);
13271       InitChessProgram(&first, FALSE);
13272     }
13273     pn = positionNumber;
13274     if (positionNumber < 0) {
13275         /* Negative position number means to seek to that byte offset */
13276         if (fseek(f, -positionNumber, 0) == -1) {
13277             DisplayError(_("Can't seek on position file"), 0);
13278             return FALSE;
13279         };
13280         pn = 1;
13281     } else {
13282         if (fseek(f, 0, 0) == -1) {
13283             if (f == lastLoadPositionFP ?
13284                 positionNumber == lastLoadPositionNumber + 1 :
13285                 positionNumber == 1) {
13286                 pn = 1;
13287             } else {
13288                 DisplayError(_("Can't seek on position file"), 0);
13289                 return FALSE;
13290             }
13291         }
13292     }
13293     /* See if this file is FEN or old-style xboard */
13294     if (fgets(line, MSG_SIZ, f) == NULL) {
13295         DisplayError(_("Position not found in file"), 0);
13296         return FALSE;
13297     }
13298     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13299     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13300
13301     if (pn >= 2) {
13302         if (fenMode || line[0] == '#') pn--;
13303         while (pn > 0) {
13304             /* skip positions before number pn */
13305             if (fgets(line, MSG_SIZ, f) == NULL) {
13306                 Reset(TRUE, TRUE);
13307                 DisplayError(_("Position not found in file"), 0);
13308                 return FALSE;
13309             }
13310             if (fenMode || line[0] == '#') pn--;
13311         }
13312     }
13313
13314     if (fenMode) {
13315         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13316             DisplayError(_("Bad FEN position in file"), 0);
13317             return FALSE;
13318         }
13319     } else {
13320         (void) fgets(line, MSG_SIZ, f);
13321         (void) fgets(line, MSG_SIZ, f);
13322
13323         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13324             (void) fgets(line, MSG_SIZ, f);
13325             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13326                 if (*p == ' ')
13327                   continue;
13328                 initial_position[i][j++] = CharToPiece(*p);
13329             }
13330         }
13331
13332         blackPlaysFirst = FALSE;
13333         if (!feof(f)) {
13334             (void) fgets(line, MSG_SIZ, f);
13335             if (strncmp(line, "black", strlen("black"))==0)
13336               blackPlaysFirst = TRUE;
13337         }
13338     }
13339     startedFromSetupPosition = TRUE;
13340
13341     CopyBoard(boards[0], initial_position);
13342     if (blackPlaysFirst) {
13343         currentMove = forwardMostMove = backwardMostMove = 1;
13344         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13345         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13346         CopyBoard(boards[1], initial_position);
13347         DisplayMessage("", _("Black to play"));
13348     } else {
13349         currentMove = forwardMostMove = backwardMostMove = 0;
13350         DisplayMessage("", _("White to play"));
13351     }
13352     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13353     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13354         SendToProgram("force\n", &first);
13355         SendBoard(&first, forwardMostMove);
13356     }
13357     if (appData.debugMode) {
13358 int i, j;
13359   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13360   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13361         fprintf(debugFP, "Load Position\n");
13362     }
13363
13364     if (positionNumber > 1) {
13365       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13366         DisplayTitle(line);
13367     } else {
13368         DisplayTitle(title);
13369     }
13370     gameMode = EditGame;
13371     ModeHighlight();
13372     ResetClocks();
13373     timeRemaining[0][1] = whiteTimeRemaining;
13374     timeRemaining[1][1] = blackTimeRemaining;
13375     DrawPosition(FALSE, boards[currentMove]);
13376
13377     return TRUE;
13378 }
13379
13380
13381 void
13382 CopyPlayerNameIntoFileName (char **dest, char *src)
13383 {
13384     while (*src != NULLCHAR && *src != ',') {
13385         if (*src == ' ') {
13386             *(*dest)++ = '_';
13387             src++;
13388         } else {
13389             *(*dest)++ = *src++;
13390         }
13391     }
13392 }
13393
13394 char *
13395 DefaultFileName (char *ext)
13396 {
13397     static char def[MSG_SIZ];
13398     char *p;
13399
13400     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13401         p = def;
13402         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13403         *p++ = '-';
13404         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13405         *p++ = '.';
13406         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13407     } else {
13408         def[0] = NULLCHAR;
13409     }
13410     return def;
13411 }
13412
13413 /* Save the current game to the given file */
13414 int
13415 SaveGameToFile (char *filename, int append)
13416 {
13417     FILE *f;
13418     char buf[MSG_SIZ];
13419     int result, i, t,tot=0;
13420
13421     if (strcmp(filename, "-") == 0) {
13422         return SaveGame(stdout, 0, NULL);
13423     } else {
13424         for(i=0; i<10; i++) { // upto 10 tries
13425              f = fopen(filename, append ? "a" : "w");
13426              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13427              if(f || errno != 13) break;
13428              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13429              tot += t;
13430         }
13431         if (f == NULL) {
13432             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13433             DisplayError(buf, errno);
13434             return FALSE;
13435         } else {
13436             safeStrCpy(buf, lastMsg, MSG_SIZ);
13437             DisplayMessage(_("Waiting for access to save file"), "");
13438             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13439             DisplayMessage(_("Saving game"), "");
13440             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13441             result = SaveGame(f, 0, NULL);
13442             DisplayMessage(buf, "");
13443             return result;
13444         }
13445     }
13446 }
13447
13448 char *
13449 SavePart (char *str)
13450 {
13451     static char buf[MSG_SIZ];
13452     char *p;
13453
13454     p = strchr(str, ' ');
13455     if (p == NULL) return str;
13456     strncpy(buf, str, p - str);
13457     buf[p - str] = NULLCHAR;
13458     return buf;
13459 }
13460
13461 #define PGN_MAX_LINE 75
13462
13463 #define PGN_SIDE_WHITE  0
13464 #define PGN_SIDE_BLACK  1
13465
13466 static int
13467 FindFirstMoveOutOfBook (int side)
13468 {
13469     int result = -1;
13470
13471     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13472         int index = backwardMostMove;
13473         int has_book_hit = 0;
13474
13475         if( (index % 2) != side ) {
13476             index++;
13477         }
13478
13479         while( index < forwardMostMove ) {
13480             /* Check to see if engine is in book */
13481             int depth = pvInfoList[index].depth;
13482             int score = pvInfoList[index].score;
13483             int in_book = 0;
13484
13485             if( depth <= 2 ) {
13486                 in_book = 1;
13487             }
13488             else if( score == 0 && depth == 63 ) {
13489                 in_book = 1; /* Zappa */
13490             }
13491             else if( score == 2 && depth == 99 ) {
13492                 in_book = 1; /* Abrok */
13493             }
13494
13495             has_book_hit += in_book;
13496
13497             if( ! in_book ) {
13498                 result = index;
13499
13500                 break;
13501             }
13502
13503             index += 2;
13504         }
13505     }
13506
13507     return result;
13508 }
13509
13510 void
13511 GetOutOfBookInfo (char * buf)
13512 {
13513     int oob[2];
13514     int i;
13515     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13516
13517     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13518     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13519
13520     *buf = '\0';
13521
13522     if( oob[0] >= 0 || oob[1] >= 0 ) {
13523         for( i=0; i<2; i++ ) {
13524             int idx = oob[i];
13525
13526             if( idx >= 0 ) {
13527                 if( i > 0 && oob[0] >= 0 ) {
13528                     strcat( buf, "   " );
13529                 }
13530
13531                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13532                 sprintf( buf+strlen(buf), "%s%.2f",
13533                     pvInfoList[idx].score >= 0 ? "+" : "",
13534                     pvInfoList[idx].score / 100.0 );
13535             }
13536         }
13537     }
13538 }
13539
13540 /* Save game in PGN style */
13541 static void
13542 SaveGamePGN2 (FILE *f)
13543 {
13544     int i, offset, linelen, newblock;
13545 //    char *movetext;
13546     char numtext[32];
13547     int movelen, numlen, blank;
13548     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13549
13550     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13551
13552     PrintPGNTags(f, &gameInfo);
13553
13554     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13555
13556     if (backwardMostMove > 0 || startedFromSetupPosition) {
13557         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13558         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13559         fprintf(f, "\n{--------------\n");
13560         PrintPosition(f, backwardMostMove);
13561         fprintf(f, "--------------}\n");
13562         free(fen);
13563     }
13564     else {
13565         /* [AS] Out of book annotation */
13566         if( appData.saveOutOfBookInfo ) {
13567             char buf[64];
13568
13569             GetOutOfBookInfo( buf );
13570
13571             if( buf[0] != '\0' ) {
13572                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13573             }
13574         }
13575
13576         fprintf(f, "\n");
13577     }
13578
13579     i = backwardMostMove;
13580     linelen = 0;
13581     newblock = TRUE;
13582
13583     while (i < forwardMostMove) {
13584         /* Print comments preceding this move */
13585         if (commentList[i] != NULL) {
13586             if (linelen > 0) fprintf(f, "\n");
13587             fprintf(f, "%s", commentList[i]);
13588             linelen = 0;
13589             newblock = TRUE;
13590         }
13591
13592         /* Format move number */
13593         if ((i % 2) == 0)
13594           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13595         else
13596           if (newblock)
13597             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13598           else
13599             numtext[0] = NULLCHAR;
13600
13601         numlen = strlen(numtext);
13602         newblock = FALSE;
13603
13604         /* Print move number */
13605         blank = linelen > 0 && numlen > 0;
13606         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13607             fprintf(f, "\n");
13608             linelen = 0;
13609             blank = 0;
13610         }
13611         if (blank) {
13612             fprintf(f, " ");
13613             linelen++;
13614         }
13615         fprintf(f, "%s", numtext);
13616         linelen += numlen;
13617
13618         /* Get move */
13619         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13620         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13621
13622         /* Print move */
13623         blank = linelen > 0 && movelen > 0;
13624         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13625             fprintf(f, "\n");
13626             linelen = 0;
13627             blank = 0;
13628         }
13629         if (blank) {
13630             fprintf(f, " ");
13631             linelen++;
13632         }
13633         fprintf(f, "%s", move_buffer);
13634         linelen += movelen;
13635
13636         /* [AS] Add PV info if present */
13637         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13638             /* [HGM] add time */
13639             char buf[MSG_SIZ]; int seconds;
13640
13641             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13642
13643             if( seconds <= 0)
13644               buf[0] = 0;
13645             else
13646               if( seconds < 30 )
13647                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13648               else
13649                 {
13650                   seconds = (seconds + 4)/10; // round to full seconds
13651                   if( seconds < 60 )
13652                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13653                   else
13654                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13655                 }
13656
13657             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13658                       pvInfoList[i].score >= 0 ? "+" : "",
13659                       pvInfoList[i].score / 100.0,
13660                       pvInfoList[i].depth,
13661                       buf );
13662
13663             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13664
13665             /* Print score/depth */
13666             blank = linelen > 0 && movelen > 0;
13667             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13668                 fprintf(f, "\n");
13669                 linelen = 0;
13670                 blank = 0;
13671             }
13672             if (blank) {
13673                 fprintf(f, " ");
13674                 linelen++;
13675             }
13676             fprintf(f, "%s", move_buffer);
13677             linelen += movelen;
13678         }
13679
13680         i++;
13681     }
13682
13683     /* Start a new line */
13684     if (linelen > 0) fprintf(f, "\n");
13685
13686     /* Print comments after last move */
13687     if (commentList[i] != NULL) {
13688         fprintf(f, "%s\n", commentList[i]);
13689     }
13690
13691     /* Print result */
13692     if (gameInfo.resultDetails != NULL &&
13693         gameInfo.resultDetails[0] != NULLCHAR) {
13694         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13695         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13696            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13697             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13698         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13699     } else {
13700         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13701     }
13702 }
13703
13704 /* Save game in PGN style and close the file */
13705 int
13706 SaveGamePGN (FILE *f)
13707 {
13708     SaveGamePGN2(f);
13709     fclose(f);
13710     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13711     return TRUE;
13712 }
13713
13714 /* Save game in old style and close the file */
13715 int
13716 SaveGameOldStyle (FILE *f)
13717 {
13718     int i, offset;
13719     time_t tm;
13720
13721     tm = time((time_t *) NULL);
13722
13723     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13724     PrintOpponents(f);
13725
13726     if (backwardMostMove > 0 || startedFromSetupPosition) {
13727         fprintf(f, "\n[--------------\n");
13728         PrintPosition(f, backwardMostMove);
13729         fprintf(f, "--------------]\n");
13730     } else {
13731         fprintf(f, "\n");
13732     }
13733
13734     i = backwardMostMove;
13735     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13736
13737     while (i < forwardMostMove) {
13738         if (commentList[i] != NULL) {
13739             fprintf(f, "[%s]\n", commentList[i]);
13740         }
13741
13742         if ((i % 2) == 1) {
13743             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13744             i++;
13745         } else {
13746             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13747             i++;
13748             if (commentList[i] != NULL) {
13749                 fprintf(f, "\n");
13750                 continue;
13751             }
13752             if (i >= forwardMostMove) {
13753                 fprintf(f, "\n");
13754                 break;
13755             }
13756             fprintf(f, "%s\n", parseList[i]);
13757             i++;
13758         }
13759     }
13760
13761     if (commentList[i] != NULL) {
13762         fprintf(f, "[%s]\n", commentList[i]);
13763     }
13764
13765     /* This isn't really the old style, but it's close enough */
13766     if (gameInfo.resultDetails != NULL &&
13767         gameInfo.resultDetails[0] != NULLCHAR) {
13768         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13769                 gameInfo.resultDetails);
13770     } else {
13771         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13772     }
13773
13774     fclose(f);
13775     return TRUE;
13776 }
13777
13778 /* Save the current game to open file f and close the file */
13779 int
13780 SaveGame (FILE *f, int dummy, char *dummy2)
13781 {
13782     if (gameMode == EditPosition) EditPositionDone(TRUE);
13783     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13784     if (appData.oldSaveStyle)
13785       return SaveGameOldStyle(f);
13786     else
13787       return SaveGamePGN(f);
13788 }
13789
13790 /* Save the current position to the given file */
13791 int
13792 SavePositionToFile (char *filename)
13793 {
13794     FILE *f;
13795     char buf[MSG_SIZ];
13796
13797     if (strcmp(filename, "-") == 0) {
13798         return SavePosition(stdout, 0, NULL);
13799     } else {
13800         f = fopen(filename, "a");
13801         if (f == NULL) {
13802             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13803             DisplayError(buf, errno);
13804             return FALSE;
13805         } else {
13806             safeStrCpy(buf, lastMsg, MSG_SIZ);
13807             DisplayMessage(_("Waiting for access to save file"), "");
13808             flock(fileno(f), LOCK_EX); // [HGM] lock
13809             DisplayMessage(_("Saving position"), "");
13810             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13811             SavePosition(f, 0, NULL);
13812             DisplayMessage(buf, "");
13813             return TRUE;
13814         }
13815     }
13816 }
13817
13818 /* Save the current position to the given open file and close the file */
13819 int
13820 SavePosition (FILE *f, int dummy, char *dummy2)
13821 {
13822     time_t tm;
13823     char *fen;
13824
13825     if (gameMode == EditPosition) EditPositionDone(TRUE);
13826     if (appData.oldSaveStyle) {
13827         tm = time((time_t *) NULL);
13828
13829         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13830         PrintOpponents(f);
13831         fprintf(f, "[--------------\n");
13832         PrintPosition(f, currentMove);
13833         fprintf(f, "--------------]\n");
13834     } else {
13835         fen = PositionToFEN(currentMove, NULL, 1);
13836         fprintf(f, "%s\n", fen);
13837         free(fen);
13838     }
13839     fclose(f);
13840     return TRUE;
13841 }
13842
13843 void
13844 ReloadCmailMsgEvent (int unregister)
13845 {
13846 #if !WIN32
13847     static char *inFilename = NULL;
13848     static char *outFilename;
13849     int i;
13850     struct stat inbuf, outbuf;
13851     int status;
13852
13853     /* Any registered moves are unregistered if unregister is set, */
13854     /* i.e. invoked by the signal handler */
13855     if (unregister) {
13856         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13857             cmailMoveRegistered[i] = FALSE;
13858             if (cmailCommentList[i] != NULL) {
13859                 free(cmailCommentList[i]);
13860                 cmailCommentList[i] = NULL;
13861             }
13862         }
13863         nCmailMovesRegistered = 0;
13864     }
13865
13866     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13867         cmailResult[i] = CMAIL_NOT_RESULT;
13868     }
13869     nCmailResults = 0;
13870
13871     if (inFilename == NULL) {
13872         /* Because the filenames are static they only get malloced once  */
13873         /* and they never get freed                                      */
13874         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13875         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13876
13877         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13878         sprintf(outFilename, "%s.out", appData.cmailGameName);
13879     }
13880
13881     status = stat(outFilename, &outbuf);
13882     if (status < 0) {
13883         cmailMailedMove = FALSE;
13884     } else {
13885         status = stat(inFilename, &inbuf);
13886         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13887     }
13888
13889     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13890        counts the games, notes how each one terminated, etc.
13891
13892        It would be nice to remove this kludge and instead gather all
13893        the information while building the game list.  (And to keep it
13894        in the game list nodes instead of having a bunch of fixed-size
13895        parallel arrays.)  Note this will require getting each game's
13896        termination from the PGN tags, as the game list builder does
13897        not process the game moves.  --mann
13898        */
13899     cmailMsgLoaded = TRUE;
13900     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13901
13902     /* Load first game in the file or popup game menu */
13903     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13904
13905 #endif /* !WIN32 */
13906     return;
13907 }
13908
13909 int
13910 RegisterMove ()
13911 {
13912     FILE *f;
13913     char string[MSG_SIZ];
13914
13915     if (   cmailMailedMove
13916         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13917         return TRUE;            /* Allow free viewing  */
13918     }
13919
13920     /* Unregister move to ensure that we don't leave RegisterMove        */
13921     /* with the move registered when the conditions for registering no   */
13922     /* longer hold                                                       */
13923     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13924         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13925         nCmailMovesRegistered --;
13926
13927         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13928           {
13929               free(cmailCommentList[lastLoadGameNumber - 1]);
13930               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13931           }
13932     }
13933
13934     if (cmailOldMove == -1) {
13935         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13936         return FALSE;
13937     }
13938
13939     if (currentMove > cmailOldMove + 1) {
13940         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13941         return FALSE;
13942     }
13943
13944     if (currentMove < cmailOldMove) {
13945         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13946         return FALSE;
13947     }
13948
13949     if (forwardMostMove > currentMove) {
13950         /* Silently truncate extra moves */
13951         TruncateGame();
13952     }
13953
13954     if (   (currentMove == cmailOldMove + 1)
13955         || (   (currentMove == cmailOldMove)
13956             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13957                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13958         if (gameInfo.result != GameUnfinished) {
13959             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13960         }
13961
13962         if (commentList[currentMove] != NULL) {
13963             cmailCommentList[lastLoadGameNumber - 1]
13964               = StrSave(commentList[currentMove]);
13965         }
13966         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13967
13968         if (appData.debugMode)
13969           fprintf(debugFP, "Saving %s for game %d\n",
13970                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13971
13972         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13973
13974         f = fopen(string, "w");
13975         if (appData.oldSaveStyle) {
13976             SaveGameOldStyle(f); /* also closes the file */
13977
13978             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13979             f = fopen(string, "w");
13980             SavePosition(f, 0, NULL); /* also closes the file */
13981         } else {
13982             fprintf(f, "{--------------\n");
13983             PrintPosition(f, currentMove);
13984             fprintf(f, "--------------}\n\n");
13985
13986             SaveGame(f, 0, NULL); /* also closes the file*/
13987         }
13988
13989         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13990         nCmailMovesRegistered ++;
13991     } else if (nCmailGames == 1) {
13992         DisplayError(_("You have not made a move yet"), 0);
13993         return FALSE;
13994     }
13995
13996     return TRUE;
13997 }
13998
13999 void
14000 MailMoveEvent ()
14001 {
14002 #if !WIN32
14003     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14004     FILE *commandOutput;
14005     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14006     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14007     int nBuffers;
14008     int i;
14009     int archived;
14010     char *arcDir;
14011
14012     if (! cmailMsgLoaded) {
14013         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14014         return;
14015     }
14016
14017     if (nCmailGames == nCmailResults) {
14018         DisplayError(_("No unfinished games"), 0);
14019         return;
14020     }
14021
14022 #if CMAIL_PROHIBIT_REMAIL
14023     if (cmailMailedMove) {
14024       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);
14025         DisplayError(msg, 0);
14026         return;
14027     }
14028 #endif
14029
14030     if (! (cmailMailedMove || RegisterMove())) return;
14031
14032     if (   cmailMailedMove
14033         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14034       snprintf(string, MSG_SIZ, partCommandString,
14035                appData.debugMode ? " -v" : "", appData.cmailGameName);
14036         commandOutput = popen(string, "r");
14037
14038         if (commandOutput == NULL) {
14039             DisplayError(_("Failed to invoke cmail"), 0);
14040         } else {
14041             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14042                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14043             }
14044             if (nBuffers > 1) {
14045                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14046                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14047                 nBytes = MSG_SIZ - 1;
14048             } else {
14049                 (void) memcpy(msg, buffer, nBytes);
14050             }
14051             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14052
14053             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14054                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14055
14056                 archived = TRUE;
14057                 for (i = 0; i < nCmailGames; i ++) {
14058                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14059                         archived = FALSE;
14060                     }
14061                 }
14062                 if (   archived
14063                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14064                         != NULL)) {
14065                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14066                            arcDir,
14067                            appData.cmailGameName,
14068                            gameInfo.date);
14069                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14070                     cmailMsgLoaded = FALSE;
14071                 }
14072             }
14073
14074             DisplayInformation(msg);
14075             pclose(commandOutput);
14076         }
14077     } else {
14078         if ((*cmailMsg) != '\0') {
14079             DisplayInformation(cmailMsg);
14080         }
14081     }
14082
14083     return;
14084 #endif /* !WIN32 */
14085 }
14086
14087 char *
14088 CmailMsg ()
14089 {
14090 #if WIN32
14091     return NULL;
14092 #else
14093     int  prependComma = 0;
14094     char number[5];
14095     char string[MSG_SIZ];       /* Space for game-list */
14096     int  i;
14097
14098     if (!cmailMsgLoaded) return "";
14099
14100     if (cmailMailedMove) {
14101       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14102     } else {
14103         /* Create a list of games left */
14104       snprintf(string, MSG_SIZ, "[");
14105         for (i = 0; i < nCmailGames; i ++) {
14106             if (! (   cmailMoveRegistered[i]
14107                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14108                 if (prependComma) {
14109                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14110                 } else {
14111                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14112                     prependComma = 1;
14113                 }
14114
14115                 strcat(string, number);
14116             }
14117         }
14118         strcat(string, "]");
14119
14120         if (nCmailMovesRegistered + nCmailResults == 0) {
14121             switch (nCmailGames) {
14122               case 1:
14123                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14124                 break;
14125
14126               case 2:
14127                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14128                 break;
14129
14130               default:
14131                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14132                          nCmailGames);
14133                 break;
14134             }
14135         } else {
14136             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14137               case 1:
14138                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14139                          string);
14140                 break;
14141
14142               case 0:
14143                 if (nCmailResults == nCmailGames) {
14144                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14145                 } else {
14146                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14147                 }
14148                 break;
14149
14150               default:
14151                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14152                          string);
14153             }
14154         }
14155     }
14156     return cmailMsg;
14157 #endif /* WIN32 */
14158 }
14159
14160 void
14161 ResetGameEvent ()
14162 {
14163     if (gameMode == Training)
14164       SetTrainingModeOff();
14165
14166     Reset(TRUE, TRUE);
14167     cmailMsgLoaded = FALSE;
14168     if (appData.icsActive) {
14169       SendToICS(ics_prefix);
14170       SendToICS("refresh\n");
14171     }
14172 }
14173
14174 void
14175 ExitEvent (int status)
14176 {
14177     exiting++;
14178     if (exiting > 2) {
14179       /* Give up on clean exit */
14180       exit(status);
14181     }
14182     if (exiting > 1) {
14183       /* Keep trying for clean exit */
14184       return;
14185     }
14186
14187     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14188     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14189
14190     if (telnetISR != NULL) {
14191       RemoveInputSource(telnetISR);
14192     }
14193     if (icsPR != NoProc) {
14194       DestroyChildProcess(icsPR, TRUE);
14195     }
14196
14197     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14198     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14199
14200     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14201     /* make sure this other one finishes before killing it!                  */
14202     if(endingGame) { int count = 0;
14203         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14204         while(endingGame && count++ < 10) DoSleep(1);
14205         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14206     }
14207
14208     /* Kill off chess programs */
14209     if (first.pr != NoProc) {
14210         ExitAnalyzeMode();
14211
14212         DoSleep( appData.delayBeforeQuit );
14213         SendToProgram("quit\n", &first);
14214         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14215     }
14216     if (second.pr != NoProc) {
14217         DoSleep( appData.delayBeforeQuit );
14218         SendToProgram("quit\n", &second);
14219         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14220     }
14221     if (first.isr != NULL) {
14222         RemoveInputSource(first.isr);
14223     }
14224     if (second.isr != NULL) {
14225         RemoveInputSource(second.isr);
14226     }
14227
14228     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14229     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14230
14231     ShutDownFrontEnd();
14232     exit(status);
14233 }
14234
14235 void
14236 PauseEngine (ChessProgramState *cps)
14237 {
14238     SendToProgram("pause\n", cps);
14239     cps->pause = 2;
14240 }
14241
14242 void
14243 UnPauseEngine (ChessProgramState *cps)
14244 {
14245     SendToProgram("resume\n", cps);
14246     cps->pause = 1;
14247 }
14248
14249 void
14250 PauseEvent ()
14251 {
14252     if (appData.debugMode)
14253         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14254     if (pausing) {
14255         pausing = FALSE;
14256         ModeHighlight();
14257         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14258             StartClocks();
14259             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14260                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14261                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14262             }
14263             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14264             HandleMachineMove(stashedInputMove, stalledEngine);
14265             stalledEngine = NULL;
14266             return;
14267         }
14268         if (gameMode == MachinePlaysWhite ||
14269             gameMode == TwoMachinesPlay   ||
14270             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14271             if(first.pause)  UnPauseEngine(&first);
14272             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14273             if(second.pause) UnPauseEngine(&second);
14274             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14275             StartClocks();
14276         } else {
14277             DisplayBothClocks();
14278         }
14279         if (gameMode == PlayFromGameFile) {
14280             if (appData.timeDelay >= 0)
14281                 AutoPlayGameLoop();
14282         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14283             Reset(FALSE, TRUE);
14284             SendToICS(ics_prefix);
14285             SendToICS("refresh\n");
14286         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14287             ForwardInner(forwardMostMove);
14288         }
14289         pauseExamInvalid = FALSE;
14290     } else {
14291         switch (gameMode) {
14292           default:
14293             return;
14294           case IcsExamining:
14295             pauseExamForwardMostMove = forwardMostMove;
14296             pauseExamInvalid = FALSE;
14297             /* fall through */
14298           case IcsObserving:
14299           case IcsPlayingWhite:
14300           case IcsPlayingBlack:
14301             pausing = TRUE;
14302             ModeHighlight();
14303             return;
14304           case PlayFromGameFile:
14305             (void) StopLoadGameTimer();
14306             pausing = TRUE;
14307             ModeHighlight();
14308             break;
14309           case BeginningOfGame:
14310             if (appData.icsActive) return;
14311             /* else fall through */
14312           case MachinePlaysWhite:
14313           case MachinePlaysBlack:
14314           case TwoMachinesPlay:
14315             if (forwardMostMove == 0)
14316               return;           /* don't pause if no one has moved */
14317             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14318                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14319                 if(onMove->pause) {           // thinking engine can be paused
14320                     PauseEngine(onMove);      // do it
14321                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14322                         PauseEngine(onMove->other);
14323                     else
14324                         SendToProgram("easy\n", onMove->other);
14325                     StopClocks();
14326                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14327             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14328                 if(first.pause) {
14329                     PauseEngine(&first);
14330                     StopClocks();
14331                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14332             } else { // human on move, pause pondering by either method
14333                 if(first.pause)
14334                     PauseEngine(&first);
14335                 else if(appData.ponderNextMove)
14336                     SendToProgram("easy\n", &first);
14337                 StopClocks();
14338             }
14339             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14340           case AnalyzeMode:
14341             pausing = TRUE;
14342             ModeHighlight();
14343             break;
14344         }
14345     }
14346 }
14347
14348 void
14349 EditCommentEvent ()
14350 {
14351     char title[MSG_SIZ];
14352
14353     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14354       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14355     } else {
14356       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14357                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14358                parseList[currentMove - 1]);
14359     }
14360
14361     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14362 }
14363
14364
14365 void
14366 EditTagsEvent ()
14367 {
14368     char *tags = PGNTags(&gameInfo);
14369     bookUp = FALSE;
14370     EditTagsPopUp(tags, NULL);
14371     free(tags);
14372 }
14373
14374 void
14375 ToggleSecond ()
14376 {
14377   if(second.analyzing) {
14378     SendToProgram("exit\n", &second);
14379     second.analyzing = FALSE;
14380   } else {
14381     if (second.pr == NoProc) StartChessProgram(&second);
14382     InitChessProgram(&second, FALSE);
14383     FeedMovesToProgram(&second, currentMove);
14384
14385     SendToProgram("analyze\n", &second);
14386     second.analyzing = TRUE;
14387   }
14388 }
14389
14390 /* Toggle ShowThinking */
14391 void
14392 ToggleShowThinking()
14393 {
14394   appData.showThinking = !appData.showThinking;
14395   ShowThinkingEvent();
14396 }
14397
14398 int
14399 AnalyzeModeEvent ()
14400 {
14401     char buf[MSG_SIZ];
14402
14403     if (!first.analysisSupport) {
14404       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14405       DisplayError(buf, 0);
14406       return 0;
14407     }
14408     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14409     if (appData.icsActive) {
14410         if (gameMode != IcsObserving) {
14411           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14412             DisplayError(buf, 0);
14413             /* secure check */
14414             if (appData.icsEngineAnalyze) {
14415                 if (appData.debugMode)
14416                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14417                 ExitAnalyzeMode();
14418                 ModeHighlight();
14419             }
14420             return 0;
14421         }
14422         /* if enable, user wants to disable icsEngineAnalyze */
14423         if (appData.icsEngineAnalyze) {
14424                 ExitAnalyzeMode();
14425                 ModeHighlight();
14426                 return 0;
14427         }
14428         appData.icsEngineAnalyze = TRUE;
14429         if (appData.debugMode)
14430             fprintf(debugFP, "ICS engine analyze starting... \n");
14431     }
14432
14433     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14434     if (appData.noChessProgram || gameMode == AnalyzeMode)
14435       return 0;
14436
14437     if (gameMode != AnalyzeFile) {
14438         if (!appData.icsEngineAnalyze) {
14439                EditGameEvent();
14440                if (gameMode != EditGame) return 0;
14441         }
14442         if (!appData.showThinking) ToggleShowThinking();
14443         ResurrectChessProgram();
14444         SendToProgram("analyze\n", &first);
14445         first.analyzing = TRUE;
14446         /*first.maybeThinking = TRUE;*/
14447         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14448         EngineOutputPopUp();
14449     }
14450     if (!appData.icsEngineAnalyze) {
14451         gameMode = AnalyzeMode;
14452         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14453     }
14454     pausing = FALSE;
14455     ModeHighlight();
14456     SetGameInfo();
14457
14458     StartAnalysisClock();
14459     GetTimeMark(&lastNodeCountTime);
14460     lastNodeCount = 0;
14461     return 1;
14462 }
14463
14464 void
14465 AnalyzeFileEvent ()
14466 {
14467     if (appData.noChessProgram || gameMode == AnalyzeFile)
14468       return;
14469
14470     if (!first.analysisSupport) {
14471       char buf[MSG_SIZ];
14472       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14473       DisplayError(buf, 0);
14474       return;
14475     }
14476
14477     if (gameMode != AnalyzeMode) {
14478         keepInfo = 1; // mere annotating should not alter PGN tags
14479         EditGameEvent();
14480         keepInfo = 0;
14481         if (gameMode != EditGame) return;
14482         if (!appData.showThinking) ToggleShowThinking();
14483         ResurrectChessProgram();
14484         SendToProgram("analyze\n", &first);
14485         first.analyzing = TRUE;
14486         /*first.maybeThinking = TRUE;*/
14487         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14488         EngineOutputPopUp();
14489     }
14490     gameMode = AnalyzeFile;
14491     pausing = FALSE;
14492     ModeHighlight();
14493
14494     StartAnalysisClock();
14495     GetTimeMark(&lastNodeCountTime);
14496     lastNodeCount = 0;
14497     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14498     AnalysisPeriodicEvent(1);
14499 }
14500
14501 void
14502 MachineWhiteEvent ()
14503 {
14504     char buf[MSG_SIZ];
14505     char *bookHit = NULL;
14506
14507     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14508       return;
14509
14510
14511     if (gameMode == PlayFromGameFile ||
14512         gameMode == TwoMachinesPlay  ||
14513         gameMode == Training         ||
14514         gameMode == AnalyzeMode      ||
14515         gameMode == EndOfGame)
14516         EditGameEvent();
14517
14518     if (gameMode == EditPosition)
14519         EditPositionDone(TRUE);
14520
14521     if (!WhiteOnMove(currentMove)) {
14522         DisplayError(_("It is not White's turn"), 0);
14523         return;
14524     }
14525
14526     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14527       ExitAnalyzeMode();
14528
14529     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14530         gameMode == AnalyzeFile)
14531         TruncateGame();
14532
14533     ResurrectChessProgram();    /* in case it isn't running */
14534     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14535         gameMode = MachinePlaysWhite;
14536         ResetClocks();
14537     } else
14538     gameMode = MachinePlaysWhite;
14539     pausing = FALSE;
14540     ModeHighlight();
14541     SetGameInfo();
14542     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14543     DisplayTitle(buf);
14544     if (first.sendName) {
14545       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14546       SendToProgram(buf, &first);
14547     }
14548     if (first.sendTime) {
14549       if (first.useColors) {
14550         SendToProgram("black\n", &first); /*gnu kludge*/
14551       }
14552       SendTimeRemaining(&first, TRUE);
14553     }
14554     if (first.useColors) {
14555       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14556     }
14557     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14558     SetMachineThinkingEnables();
14559     first.maybeThinking = TRUE;
14560     StartClocks();
14561     firstMove = FALSE;
14562
14563     if (appData.autoFlipView && !flipView) {
14564       flipView = !flipView;
14565       DrawPosition(FALSE, NULL);
14566       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14567     }
14568
14569     if(bookHit) { // [HGM] book: simulate book reply
14570         static char bookMove[MSG_SIZ]; // a bit generous?
14571
14572         programStats.nodes = programStats.depth = programStats.time =
14573         programStats.score = programStats.got_only_move = 0;
14574         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14575
14576         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14577         strcat(bookMove, bookHit);
14578         HandleMachineMove(bookMove, &first);
14579     }
14580 }
14581
14582 void
14583 MachineBlackEvent ()
14584 {
14585   char buf[MSG_SIZ];
14586   char *bookHit = NULL;
14587
14588     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14589         return;
14590
14591
14592     if (gameMode == PlayFromGameFile ||
14593         gameMode == TwoMachinesPlay  ||
14594         gameMode == Training         ||
14595         gameMode == AnalyzeMode      ||
14596         gameMode == EndOfGame)
14597         EditGameEvent();
14598
14599     if (gameMode == EditPosition)
14600         EditPositionDone(TRUE);
14601
14602     if (WhiteOnMove(currentMove)) {
14603         DisplayError(_("It is not Black's turn"), 0);
14604         return;
14605     }
14606
14607     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14608       ExitAnalyzeMode();
14609
14610     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14611         gameMode == AnalyzeFile)
14612         TruncateGame();
14613
14614     ResurrectChessProgram();    /* in case it isn't running */
14615     gameMode = MachinePlaysBlack;
14616     pausing = FALSE;
14617     ModeHighlight();
14618     SetGameInfo();
14619     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14620     DisplayTitle(buf);
14621     if (first.sendName) {
14622       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14623       SendToProgram(buf, &first);
14624     }
14625     if (first.sendTime) {
14626       if (first.useColors) {
14627         SendToProgram("white\n", &first); /*gnu kludge*/
14628       }
14629       SendTimeRemaining(&first, FALSE);
14630     }
14631     if (first.useColors) {
14632       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14633     }
14634     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14635     SetMachineThinkingEnables();
14636     first.maybeThinking = TRUE;
14637     StartClocks();
14638
14639     if (appData.autoFlipView && flipView) {
14640       flipView = !flipView;
14641       DrawPosition(FALSE, NULL);
14642       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14643     }
14644     if(bookHit) { // [HGM] book: simulate book reply
14645         static char bookMove[MSG_SIZ]; // a bit generous?
14646
14647         programStats.nodes = programStats.depth = programStats.time =
14648         programStats.score = programStats.got_only_move = 0;
14649         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14650
14651         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14652         strcat(bookMove, bookHit);
14653         HandleMachineMove(bookMove, &first);
14654     }
14655 }
14656
14657
14658 void
14659 DisplayTwoMachinesTitle ()
14660 {
14661     char buf[MSG_SIZ];
14662     if (appData.matchGames > 0) {
14663         if(appData.tourneyFile[0]) {
14664           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14665                    gameInfo.white, _("vs."), gameInfo.black,
14666                    nextGame+1, appData.matchGames+1,
14667                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14668         } else
14669         if (first.twoMachinesColor[0] == 'w') {
14670           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14671                    gameInfo.white, _("vs."),  gameInfo.black,
14672                    first.matchWins, second.matchWins,
14673                    matchGame - 1 - (first.matchWins + second.matchWins));
14674         } else {
14675           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14676                    gameInfo.white, _("vs."), gameInfo.black,
14677                    second.matchWins, first.matchWins,
14678                    matchGame - 1 - (first.matchWins + second.matchWins));
14679         }
14680     } else {
14681       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14682     }
14683     DisplayTitle(buf);
14684 }
14685
14686 void
14687 SettingsMenuIfReady ()
14688 {
14689   if (second.lastPing != second.lastPong) {
14690     DisplayMessage("", _("Waiting for second chess program"));
14691     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14692     return;
14693   }
14694   ThawUI();
14695   DisplayMessage("", "");
14696   SettingsPopUp(&second);
14697 }
14698
14699 int
14700 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14701 {
14702     char buf[MSG_SIZ];
14703     if (cps->pr == NoProc) {
14704         StartChessProgram(cps);
14705         if (cps->protocolVersion == 1) {
14706           retry();
14707           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14708         } else {
14709           /* kludge: allow timeout for initial "feature" command */
14710           if(retry != TwoMachinesEventIfReady) FreezeUI();
14711           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14712           DisplayMessage("", buf);
14713           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14714         }
14715         return 1;
14716     }
14717     return 0;
14718 }
14719
14720 void
14721 TwoMachinesEvent P((void))
14722 {
14723     int i;
14724     char buf[MSG_SIZ];
14725     ChessProgramState *onmove;
14726     char *bookHit = NULL;
14727     static int stalling = 0;
14728     TimeMark now;
14729     long wait;
14730
14731     if (appData.noChessProgram) return;
14732
14733     switch (gameMode) {
14734       case TwoMachinesPlay:
14735         return;
14736       case MachinePlaysWhite:
14737       case MachinePlaysBlack:
14738         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14739             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14740             return;
14741         }
14742         /* fall through */
14743       case BeginningOfGame:
14744       case PlayFromGameFile:
14745       case EndOfGame:
14746         EditGameEvent();
14747         if (gameMode != EditGame) return;
14748         break;
14749       case EditPosition:
14750         EditPositionDone(TRUE);
14751         break;
14752       case AnalyzeMode:
14753       case AnalyzeFile:
14754         ExitAnalyzeMode();
14755         break;
14756       case EditGame:
14757       default:
14758         break;
14759     }
14760
14761 //    forwardMostMove = currentMove;
14762     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14763     startingEngine = TRUE;
14764
14765     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14766
14767     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14768     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14769       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14770       return;
14771     }
14772     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14773
14774     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14775                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14776         startingEngine = matchMode = FALSE;
14777         DisplayError("second engine does not play this", 0);
14778         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14779         EditGameEvent(); // switch back to EditGame mode
14780         return;
14781     }
14782
14783     if(!stalling) {
14784       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14785       SendToProgram("force\n", &second);
14786       stalling = 1;
14787       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14788       return;
14789     }
14790     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14791     if(appData.matchPause>10000 || appData.matchPause<10)
14792                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14793     wait = SubtractTimeMarks(&now, &pauseStart);
14794     if(wait < appData.matchPause) {
14795         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14796         return;
14797     }
14798     // we are now committed to starting the game
14799     stalling = 0;
14800     DisplayMessage("", "");
14801     if (startedFromSetupPosition) {
14802         SendBoard(&second, backwardMostMove);
14803     if (appData.debugMode) {
14804         fprintf(debugFP, "Two Machines\n");
14805     }
14806     }
14807     for (i = backwardMostMove; i < forwardMostMove; i++) {
14808         SendMoveToProgram(i, &second);
14809     }
14810
14811     gameMode = TwoMachinesPlay;
14812     pausing = startingEngine = FALSE;
14813     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14814     SetGameInfo();
14815     DisplayTwoMachinesTitle();
14816     firstMove = TRUE;
14817     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14818         onmove = &first;
14819     } else {
14820         onmove = &second;
14821     }
14822     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14823     SendToProgram(first.computerString, &first);
14824     if (first.sendName) {
14825       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14826       SendToProgram(buf, &first);
14827     }
14828     SendToProgram(second.computerString, &second);
14829     if (second.sendName) {
14830       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14831       SendToProgram(buf, &second);
14832     }
14833
14834     ResetClocks();
14835     if (!first.sendTime || !second.sendTime) {
14836         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14837         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14838     }
14839     if (onmove->sendTime) {
14840       if (onmove->useColors) {
14841         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14842       }
14843       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14844     }
14845     if (onmove->useColors) {
14846       SendToProgram(onmove->twoMachinesColor, onmove);
14847     }
14848     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14849 //    SendToProgram("go\n", onmove);
14850     onmove->maybeThinking = TRUE;
14851     SetMachineThinkingEnables();
14852
14853     StartClocks();
14854
14855     if(bookHit) { // [HGM] book: simulate book reply
14856         static char bookMove[MSG_SIZ]; // a bit generous?
14857
14858         programStats.nodes = programStats.depth = programStats.time =
14859         programStats.score = programStats.got_only_move = 0;
14860         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14861
14862         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14863         strcat(bookMove, bookHit);
14864         savedMessage = bookMove; // args for deferred call
14865         savedState = onmove;
14866         ScheduleDelayedEvent(DeferredBookMove, 1);
14867     }
14868 }
14869
14870 void
14871 TrainingEvent ()
14872 {
14873     if (gameMode == Training) {
14874       SetTrainingModeOff();
14875       gameMode = PlayFromGameFile;
14876       DisplayMessage("", _("Training mode off"));
14877     } else {
14878       gameMode = Training;
14879       animateTraining = appData.animate;
14880
14881       /* make sure we are not already at the end of the game */
14882       if (currentMove < forwardMostMove) {
14883         SetTrainingModeOn();
14884         DisplayMessage("", _("Training mode on"));
14885       } else {
14886         gameMode = PlayFromGameFile;
14887         DisplayError(_("Already at end of game"), 0);
14888       }
14889     }
14890     ModeHighlight();
14891 }
14892
14893 void
14894 IcsClientEvent ()
14895 {
14896     if (!appData.icsActive) return;
14897     switch (gameMode) {
14898       case IcsPlayingWhite:
14899       case IcsPlayingBlack:
14900       case IcsObserving:
14901       case IcsIdle:
14902       case BeginningOfGame:
14903       case IcsExamining:
14904         return;
14905
14906       case EditGame:
14907         break;
14908
14909       case EditPosition:
14910         EditPositionDone(TRUE);
14911         break;
14912
14913       case AnalyzeMode:
14914       case AnalyzeFile:
14915         ExitAnalyzeMode();
14916         break;
14917
14918       default:
14919         EditGameEvent();
14920         break;
14921     }
14922
14923     gameMode = IcsIdle;
14924     ModeHighlight();
14925     return;
14926 }
14927
14928 void
14929 EditGameEvent ()
14930 {
14931     int i;
14932
14933     switch (gameMode) {
14934       case Training:
14935         SetTrainingModeOff();
14936         break;
14937       case MachinePlaysWhite:
14938       case MachinePlaysBlack:
14939       case BeginningOfGame:
14940         SendToProgram("force\n", &first);
14941         SetUserThinkingEnables();
14942         break;
14943       case PlayFromGameFile:
14944         (void) StopLoadGameTimer();
14945         if (gameFileFP != NULL) {
14946             gameFileFP = NULL;
14947         }
14948         break;
14949       case EditPosition:
14950         EditPositionDone(TRUE);
14951         break;
14952       case AnalyzeMode:
14953       case AnalyzeFile:
14954         ExitAnalyzeMode();
14955         SendToProgram("force\n", &first);
14956         break;
14957       case TwoMachinesPlay:
14958         GameEnds(EndOfFile, NULL, GE_PLAYER);
14959         ResurrectChessProgram();
14960         SetUserThinkingEnables();
14961         break;
14962       case EndOfGame:
14963         ResurrectChessProgram();
14964         break;
14965       case IcsPlayingBlack:
14966       case IcsPlayingWhite:
14967         DisplayError(_("Warning: You are still playing a game"), 0);
14968         break;
14969       case IcsObserving:
14970         DisplayError(_("Warning: You are still observing a game"), 0);
14971         break;
14972       case IcsExamining:
14973         DisplayError(_("Warning: You are still examining a game"), 0);
14974         break;
14975       case IcsIdle:
14976         break;
14977       case EditGame:
14978       default:
14979         return;
14980     }
14981
14982     pausing = FALSE;
14983     StopClocks();
14984     first.offeredDraw = second.offeredDraw = 0;
14985
14986     if (gameMode == PlayFromGameFile) {
14987         whiteTimeRemaining = timeRemaining[0][currentMove];
14988         blackTimeRemaining = timeRemaining[1][currentMove];
14989         DisplayTitle("");
14990     }
14991
14992     if (gameMode == MachinePlaysWhite ||
14993         gameMode == MachinePlaysBlack ||
14994         gameMode == TwoMachinesPlay ||
14995         gameMode == EndOfGame) {
14996         i = forwardMostMove;
14997         while (i > currentMove) {
14998             SendToProgram("undo\n", &first);
14999             i--;
15000         }
15001         if(!adjustedClock) {
15002         whiteTimeRemaining = timeRemaining[0][currentMove];
15003         blackTimeRemaining = timeRemaining[1][currentMove];
15004         DisplayBothClocks();
15005         }
15006         if (whiteFlag || blackFlag) {
15007             whiteFlag = blackFlag = 0;
15008         }
15009         DisplayTitle("");
15010     }
15011
15012     gameMode = EditGame;
15013     ModeHighlight();
15014     SetGameInfo();
15015 }
15016
15017
15018 void
15019 EditPositionEvent ()
15020 {
15021     if (gameMode == EditPosition) {
15022         EditGameEvent();
15023         return;
15024     }
15025
15026     EditGameEvent();
15027     if (gameMode != EditGame) return;
15028
15029     gameMode = EditPosition;
15030     ModeHighlight();
15031     SetGameInfo();
15032     if (currentMove > 0)
15033       CopyBoard(boards[0], boards[currentMove]);
15034
15035     blackPlaysFirst = !WhiteOnMove(currentMove);
15036     ResetClocks();
15037     currentMove = forwardMostMove = backwardMostMove = 0;
15038     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15039     DisplayMove(-1);
15040     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15041 }
15042
15043 void
15044 ExitAnalyzeMode ()
15045 {
15046     /* [DM] icsEngineAnalyze - possible call from other functions */
15047     if (appData.icsEngineAnalyze) {
15048         appData.icsEngineAnalyze = FALSE;
15049
15050         DisplayMessage("",_("Close ICS engine analyze..."));
15051     }
15052     if (first.analysisSupport && first.analyzing) {
15053       SendToBoth("exit\n");
15054       first.analyzing = second.analyzing = FALSE;
15055     }
15056     thinkOutput[0] = NULLCHAR;
15057 }
15058
15059 void
15060 EditPositionDone (Boolean fakeRights)
15061 {
15062     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15063
15064     startedFromSetupPosition = TRUE;
15065     InitChessProgram(&first, FALSE);
15066     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15067       boards[0][EP_STATUS] = EP_NONE;
15068       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15069       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15070         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15071         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15072       } else boards[0][CASTLING][2] = NoRights;
15073       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15074         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15075         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15076       } else boards[0][CASTLING][5] = NoRights;
15077       if(gameInfo.variant == VariantSChess) {
15078         int i;
15079         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15080           boards[0][VIRGIN][i] = 0;
15081           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15082           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15083         }
15084       }
15085     }
15086     SendToProgram("force\n", &first);
15087     if (blackPlaysFirst) {
15088         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15089         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15090         currentMove = forwardMostMove = backwardMostMove = 1;
15091         CopyBoard(boards[1], boards[0]);
15092     } else {
15093         currentMove = forwardMostMove = backwardMostMove = 0;
15094     }
15095     SendBoard(&first, forwardMostMove);
15096     if (appData.debugMode) {
15097         fprintf(debugFP, "EditPosDone\n");
15098     }
15099     DisplayTitle("");
15100     DisplayMessage("", "");
15101     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15102     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15103     gameMode = EditGame;
15104     ModeHighlight();
15105     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15106     ClearHighlights(); /* [AS] */
15107 }
15108
15109 /* Pause for `ms' milliseconds */
15110 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15111 void
15112 TimeDelay (long ms)
15113 {
15114     TimeMark m1, m2;
15115
15116     GetTimeMark(&m1);
15117     do {
15118         GetTimeMark(&m2);
15119     } while (SubtractTimeMarks(&m2, &m1) < ms);
15120 }
15121
15122 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15123 void
15124 SendMultiLineToICS (char *buf)
15125 {
15126     char temp[MSG_SIZ+1], *p;
15127     int len;
15128
15129     len = strlen(buf);
15130     if (len > MSG_SIZ)
15131       len = MSG_SIZ;
15132
15133     strncpy(temp, buf, len);
15134     temp[len] = 0;
15135
15136     p = temp;
15137     while (*p) {
15138         if (*p == '\n' || *p == '\r')
15139           *p = ' ';
15140         ++p;
15141     }
15142
15143     strcat(temp, "\n");
15144     SendToICS(temp);
15145     SendToPlayer(temp, strlen(temp));
15146 }
15147
15148 void
15149 SetWhiteToPlayEvent ()
15150 {
15151     if (gameMode == EditPosition) {
15152         blackPlaysFirst = FALSE;
15153         DisplayBothClocks();    /* works because currentMove is 0 */
15154     } else if (gameMode == IcsExamining) {
15155         SendToICS(ics_prefix);
15156         SendToICS("tomove white\n");
15157     }
15158 }
15159
15160 void
15161 SetBlackToPlayEvent ()
15162 {
15163     if (gameMode == EditPosition) {
15164         blackPlaysFirst = TRUE;
15165         currentMove = 1;        /* kludge */
15166         DisplayBothClocks();
15167         currentMove = 0;
15168     } else if (gameMode == IcsExamining) {
15169         SendToICS(ics_prefix);
15170         SendToICS("tomove black\n");
15171     }
15172 }
15173
15174 void
15175 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15176 {
15177     char buf[MSG_SIZ];
15178     ChessSquare piece = boards[0][y][x];
15179     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15180     static int lastVariant;
15181
15182     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15183
15184     switch (selection) {
15185       case ClearBoard:
15186         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15187         MarkTargetSquares(1);
15188         CopyBoard(currentBoard, boards[0]);
15189         CopyBoard(menuBoard, initialPosition);
15190         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15191             SendToICS(ics_prefix);
15192             SendToICS("bsetup clear\n");
15193         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15194             SendToICS(ics_prefix);
15195             SendToICS("clearboard\n");
15196         } else {
15197             int nonEmpty = 0;
15198             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15199                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15200                 for (y = 0; y < BOARD_HEIGHT; y++) {
15201                     if (gameMode == IcsExamining) {
15202                         if (boards[currentMove][y][x] != EmptySquare) {
15203                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15204                                     AAA + x, ONE + y);
15205                             SendToICS(buf);
15206                         }
15207                     } else if(boards[0][y][x] != DarkSquare) {
15208                         if(boards[0][y][x] != p) nonEmpty++;
15209                         boards[0][y][x] = p;
15210                     }
15211                 }
15212             }
15213             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15214                 int r;
15215                 for(r = 0; r < BOARD_HEIGHT; r++) {
15216                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15217                     ChessSquare p = menuBoard[r][x];
15218                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15219                   }
15220                 }
15221                 DisplayMessage("Clicking clock again restores position", "");
15222                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15223                 if(!nonEmpty) { // asked to clear an empty board
15224                     CopyBoard(boards[0], menuBoard);
15225                 } else
15226                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15227                     CopyBoard(boards[0], initialPosition);
15228                 } else
15229                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15230                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15231                     CopyBoard(boards[0], erasedBoard);
15232                 } else
15233                     CopyBoard(erasedBoard, currentBoard);
15234
15235             }
15236         }
15237         if (gameMode == EditPosition) {
15238             DrawPosition(FALSE, boards[0]);
15239         }
15240         break;
15241
15242       case WhitePlay:
15243         SetWhiteToPlayEvent();
15244         break;
15245
15246       case BlackPlay:
15247         SetBlackToPlayEvent();
15248         break;
15249
15250       case EmptySquare:
15251         if (gameMode == IcsExamining) {
15252             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15253             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15254             SendToICS(buf);
15255         } else {
15256             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15257                 if(x == BOARD_LEFT-2) {
15258                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15259                     boards[0][y][1] = 0;
15260                 } else
15261                 if(x == BOARD_RGHT+1) {
15262                     if(y >= gameInfo.holdingsSize) break;
15263                     boards[0][y][BOARD_WIDTH-2] = 0;
15264                 } else break;
15265             }
15266             boards[0][y][x] = EmptySquare;
15267             DrawPosition(FALSE, boards[0]);
15268         }
15269         break;
15270
15271       case PromotePiece:
15272         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15273            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15274             selection = (ChessSquare) (PROMOTED piece);
15275         } else if(piece == EmptySquare) selection = WhiteSilver;
15276         else selection = (ChessSquare)((int)piece - 1);
15277         goto defaultlabel;
15278
15279       case DemotePiece:
15280         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15281            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15282             selection = (ChessSquare) (DEMOTED piece);
15283         } else if(piece == EmptySquare) selection = BlackSilver;
15284         else selection = (ChessSquare)((int)piece + 1);
15285         goto defaultlabel;
15286
15287       case WhiteQueen:
15288       case BlackQueen:
15289         if(gameInfo.variant == VariantShatranj ||
15290            gameInfo.variant == VariantXiangqi  ||
15291            gameInfo.variant == VariantCourier  ||
15292            gameInfo.variant == VariantASEAN    ||
15293            gameInfo.variant == VariantMakruk     )
15294             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15295         goto defaultlabel;
15296
15297       case WhiteKing:
15298       case BlackKing:
15299         if(gameInfo.variant == VariantXiangqi)
15300             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15301         if(gameInfo.variant == VariantKnightmate)
15302             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15303       default:
15304         defaultlabel:
15305         if (gameMode == IcsExamining) {
15306             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15307             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15308                      PieceToChar(selection), AAA + x, ONE + y);
15309             SendToICS(buf);
15310         } else {
15311             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15312                 int n;
15313                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15314                     n = PieceToNumber(selection - BlackPawn);
15315                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15316                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15317                     boards[0][BOARD_HEIGHT-1-n][1]++;
15318                 } else
15319                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15320                     n = PieceToNumber(selection);
15321                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15322                     boards[0][n][BOARD_WIDTH-1] = selection;
15323                     boards[0][n][BOARD_WIDTH-2]++;
15324                 }
15325             } else
15326             boards[0][y][x] = selection;
15327             DrawPosition(TRUE, boards[0]);
15328             ClearHighlights();
15329             fromX = fromY = -1;
15330         }
15331         break;
15332     }
15333 }
15334
15335
15336 void
15337 DropMenuEvent (ChessSquare selection, int x, int y)
15338 {
15339     ChessMove moveType;
15340
15341     switch (gameMode) {
15342       case IcsPlayingWhite:
15343       case MachinePlaysBlack:
15344         if (!WhiteOnMove(currentMove)) {
15345             DisplayMoveError(_("It is Black's turn"));
15346             return;
15347         }
15348         moveType = WhiteDrop;
15349         break;
15350       case IcsPlayingBlack:
15351       case MachinePlaysWhite:
15352         if (WhiteOnMove(currentMove)) {
15353             DisplayMoveError(_("It is White's turn"));
15354             return;
15355         }
15356         moveType = BlackDrop;
15357         break;
15358       case EditGame:
15359         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15360         break;
15361       default:
15362         return;
15363     }
15364
15365     if (moveType == BlackDrop && selection < BlackPawn) {
15366       selection = (ChessSquare) ((int) selection
15367                                  + (int) BlackPawn - (int) WhitePawn);
15368     }
15369     if (boards[currentMove][y][x] != EmptySquare) {
15370         DisplayMoveError(_("That square is occupied"));
15371         return;
15372     }
15373
15374     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15375 }
15376
15377 void
15378 AcceptEvent ()
15379 {
15380     /* Accept a pending offer of any kind from opponent */
15381
15382     if (appData.icsActive) {
15383         SendToICS(ics_prefix);
15384         SendToICS("accept\n");
15385     } else if (cmailMsgLoaded) {
15386         if (currentMove == cmailOldMove &&
15387             commentList[cmailOldMove] != NULL &&
15388             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15389                    "Black offers a draw" : "White offers a draw")) {
15390             TruncateGame();
15391             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15392             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15393         } else {
15394             DisplayError(_("There is no pending offer on this move"), 0);
15395             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15396         }
15397     } else {
15398         /* Not used for offers from chess program */
15399     }
15400 }
15401
15402 void
15403 DeclineEvent ()
15404 {
15405     /* Decline a pending offer of any kind from opponent */
15406
15407     if (appData.icsActive) {
15408         SendToICS(ics_prefix);
15409         SendToICS("decline\n");
15410     } else if (cmailMsgLoaded) {
15411         if (currentMove == cmailOldMove &&
15412             commentList[cmailOldMove] != NULL &&
15413             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15414                    "Black offers a draw" : "White offers a draw")) {
15415 #ifdef NOTDEF
15416             AppendComment(cmailOldMove, "Draw declined", TRUE);
15417             DisplayComment(cmailOldMove - 1, "Draw declined");
15418 #endif /*NOTDEF*/
15419         } else {
15420             DisplayError(_("There is no pending offer on this move"), 0);
15421         }
15422     } else {
15423         /* Not used for offers from chess program */
15424     }
15425 }
15426
15427 void
15428 RematchEvent ()
15429 {
15430     /* Issue ICS rematch command */
15431     if (appData.icsActive) {
15432         SendToICS(ics_prefix);
15433         SendToICS("rematch\n");
15434     }
15435 }
15436
15437 void
15438 CallFlagEvent ()
15439 {
15440     /* Call your opponent's flag (claim a win on time) */
15441     if (appData.icsActive) {
15442         SendToICS(ics_prefix);
15443         SendToICS("flag\n");
15444     } else {
15445         switch (gameMode) {
15446           default:
15447             return;
15448           case MachinePlaysWhite:
15449             if (whiteFlag) {
15450                 if (blackFlag)
15451                   GameEnds(GameIsDrawn, "Both players ran out of time",
15452                            GE_PLAYER);
15453                 else
15454                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15455             } else {
15456                 DisplayError(_("Your opponent is not out of time"), 0);
15457             }
15458             break;
15459           case MachinePlaysBlack:
15460             if (blackFlag) {
15461                 if (whiteFlag)
15462                   GameEnds(GameIsDrawn, "Both players ran out of time",
15463                            GE_PLAYER);
15464                 else
15465                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15466             } else {
15467                 DisplayError(_("Your opponent is not out of time"), 0);
15468             }
15469             break;
15470         }
15471     }
15472 }
15473
15474 void
15475 ClockClick (int which)
15476 {       // [HGM] code moved to back-end from winboard.c
15477         if(which) { // black clock
15478           if (gameMode == EditPosition || gameMode == IcsExamining) {
15479             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15480             SetBlackToPlayEvent();
15481           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15482                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15483           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15484           } else if (shiftKey) {
15485             AdjustClock(which, -1);
15486           } else if (gameMode == IcsPlayingWhite ||
15487                      gameMode == MachinePlaysBlack) {
15488             CallFlagEvent();
15489           }
15490         } else { // white clock
15491           if (gameMode == EditPosition || gameMode == IcsExamining) {
15492             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15493             SetWhiteToPlayEvent();
15494           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15495                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15496           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15497           } else if (shiftKey) {
15498             AdjustClock(which, -1);
15499           } else if (gameMode == IcsPlayingBlack ||
15500                    gameMode == MachinePlaysWhite) {
15501             CallFlagEvent();
15502           }
15503         }
15504 }
15505
15506 void
15507 DrawEvent ()
15508 {
15509     /* Offer draw or accept pending draw offer from opponent */
15510
15511     if (appData.icsActive) {
15512         /* Note: tournament rules require draw offers to be
15513            made after you make your move but before you punch
15514            your clock.  Currently ICS doesn't let you do that;
15515            instead, you immediately punch your clock after making
15516            a move, but you can offer a draw at any time. */
15517
15518         SendToICS(ics_prefix);
15519         SendToICS("draw\n");
15520         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15521     } else if (cmailMsgLoaded) {
15522         if (currentMove == cmailOldMove &&
15523             commentList[cmailOldMove] != NULL &&
15524             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15525                    "Black offers a draw" : "White offers a draw")) {
15526             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15527             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15528         } else if (currentMove == cmailOldMove + 1) {
15529             char *offer = WhiteOnMove(cmailOldMove) ?
15530               "White offers a draw" : "Black offers a draw";
15531             AppendComment(currentMove, offer, TRUE);
15532             DisplayComment(currentMove - 1, offer);
15533             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15534         } else {
15535             DisplayError(_("You must make your move before offering a draw"), 0);
15536             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15537         }
15538     } else if (first.offeredDraw) {
15539         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15540     } else {
15541         if (first.sendDrawOffers) {
15542             SendToProgram("draw\n", &first);
15543             userOfferedDraw = TRUE;
15544         }
15545     }
15546 }
15547
15548 void
15549 AdjournEvent ()
15550 {
15551     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15552
15553     if (appData.icsActive) {
15554         SendToICS(ics_prefix);
15555         SendToICS("adjourn\n");
15556     } else {
15557         /* Currently GNU Chess doesn't offer or accept Adjourns */
15558     }
15559 }
15560
15561
15562 void
15563 AbortEvent ()
15564 {
15565     /* Offer Abort or accept pending Abort offer from opponent */
15566
15567     if (appData.icsActive) {
15568         SendToICS(ics_prefix);
15569         SendToICS("abort\n");
15570     } else {
15571         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15572     }
15573 }
15574
15575 void
15576 ResignEvent ()
15577 {
15578     /* Resign.  You can do this even if it's not your turn. */
15579
15580     if (appData.icsActive) {
15581         SendToICS(ics_prefix);
15582         SendToICS("resign\n");
15583     } else {
15584         switch (gameMode) {
15585           case MachinePlaysWhite:
15586             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15587             break;
15588           case MachinePlaysBlack:
15589             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15590             break;
15591           case EditGame:
15592             if (cmailMsgLoaded) {
15593                 TruncateGame();
15594                 if (WhiteOnMove(cmailOldMove)) {
15595                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15596                 } else {
15597                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15598                 }
15599                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15600             }
15601             break;
15602           default:
15603             break;
15604         }
15605     }
15606 }
15607
15608
15609 void
15610 StopObservingEvent ()
15611 {
15612     /* Stop observing current games */
15613     SendToICS(ics_prefix);
15614     SendToICS("unobserve\n");
15615 }
15616
15617 void
15618 StopExaminingEvent ()
15619 {
15620     /* Stop observing current game */
15621     SendToICS(ics_prefix);
15622     SendToICS("unexamine\n");
15623 }
15624
15625 void
15626 ForwardInner (int target)
15627 {
15628     int limit; int oldSeekGraphUp = seekGraphUp;
15629
15630     if (appData.debugMode)
15631         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15632                 target, currentMove, forwardMostMove);
15633
15634     if (gameMode == EditPosition)
15635       return;
15636
15637     seekGraphUp = FALSE;
15638     MarkTargetSquares(1);
15639     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15640
15641     if (gameMode == PlayFromGameFile && !pausing)
15642       PauseEvent();
15643
15644     if (gameMode == IcsExamining && pausing)
15645       limit = pauseExamForwardMostMove;
15646     else
15647       limit = forwardMostMove;
15648
15649     if (target > limit) target = limit;
15650
15651     if (target > 0 && moveList[target - 1][0]) {
15652         int fromX, fromY, toX, toY;
15653         toX = moveList[target - 1][2] - AAA;
15654         toY = moveList[target - 1][3] - ONE;
15655         if (moveList[target - 1][1] == '@') {
15656             if (appData.highlightLastMove) {
15657                 SetHighlights(-1, -1, toX, toY);
15658             }
15659         } else {
15660             int viaX = moveList[target - 1][5] - AAA;
15661             int viaY = moveList[target - 1][6] - ONE;
15662             fromX = moveList[target - 1][0] - AAA;
15663             fromY = moveList[target - 1][1] - ONE;
15664             if (target == currentMove + 1) {
15665                 if(moveList[target - 1][4] == ';') { // multi-leg
15666                     ChessSquare piece = boards[currentMove][viaY][viaX];
15667                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15668                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15669                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15670                     boards[currentMove][viaY][viaX] = piece;
15671                 } else
15672                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15673             }
15674             if (appData.highlightLastMove) {
15675                 SetHighlights(fromX, fromY, toX, toY);
15676             }
15677         }
15678     }
15679     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15680         gameMode == Training || gameMode == PlayFromGameFile ||
15681         gameMode == AnalyzeFile) {
15682         while (currentMove < target) {
15683             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15684             SendMoveToProgram(currentMove++, &first);
15685         }
15686     } else {
15687         currentMove = target;
15688     }
15689
15690     if (gameMode == EditGame || gameMode == EndOfGame) {
15691         whiteTimeRemaining = timeRemaining[0][currentMove];
15692         blackTimeRemaining = timeRemaining[1][currentMove];
15693     }
15694     DisplayBothClocks();
15695     DisplayMove(currentMove - 1);
15696     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15697     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15698     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15699         DisplayComment(currentMove - 1, commentList[currentMove]);
15700     }
15701     ClearMap(); // [HGM] exclude: invalidate map
15702 }
15703
15704
15705 void
15706 ForwardEvent ()
15707 {
15708     if (gameMode == IcsExamining && !pausing) {
15709         SendToICS(ics_prefix);
15710         SendToICS("forward\n");
15711     } else {
15712         ForwardInner(currentMove + 1);
15713     }
15714 }
15715
15716 void
15717 ToEndEvent ()
15718 {
15719     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15720         /* to optimze, we temporarily turn off analysis mode while we feed
15721          * the remaining moves to the engine. Otherwise we get analysis output
15722          * after each move.
15723          */
15724         if (first.analysisSupport) {
15725           SendToProgram("exit\nforce\n", &first);
15726           first.analyzing = FALSE;
15727         }
15728     }
15729
15730     if (gameMode == IcsExamining && !pausing) {
15731         SendToICS(ics_prefix);
15732         SendToICS("forward 999999\n");
15733     } else {
15734         ForwardInner(forwardMostMove);
15735     }
15736
15737     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15738         /* we have fed all the moves, so reactivate analysis mode */
15739         SendToProgram("analyze\n", &first);
15740         first.analyzing = TRUE;
15741         /*first.maybeThinking = TRUE;*/
15742         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15743     }
15744 }
15745
15746 void
15747 BackwardInner (int target)
15748 {
15749     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15750
15751     if (appData.debugMode)
15752         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15753                 target, currentMove, forwardMostMove);
15754
15755     if (gameMode == EditPosition) return;
15756     seekGraphUp = FALSE;
15757     MarkTargetSquares(1);
15758     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15759     if (currentMove <= backwardMostMove) {
15760         ClearHighlights();
15761         DrawPosition(full_redraw, boards[currentMove]);
15762         return;
15763     }
15764     if (gameMode == PlayFromGameFile && !pausing)
15765       PauseEvent();
15766
15767     if (moveList[target][0]) {
15768         int fromX, fromY, toX, toY;
15769         toX = moveList[target][2] - AAA;
15770         toY = moveList[target][3] - ONE;
15771         if (moveList[target][1] == '@') {
15772             if (appData.highlightLastMove) {
15773                 SetHighlights(-1, -1, toX, toY);
15774             }
15775         } else {
15776             fromX = moveList[target][0] - AAA;
15777             fromY = moveList[target][1] - ONE;
15778             if (target == currentMove - 1) {
15779                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15780             }
15781             if (appData.highlightLastMove) {
15782                 SetHighlights(fromX, fromY, toX, toY);
15783             }
15784         }
15785     }
15786     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15787         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15788         while (currentMove > target) {
15789             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15790                 // null move cannot be undone. Reload program with move history before it.
15791                 int i;
15792                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15793                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15794                 }
15795                 SendBoard(&first, i);
15796               if(second.analyzing) SendBoard(&second, i);
15797                 for(currentMove=i; currentMove<target; currentMove++) {
15798                     SendMoveToProgram(currentMove, &first);
15799                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15800                 }
15801                 break;
15802             }
15803             SendToBoth("undo\n");
15804             currentMove--;
15805         }
15806     } else {
15807         currentMove = target;
15808     }
15809
15810     if (gameMode == EditGame || gameMode == EndOfGame) {
15811         whiteTimeRemaining = timeRemaining[0][currentMove];
15812         blackTimeRemaining = timeRemaining[1][currentMove];
15813     }
15814     DisplayBothClocks();
15815     DisplayMove(currentMove - 1);
15816     DrawPosition(full_redraw, boards[currentMove]);
15817     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15818     // [HGM] PV info: routine tests if comment empty
15819     DisplayComment(currentMove - 1, commentList[currentMove]);
15820     ClearMap(); // [HGM] exclude: invalidate map
15821 }
15822
15823 void
15824 BackwardEvent ()
15825 {
15826     if (gameMode == IcsExamining && !pausing) {
15827         SendToICS(ics_prefix);
15828         SendToICS("backward\n");
15829     } else {
15830         BackwardInner(currentMove - 1);
15831     }
15832 }
15833
15834 void
15835 ToStartEvent ()
15836 {
15837     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15838         /* to optimize, we temporarily turn off analysis mode while we undo
15839          * all the moves. Otherwise we get analysis output after each undo.
15840          */
15841         if (first.analysisSupport) {
15842           SendToProgram("exit\nforce\n", &first);
15843           first.analyzing = FALSE;
15844         }
15845     }
15846
15847     if (gameMode == IcsExamining && !pausing) {
15848         SendToICS(ics_prefix);
15849         SendToICS("backward 999999\n");
15850     } else {
15851         BackwardInner(backwardMostMove);
15852     }
15853
15854     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15855         /* we have fed all the moves, so reactivate analysis mode */
15856         SendToProgram("analyze\n", &first);
15857         first.analyzing = TRUE;
15858         /*first.maybeThinking = TRUE;*/
15859         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15860     }
15861 }
15862
15863 void
15864 ToNrEvent (int to)
15865 {
15866   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15867   if (to >= forwardMostMove) to = forwardMostMove;
15868   if (to <= backwardMostMove) to = backwardMostMove;
15869   if (to < currentMove) {
15870     BackwardInner(to);
15871   } else {
15872     ForwardInner(to);
15873   }
15874 }
15875
15876 void
15877 RevertEvent (Boolean annotate)
15878 {
15879     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15880         return;
15881     }
15882     if (gameMode != IcsExamining) {
15883         DisplayError(_("You are not examining a game"), 0);
15884         return;
15885     }
15886     if (pausing) {
15887         DisplayError(_("You can't revert while pausing"), 0);
15888         return;
15889     }
15890     SendToICS(ics_prefix);
15891     SendToICS("revert\n");
15892 }
15893
15894 void
15895 RetractMoveEvent ()
15896 {
15897     switch (gameMode) {
15898       case MachinePlaysWhite:
15899       case MachinePlaysBlack:
15900         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15901             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15902             return;
15903         }
15904         if (forwardMostMove < 2) return;
15905         currentMove = forwardMostMove = forwardMostMove - 2;
15906         whiteTimeRemaining = timeRemaining[0][currentMove];
15907         blackTimeRemaining = timeRemaining[1][currentMove];
15908         DisplayBothClocks();
15909         DisplayMove(currentMove - 1);
15910         ClearHighlights();/*!! could figure this out*/
15911         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15912         SendToProgram("remove\n", &first);
15913         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15914         break;
15915
15916       case BeginningOfGame:
15917       default:
15918         break;
15919
15920       case IcsPlayingWhite:
15921       case IcsPlayingBlack:
15922         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15923             SendToICS(ics_prefix);
15924             SendToICS("takeback 2\n");
15925         } else {
15926             SendToICS(ics_prefix);
15927             SendToICS("takeback 1\n");
15928         }
15929         break;
15930     }
15931 }
15932
15933 void
15934 MoveNowEvent ()
15935 {
15936     ChessProgramState *cps;
15937
15938     switch (gameMode) {
15939       case MachinePlaysWhite:
15940         if (!WhiteOnMove(forwardMostMove)) {
15941             DisplayError(_("It is your turn"), 0);
15942             return;
15943         }
15944         cps = &first;
15945         break;
15946       case MachinePlaysBlack:
15947         if (WhiteOnMove(forwardMostMove)) {
15948             DisplayError(_("It is your turn"), 0);
15949             return;
15950         }
15951         cps = &first;
15952         break;
15953       case TwoMachinesPlay:
15954         if (WhiteOnMove(forwardMostMove) ==
15955             (first.twoMachinesColor[0] == 'w')) {
15956             cps = &first;
15957         } else {
15958             cps = &second;
15959         }
15960         break;
15961       case BeginningOfGame:
15962       default:
15963         return;
15964     }
15965     SendToProgram("?\n", cps);
15966 }
15967
15968 void
15969 TruncateGameEvent ()
15970 {
15971     EditGameEvent();
15972     if (gameMode != EditGame) return;
15973     TruncateGame();
15974 }
15975
15976 void
15977 TruncateGame ()
15978 {
15979     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15980     if (forwardMostMove > currentMove) {
15981         if (gameInfo.resultDetails != NULL) {
15982             free(gameInfo.resultDetails);
15983             gameInfo.resultDetails = NULL;
15984             gameInfo.result = GameUnfinished;
15985         }
15986         forwardMostMove = currentMove;
15987         HistorySet(parseList, backwardMostMove, forwardMostMove,
15988                    currentMove-1);
15989     }
15990 }
15991
15992 void
15993 HintEvent ()
15994 {
15995     if (appData.noChessProgram) return;
15996     switch (gameMode) {
15997       case MachinePlaysWhite:
15998         if (WhiteOnMove(forwardMostMove)) {
15999             DisplayError(_("Wait until your turn."), 0);
16000             return;
16001         }
16002         break;
16003       case BeginningOfGame:
16004       case MachinePlaysBlack:
16005         if (!WhiteOnMove(forwardMostMove)) {
16006             DisplayError(_("Wait until your turn."), 0);
16007             return;
16008         }
16009         break;
16010       default:
16011         DisplayError(_("No hint available"), 0);
16012         return;
16013     }
16014     SendToProgram("hint\n", &first);
16015     hintRequested = TRUE;
16016 }
16017
16018 int
16019 SaveSelected (FILE *g, int dummy, char *dummy2)
16020 {
16021     ListGame * lg = (ListGame *) gameList.head;
16022     int nItem, cnt=0;
16023     FILE *f;
16024
16025     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16026         DisplayError(_("Game list not loaded or empty"), 0);
16027         return 0;
16028     }
16029
16030     creatingBook = TRUE; // suppresses stuff during load game
16031
16032     /* Get list size */
16033     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16034         if(lg->position >= 0) { // selected?
16035             LoadGame(f, nItem, "", TRUE);
16036             SaveGamePGN2(g); // leaves g open
16037             cnt++; DoEvents();
16038         }
16039         lg = (ListGame *) lg->node.succ;
16040     }
16041
16042     fclose(g);
16043     creatingBook = FALSE;
16044
16045     return cnt;
16046 }
16047
16048 void
16049 CreateBookEvent ()
16050 {
16051     ListGame * lg = (ListGame *) gameList.head;
16052     FILE *f, *g;
16053     int nItem;
16054     static int secondTime = FALSE;
16055
16056     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16057         DisplayError(_("Game list not loaded or empty"), 0);
16058         return;
16059     }
16060
16061     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16062         fclose(g);
16063         secondTime++;
16064         DisplayNote(_("Book file exists! Try again for overwrite."));
16065         return;
16066     }
16067
16068     creatingBook = TRUE;
16069     secondTime = FALSE;
16070
16071     /* Get list size */
16072     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16073         if(lg->position >= 0) {
16074             LoadGame(f, nItem, "", TRUE);
16075             AddGameToBook(TRUE);
16076             DoEvents();
16077         }
16078         lg = (ListGame *) lg->node.succ;
16079     }
16080
16081     creatingBook = FALSE;
16082     FlushBook();
16083 }
16084
16085 void
16086 BookEvent ()
16087 {
16088     if (appData.noChessProgram) return;
16089     switch (gameMode) {
16090       case MachinePlaysWhite:
16091         if (WhiteOnMove(forwardMostMove)) {
16092             DisplayError(_("Wait until your turn."), 0);
16093             return;
16094         }
16095         break;
16096       case BeginningOfGame:
16097       case MachinePlaysBlack:
16098         if (!WhiteOnMove(forwardMostMove)) {
16099             DisplayError(_("Wait until your turn."), 0);
16100             return;
16101         }
16102         break;
16103       case EditPosition:
16104         EditPositionDone(TRUE);
16105         break;
16106       case TwoMachinesPlay:
16107         return;
16108       default:
16109         break;
16110     }
16111     SendToProgram("bk\n", &first);
16112     bookOutput[0] = NULLCHAR;
16113     bookRequested = TRUE;
16114 }
16115
16116 void
16117 AboutGameEvent ()
16118 {
16119     char *tags = PGNTags(&gameInfo);
16120     TagsPopUp(tags, CmailMsg());
16121     free(tags);
16122 }
16123
16124 /* end button procedures */
16125
16126 void
16127 PrintPosition (FILE *fp, int move)
16128 {
16129     int i, j;
16130
16131     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16132         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16133             char c = PieceToChar(boards[move][i][j]);
16134             fputc(c == 'x' ? '.' : c, fp);
16135             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16136         }
16137     }
16138     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16139       fprintf(fp, "white to play\n");
16140     else
16141       fprintf(fp, "black to play\n");
16142 }
16143
16144 void
16145 PrintOpponents (FILE *fp)
16146 {
16147     if (gameInfo.white != NULL) {
16148         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16149     } else {
16150         fprintf(fp, "\n");
16151     }
16152 }
16153
16154 /* Find last component of program's own name, using some heuristics */
16155 void
16156 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16157 {
16158     char *p, *q, c;
16159     int local = (strcmp(host, "localhost") == 0);
16160     while (!local && (p = strchr(prog, ';')) != NULL) {
16161         p++;
16162         while (*p == ' ') p++;
16163         prog = p;
16164     }
16165     if (*prog == '"' || *prog == '\'') {
16166         q = strchr(prog + 1, *prog);
16167     } else {
16168         q = strchr(prog, ' ');
16169     }
16170     if (q == NULL) q = prog + strlen(prog);
16171     p = q;
16172     while (p >= prog && *p != '/' && *p != '\\') p--;
16173     p++;
16174     if(p == prog && *p == '"') p++;
16175     c = *q; *q = 0;
16176     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16177     memcpy(buf, p, q - p);
16178     buf[q - p] = NULLCHAR;
16179     if (!local) {
16180         strcat(buf, "@");
16181         strcat(buf, host);
16182     }
16183 }
16184
16185 char *
16186 TimeControlTagValue ()
16187 {
16188     char buf[MSG_SIZ];
16189     if (!appData.clockMode) {
16190       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16191     } else if (movesPerSession > 0) {
16192       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16193     } else if (timeIncrement == 0) {
16194       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16195     } else {
16196       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16197     }
16198     return StrSave(buf);
16199 }
16200
16201 void
16202 SetGameInfo ()
16203 {
16204     /* This routine is used only for certain modes */
16205     VariantClass v = gameInfo.variant;
16206     ChessMove r = GameUnfinished;
16207     char *p = NULL;
16208
16209     if(keepInfo) return;
16210
16211     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16212         r = gameInfo.result;
16213         p = gameInfo.resultDetails;
16214         gameInfo.resultDetails = NULL;
16215     }
16216     ClearGameInfo(&gameInfo);
16217     gameInfo.variant = v;
16218
16219     switch (gameMode) {
16220       case MachinePlaysWhite:
16221         gameInfo.event = StrSave( appData.pgnEventHeader );
16222         gameInfo.site = StrSave(HostName());
16223         gameInfo.date = PGNDate();
16224         gameInfo.round = StrSave("-");
16225         gameInfo.white = StrSave(first.tidy);
16226         gameInfo.black = StrSave(UserName());
16227         gameInfo.timeControl = TimeControlTagValue();
16228         break;
16229
16230       case MachinePlaysBlack:
16231         gameInfo.event = StrSave( appData.pgnEventHeader );
16232         gameInfo.site = StrSave(HostName());
16233         gameInfo.date = PGNDate();
16234         gameInfo.round = StrSave("-");
16235         gameInfo.white = StrSave(UserName());
16236         gameInfo.black = StrSave(first.tidy);
16237         gameInfo.timeControl = TimeControlTagValue();
16238         break;
16239
16240       case TwoMachinesPlay:
16241         gameInfo.event = StrSave( appData.pgnEventHeader );
16242         gameInfo.site = StrSave(HostName());
16243         gameInfo.date = PGNDate();
16244         if (roundNr > 0) {
16245             char buf[MSG_SIZ];
16246             snprintf(buf, MSG_SIZ, "%d", roundNr);
16247             gameInfo.round = StrSave(buf);
16248         } else {
16249             gameInfo.round = StrSave("-");
16250         }
16251         if (first.twoMachinesColor[0] == 'w') {
16252             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16253             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16254         } else {
16255             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16256             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16257         }
16258         gameInfo.timeControl = TimeControlTagValue();
16259         break;
16260
16261       case EditGame:
16262         gameInfo.event = StrSave("Edited game");
16263         gameInfo.site = StrSave(HostName());
16264         gameInfo.date = PGNDate();
16265         gameInfo.round = StrSave("-");
16266         gameInfo.white = StrSave("-");
16267         gameInfo.black = StrSave("-");
16268         gameInfo.result = r;
16269         gameInfo.resultDetails = p;
16270         break;
16271
16272       case EditPosition:
16273         gameInfo.event = StrSave("Edited position");
16274         gameInfo.site = StrSave(HostName());
16275         gameInfo.date = PGNDate();
16276         gameInfo.round = StrSave("-");
16277         gameInfo.white = StrSave("-");
16278         gameInfo.black = StrSave("-");
16279         break;
16280
16281       case IcsPlayingWhite:
16282       case IcsPlayingBlack:
16283       case IcsObserving:
16284       case IcsExamining:
16285         break;
16286
16287       case PlayFromGameFile:
16288         gameInfo.event = StrSave("Game from non-PGN file");
16289         gameInfo.site = StrSave(HostName());
16290         gameInfo.date = PGNDate();
16291         gameInfo.round = StrSave("-");
16292         gameInfo.white = StrSave("?");
16293         gameInfo.black = StrSave("?");
16294         break;
16295
16296       default:
16297         break;
16298     }
16299 }
16300
16301 void
16302 ReplaceComment (int index, char *text)
16303 {
16304     int len;
16305     char *p;
16306     float score;
16307
16308     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16309        pvInfoList[index-1].depth == len &&
16310        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16311        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16312     while (*text == '\n') text++;
16313     len = strlen(text);
16314     while (len > 0 && text[len - 1] == '\n') len--;
16315
16316     if (commentList[index] != NULL)
16317       free(commentList[index]);
16318
16319     if (len == 0) {
16320         commentList[index] = NULL;
16321         return;
16322     }
16323   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16324       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16325       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16326     commentList[index] = (char *) malloc(len + 2);
16327     strncpy(commentList[index], text, len);
16328     commentList[index][len] = '\n';
16329     commentList[index][len + 1] = NULLCHAR;
16330   } else {
16331     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16332     char *p;
16333     commentList[index] = (char *) malloc(len + 7);
16334     safeStrCpy(commentList[index], "{\n", 3);
16335     safeStrCpy(commentList[index]+2, text, len+1);
16336     commentList[index][len+2] = NULLCHAR;
16337     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16338     strcat(commentList[index], "\n}\n");
16339   }
16340 }
16341
16342 void
16343 CrushCRs (char *text)
16344 {
16345   char *p = text;
16346   char *q = text;
16347   char ch;
16348
16349   do {
16350     ch = *p++;
16351     if (ch == '\r') continue;
16352     *q++ = ch;
16353   } while (ch != '\0');
16354 }
16355
16356 void
16357 AppendComment (int index, char *text, Boolean addBraces)
16358 /* addBraces  tells if we should add {} */
16359 {
16360     int oldlen, len;
16361     char *old;
16362
16363 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16364     if(addBraces == 3) addBraces = 0; else // force appending literally
16365     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16366
16367     CrushCRs(text);
16368     while (*text == '\n') text++;
16369     len = strlen(text);
16370     while (len > 0 && text[len - 1] == '\n') len--;
16371     text[len] = NULLCHAR;
16372
16373     if (len == 0) return;
16374
16375     if (commentList[index] != NULL) {
16376       Boolean addClosingBrace = addBraces;
16377         old = commentList[index];
16378         oldlen = strlen(old);
16379         while(commentList[index][oldlen-1] ==  '\n')
16380           commentList[index][--oldlen] = NULLCHAR;
16381         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16382         safeStrCpy(commentList[index], old, oldlen + len + 6);
16383         free(old);
16384         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16385         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16386           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16387           while (*text == '\n') { text++; len--; }
16388           commentList[index][--oldlen] = NULLCHAR;
16389       }
16390         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16391         else          strcat(commentList[index], "\n");
16392         strcat(commentList[index], text);
16393         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16394         else          strcat(commentList[index], "\n");
16395     } else {
16396         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16397         if(addBraces)
16398           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16399         else commentList[index][0] = NULLCHAR;
16400         strcat(commentList[index], text);
16401         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16402         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16403     }
16404 }
16405
16406 static char *
16407 FindStr (char * text, char * sub_text)
16408 {
16409     char * result = strstr( text, sub_text );
16410
16411     if( result != NULL ) {
16412         result += strlen( sub_text );
16413     }
16414
16415     return result;
16416 }
16417
16418 /* [AS] Try to extract PV info from PGN comment */
16419 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16420 char *
16421 GetInfoFromComment (int index, char * text)
16422 {
16423     char * sep = text, *p;
16424
16425     if( text != NULL && index > 0 ) {
16426         int score = 0;
16427         int depth = 0;
16428         int time = -1, sec = 0, deci;
16429         char * s_eval = FindStr( text, "[%eval " );
16430         char * s_emt = FindStr( text, "[%emt " );
16431 #if 0
16432         if( s_eval != NULL || s_emt != NULL ) {
16433 #else
16434         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16435 #endif
16436             /* New style */
16437             char delim;
16438
16439             if( s_eval != NULL ) {
16440                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16441                     return text;
16442                 }
16443
16444                 if( delim != ']' ) {
16445                     return text;
16446                 }
16447             }
16448
16449             if( s_emt != NULL ) {
16450             }
16451                 return text;
16452         }
16453         else {
16454             /* We expect something like: [+|-]nnn.nn/dd */
16455             int score_lo = 0;
16456
16457             if(*text != '{') return text; // [HGM] braces: must be normal comment
16458
16459             sep = strchr( text, '/' );
16460             if( sep == NULL || sep < (text+4) ) {
16461                 return text;
16462             }
16463
16464             p = text;
16465             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16466             if(p[1] == '(') { // comment starts with PV
16467                p = strchr(p, ')'); // locate end of PV
16468                if(p == NULL || sep < p+5) return text;
16469                // at this point we have something like "{(.*) +0.23/6 ..."
16470                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16471                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16472                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16473             }
16474             time = -1; sec = -1; deci = -1;
16475             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16476                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16477                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16478                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16479                 return text;
16480             }
16481
16482             if( score_lo < 0 || score_lo >= 100 ) {
16483                 return text;
16484             }
16485
16486             if(sec >= 0) time = 600*time + 10*sec; else
16487             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16488
16489             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16490
16491             /* [HGM] PV time: now locate end of PV info */
16492             while( *++sep >= '0' && *sep <= '9'); // strip depth
16493             if(time >= 0)
16494             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16495             if(sec >= 0)
16496             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16497             if(deci >= 0)
16498             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16499             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16500         }
16501
16502         if( depth <= 0 ) {
16503             return text;
16504         }
16505
16506         if( time < 0 ) {
16507             time = -1;
16508         }
16509
16510         pvInfoList[index-1].depth = depth;
16511         pvInfoList[index-1].score = score;
16512         pvInfoList[index-1].time  = 10*time; // centi-sec
16513         if(*sep == '}') *sep = 0; else *--sep = '{';
16514         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16515     }
16516     return sep;
16517 }
16518
16519 void
16520 SendToProgram (char *message, ChessProgramState *cps)
16521 {
16522     int count, outCount, error;
16523     char buf[MSG_SIZ];
16524
16525     if (cps->pr == NoProc) return;
16526     Attention(cps);
16527
16528     if (appData.debugMode) {
16529         TimeMark now;
16530         GetTimeMark(&now);
16531         fprintf(debugFP, "%ld >%-6s: %s",
16532                 SubtractTimeMarks(&now, &programStartTime),
16533                 cps->which, message);
16534         if(serverFP)
16535             fprintf(serverFP, "%ld >%-6s: %s",
16536                 SubtractTimeMarks(&now, &programStartTime),
16537                 cps->which, message), fflush(serverFP);
16538     }
16539
16540     count = strlen(message);
16541     outCount = OutputToProcess(cps->pr, message, count, &error);
16542     if (outCount < count && !exiting
16543                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16544       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16545       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16546         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16547             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16548                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16549                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16550                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16551             } else {
16552                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16553                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16554                 gameInfo.result = res;
16555             }
16556             gameInfo.resultDetails = StrSave(buf);
16557         }
16558         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16559         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16560     }
16561 }
16562
16563 void
16564 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16565 {
16566     char *end_str;
16567     char buf[MSG_SIZ];
16568     ChessProgramState *cps = (ChessProgramState *)closure;
16569
16570     if (isr != cps->isr) return; /* Killed intentionally */
16571     if (count <= 0) {
16572         if (count == 0) {
16573             RemoveInputSource(cps->isr);
16574             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16575                     _(cps->which), cps->program);
16576             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16577             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16578                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16579                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16580                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16581                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16582                 } else {
16583                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16584                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16585                     gameInfo.result = res;
16586                 }
16587                 gameInfo.resultDetails = StrSave(buf);
16588             }
16589             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16590             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16591         } else {
16592             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16593                     _(cps->which), cps->program);
16594             RemoveInputSource(cps->isr);
16595
16596             /* [AS] Program is misbehaving badly... kill it */
16597             if( count == -2 ) {
16598                 DestroyChildProcess( cps->pr, 9 );
16599                 cps->pr = NoProc;
16600             }
16601
16602             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16603         }
16604         return;
16605     }
16606
16607     if ((end_str = strchr(message, '\r')) != NULL)
16608       *end_str = NULLCHAR;
16609     if ((end_str = strchr(message, '\n')) != NULL)
16610       *end_str = NULLCHAR;
16611
16612     if (appData.debugMode) {
16613         TimeMark now; int print = 1;
16614         char *quote = ""; char c; int i;
16615
16616         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16617                 char start = message[0];
16618                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16619                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16620                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16621                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16622                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16623                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16624                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16625                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16626                    sscanf(message, "hint: %c", &c)!=1 &&
16627                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16628                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16629                     print = (appData.engineComments >= 2);
16630                 }
16631                 message[0] = start; // restore original message
16632         }
16633         if(print) {
16634                 GetTimeMark(&now);
16635                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16636                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16637                         quote,
16638                         message);
16639                 if(serverFP)
16640                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16641                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16642                         quote,
16643                         message), fflush(serverFP);
16644         }
16645     }
16646
16647     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16648     if (appData.icsEngineAnalyze) {
16649         if (strstr(message, "whisper") != NULL ||
16650              strstr(message, "kibitz") != NULL ||
16651             strstr(message, "tellics") != NULL) return;
16652     }
16653
16654     HandleMachineMove(message, cps);
16655 }
16656
16657
16658 void
16659 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16660 {
16661     char buf[MSG_SIZ];
16662     int seconds;
16663
16664     if( timeControl_2 > 0 ) {
16665         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16666             tc = timeControl_2;
16667         }
16668     }
16669     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16670     inc /= cps->timeOdds;
16671     st  /= cps->timeOdds;
16672
16673     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16674
16675     if (st > 0) {
16676       /* Set exact time per move, normally using st command */
16677       if (cps->stKludge) {
16678         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16679         seconds = st % 60;
16680         if (seconds == 0) {
16681           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16682         } else {
16683           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16684         }
16685       } else {
16686         snprintf(buf, MSG_SIZ, "st %d\n", st);
16687       }
16688     } else {
16689       /* Set conventional or incremental time control, using level command */
16690       if (seconds == 0) {
16691         /* Note old gnuchess bug -- minutes:seconds used to not work.
16692            Fixed in later versions, but still avoid :seconds
16693            when seconds is 0. */
16694         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16695       } else {
16696         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16697                  seconds, inc/1000.);
16698       }
16699     }
16700     SendToProgram(buf, cps);
16701
16702     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16703     /* Orthogonally, limit search to given depth */
16704     if (sd > 0) {
16705       if (cps->sdKludge) {
16706         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16707       } else {
16708         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16709       }
16710       SendToProgram(buf, cps);
16711     }
16712
16713     if(cps->nps >= 0) { /* [HGM] nps */
16714         if(cps->supportsNPS == FALSE)
16715           cps->nps = -1; // don't use if engine explicitly says not supported!
16716         else {
16717           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16718           SendToProgram(buf, cps);
16719         }
16720     }
16721 }
16722
16723 ChessProgramState *
16724 WhitePlayer ()
16725 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16726 {
16727     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16728        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16729         return &second;
16730     return &first;
16731 }
16732
16733 void
16734 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16735 {
16736     char message[MSG_SIZ];
16737     long time, otime;
16738
16739     /* Note: this routine must be called when the clocks are stopped
16740        or when they have *just* been set or switched; otherwise
16741        it will be off by the time since the current tick started.
16742     */
16743     if (machineWhite) {
16744         time = whiteTimeRemaining / 10;
16745         otime = blackTimeRemaining / 10;
16746     } else {
16747         time = blackTimeRemaining / 10;
16748         otime = whiteTimeRemaining / 10;
16749     }
16750     /* [HGM] translate opponent's time by time-odds factor */
16751     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16752
16753     if (time <= 0) time = 1;
16754     if (otime <= 0) otime = 1;
16755
16756     snprintf(message, MSG_SIZ, "time %ld\n", time);
16757     SendToProgram(message, cps);
16758
16759     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16760     SendToProgram(message, cps);
16761 }
16762
16763 char *
16764 EngineDefinedVariant (ChessProgramState *cps, int n)
16765 {   // return name of n-th unknown variant that engine supports
16766     static char buf[MSG_SIZ];
16767     char *p, *s = cps->variants;
16768     if(!s) return NULL;
16769     do { // parse string from variants feature
16770       VariantClass v;
16771         p = strchr(s, ',');
16772         if(p) *p = NULLCHAR;
16773       v = StringToVariant(s);
16774       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16775         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16776             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16777                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16778                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16779                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16780             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16781         }
16782         if(p) *p++ = ',';
16783         if(n < 0) return buf;
16784     } while(s = p);
16785     return NULL;
16786 }
16787
16788 int
16789 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16790 {
16791   char buf[MSG_SIZ];
16792   int len = strlen(name);
16793   int val;
16794
16795   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16796     (*p) += len + 1;
16797     sscanf(*p, "%d", &val);
16798     *loc = (val != 0);
16799     while (**p && **p != ' ')
16800       (*p)++;
16801     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16802     SendToProgram(buf, cps);
16803     return TRUE;
16804   }
16805   return FALSE;
16806 }
16807
16808 int
16809 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16810 {
16811   char buf[MSG_SIZ];
16812   int len = strlen(name);
16813   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16814     (*p) += len + 1;
16815     sscanf(*p, "%d", loc);
16816     while (**p && **p != ' ') (*p)++;
16817     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16818     SendToProgram(buf, cps);
16819     return TRUE;
16820   }
16821   return FALSE;
16822 }
16823
16824 int
16825 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16826 {
16827   char buf[MSG_SIZ];
16828   int len = strlen(name);
16829   if (strncmp((*p), name, len) == 0
16830       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16831     (*p) += len + 2;
16832     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16833     sscanf(*p, "%[^\"]", *loc);
16834     while (**p && **p != '\"') (*p)++;
16835     if (**p == '\"') (*p)++;
16836     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16837     SendToProgram(buf, cps);
16838     return TRUE;
16839   }
16840   return FALSE;
16841 }
16842
16843 int
16844 ParseOption (Option *opt, ChessProgramState *cps)
16845 // [HGM] options: process the string that defines an engine option, and determine
16846 // name, type, default value, and allowed value range
16847 {
16848         char *p, *q, buf[MSG_SIZ];
16849         int n, min = (-1)<<31, max = 1<<31, def;
16850
16851         if(p = strstr(opt->name, " -spin ")) {
16852             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16853             if(max < min) max = min; // enforce consistency
16854             if(def < min) def = min;
16855             if(def > max) def = max;
16856             opt->value = def;
16857             opt->min = min;
16858             opt->max = max;
16859             opt->type = Spin;
16860         } else if((p = strstr(opt->name, " -slider "))) {
16861             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16862             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16863             if(max < min) max = min; // enforce consistency
16864             if(def < min) def = min;
16865             if(def > max) def = max;
16866             opt->value = def;
16867             opt->min = min;
16868             opt->max = max;
16869             opt->type = Spin; // Slider;
16870         } else if((p = strstr(opt->name, " -string "))) {
16871             opt->textValue = p+9;
16872             opt->type = TextBox;
16873         } else if((p = strstr(opt->name, " -file "))) {
16874             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16875             opt->textValue = p+7;
16876             opt->type = FileName; // FileName;
16877         } else if((p = strstr(opt->name, " -path "))) {
16878             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16879             opt->textValue = p+7;
16880             opt->type = PathName; // PathName;
16881         } else if(p = strstr(opt->name, " -check ")) {
16882             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16883             opt->value = (def != 0);
16884             opt->type = CheckBox;
16885         } else if(p = strstr(opt->name, " -combo ")) {
16886             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16887             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16888             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16889             opt->value = n = 0;
16890             while(q = StrStr(q, " /// ")) {
16891                 n++; *q = 0;    // count choices, and null-terminate each of them
16892                 q += 5;
16893                 if(*q == '*') { // remember default, which is marked with * prefix
16894                     q++;
16895                     opt->value = n;
16896                 }
16897                 cps->comboList[cps->comboCnt++] = q;
16898             }
16899             cps->comboList[cps->comboCnt++] = NULL;
16900             opt->max = n + 1;
16901             opt->type = ComboBox;
16902         } else if(p = strstr(opt->name, " -button")) {
16903             opt->type = Button;
16904         } else if(p = strstr(opt->name, " -save")) {
16905             opt->type = SaveButton;
16906         } else return FALSE;
16907         *p = 0; // terminate option name
16908         // now look if the command-line options define a setting for this engine option.
16909         if(cps->optionSettings && cps->optionSettings[0])
16910             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16911         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16912           snprintf(buf, MSG_SIZ, "option %s", p);
16913                 if(p = strstr(buf, ",")) *p = 0;
16914                 if(q = strchr(buf, '=')) switch(opt->type) {
16915                     case ComboBox:
16916                         for(n=0; n<opt->max; n++)
16917                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16918                         break;
16919                     case TextBox:
16920                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16921                         break;
16922                     case Spin:
16923                     case CheckBox:
16924                         opt->value = atoi(q+1);
16925                     default:
16926                         break;
16927                 }
16928                 strcat(buf, "\n");
16929                 SendToProgram(buf, cps);
16930         }
16931         return TRUE;
16932 }
16933
16934 void
16935 FeatureDone (ChessProgramState *cps, int val)
16936 {
16937   DelayedEventCallback cb = GetDelayedEvent();
16938   if ((cb == InitBackEnd3 && cps == &first) ||
16939       (cb == SettingsMenuIfReady && cps == &second) ||
16940       (cb == LoadEngine) ||
16941       (cb == TwoMachinesEventIfReady)) {
16942     CancelDelayedEvent();
16943     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16944   }
16945   cps->initDone = val;
16946   if(val) cps->reload = FALSE;
16947 }
16948
16949 /* Parse feature command from engine */
16950 void
16951 ParseFeatures (char *args, ChessProgramState *cps)
16952 {
16953   char *p = args;
16954   char *q = NULL;
16955   int val;
16956   char buf[MSG_SIZ];
16957
16958   for (;;) {
16959     while (*p == ' ') p++;
16960     if (*p == NULLCHAR) return;
16961
16962     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16963     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16964     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16965     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16966     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16967     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16968     if (BoolFeature(&p, "reuse", &val, cps)) {
16969       /* Engine can disable reuse, but can't enable it if user said no */
16970       if (!val) cps->reuse = FALSE;
16971       continue;
16972     }
16973     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16974     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16975       if (gameMode == TwoMachinesPlay) {
16976         DisplayTwoMachinesTitle();
16977       } else {
16978         DisplayTitle("");
16979       }
16980       continue;
16981     }
16982     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16983     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16984     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16985     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16986     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16987     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16988     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16989     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16990     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16991     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16992     if (IntFeature(&p, "done", &val, cps)) {
16993       FeatureDone(cps, val);
16994       continue;
16995     }
16996     /* Added by Tord: */
16997     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16998     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16999     /* End of additions by Tord */
17000
17001     /* [HGM] added features: */
17002     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17003     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17004     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17005     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17006     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17007     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17008     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17009     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17010         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17011         FREE(cps->option[cps->nrOptions].name);
17012         cps->option[cps->nrOptions].name = q; q = NULL;
17013         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17014           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17015             SendToProgram(buf, cps);
17016             continue;
17017         }
17018         if(cps->nrOptions >= MAX_OPTIONS) {
17019             cps->nrOptions--;
17020             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17021             DisplayError(buf, 0);
17022         }
17023         continue;
17024     }
17025     /* End of additions by HGM */
17026
17027     /* unknown feature: complain and skip */
17028     q = p;
17029     while (*q && *q != '=') q++;
17030     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17031     SendToProgram(buf, cps);
17032     p = q;
17033     if (*p == '=') {
17034       p++;
17035       if (*p == '\"') {
17036         p++;
17037         while (*p && *p != '\"') p++;
17038         if (*p == '\"') p++;
17039       } else {
17040         while (*p && *p != ' ') p++;
17041       }
17042     }
17043   }
17044
17045 }
17046
17047 void
17048 PeriodicUpdatesEvent (int newState)
17049 {
17050     if (newState == appData.periodicUpdates)
17051       return;
17052
17053     appData.periodicUpdates=newState;
17054
17055     /* Display type changes, so update it now */
17056 //    DisplayAnalysis();
17057
17058     /* Get the ball rolling again... */
17059     if (newState) {
17060         AnalysisPeriodicEvent(1);
17061         StartAnalysisClock();
17062     }
17063 }
17064
17065 void
17066 PonderNextMoveEvent (int newState)
17067 {
17068     if (newState == appData.ponderNextMove) return;
17069     if (gameMode == EditPosition) EditPositionDone(TRUE);
17070     if (newState) {
17071         SendToProgram("hard\n", &first);
17072         if (gameMode == TwoMachinesPlay) {
17073             SendToProgram("hard\n", &second);
17074         }
17075     } else {
17076         SendToProgram("easy\n", &first);
17077         thinkOutput[0] = NULLCHAR;
17078         if (gameMode == TwoMachinesPlay) {
17079             SendToProgram("easy\n", &second);
17080         }
17081     }
17082     appData.ponderNextMove = newState;
17083 }
17084
17085 void
17086 NewSettingEvent (int option, int *feature, char *command, int value)
17087 {
17088     char buf[MSG_SIZ];
17089
17090     if (gameMode == EditPosition) EditPositionDone(TRUE);
17091     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17092     if(feature == NULL || *feature) SendToProgram(buf, &first);
17093     if (gameMode == TwoMachinesPlay) {
17094         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17095     }
17096 }
17097
17098 void
17099 ShowThinkingEvent ()
17100 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17101 {
17102     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17103     int newState = appData.showThinking
17104         // [HGM] thinking: other features now need thinking output as well
17105         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17106
17107     if (oldState == newState) return;
17108     oldState = newState;
17109     if (gameMode == EditPosition) EditPositionDone(TRUE);
17110     if (oldState) {
17111         SendToProgram("post\n", &first);
17112         if (gameMode == TwoMachinesPlay) {
17113             SendToProgram("post\n", &second);
17114         }
17115     } else {
17116         SendToProgram("nopost\n", &first);
17117         thinkOutput[0] = NULLCHAR;
17118         if (gameMode == TwoMachinesPlay) {
17119             SendToProgram("nopost\n", &second);
17120         }
17121     }
17122 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17123 }
17124
17125 void
17126 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17127 {
17128   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17129   if (pr == NoProc) return;
17130   AskQuestion(title, question, replyPrefix, pr);
17131 }
17132
17133 void
17134 TypeInEvent (char firstChar)
17135 {
17136     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17137         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17138         gameMode == AnalyzeMode || gameMode == EditGame ||
17139         gameMode == EditPosition || gameMode == IcsExamining ||
17140         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17141         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17142                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17143                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17144         gameMode == Training) PopUpMoveDialog(firstChar);
17145 }
17146
17147 void
17148 TypeInDoneEvent (char *move)
17149 {
17150         Board board;
17151         int n, fromX, fromY, toX, toY;
17152         char promoChar;
17153         ChessMove moveType;
17154
17155         // [HGM] FENedit
17156         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17157                 EditPositionPasteFEN(move);
17158                 return;
17159         }
17160         // [HGM] movenum: allow move number to be typed in any mode
17161         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17162           ToNrEvent(2*n-1);
17163           return;
17164         }
17165         // undocumented kludge: allow command-line option to be typed in!
17166         // (potentially fatal, and does not implement the effect of the option.)
17167         // should only be used for options that are values on which future decisions will be made,
17168         // and definitely not on options that would be used during initialization.
17169         if(strstr(move, "!!! -") == move) {
17170             ParseArgsFromString(move+4);
17171             return;
17172         }
17173
17174       if (gameMode != EditGame && currentMove != forwardMostMove &&
17175         gameMode != Training) {
17176         DisplayMoveError(_("Displayed move is not current"));
17177       } else {
17178         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17179           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17180         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17181         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17182           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17183           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17184         } else {
17185           DisplayMoveError(_("Could not parse move"));
17186         }
17187       }
17188 }
17189
17190 void
17191 DisplayMove (int moveNumber)
17192 {
17193     char message[MSG_SIZ];
17194     char res[MSG_SIZ];
17195     char cpThinkOutput[MSG_SIZ];
17196
17197     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17198
17199     if (moveNumber == forwardMostMove - 1 ||
17200         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17201
17202         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17203
17204         if (strchr(cpThinkOutput, '\n')) {
17205             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17206         }
17207     } else {
17208         *cpThinkOutput = NULLCHAR;
17209     }
17210
17211     /* [AS] Hide thinking from human user */
17212     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17213         *cpThinkOutput = NULLCHAR;
17214         if( thinkOutput[0] != NULLCHAR ) {
17215             int i;
17216
17217             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17218                 cpThinkOutput[i] = '.';
17219             }
17220             cpThinkOutput[i] = NULLCHAR;
17221             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17222         }
17223     }
17224
17225     if (moveNumber == forwardMostMove - 1 &&
17226         gameInfo.resultDetails != NULL) {
17227         if (gameInfo.resultDetails[0] == NULLCHAR) {
17228           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17229         } else {
17230           snprintf(res, MSG_SIZ, " {%s} %s",
17231                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17232         }
17233     } else {
17234         res[0] = NULLCHAR;
17235     }
17236
17237     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17238         DisplayMessage(res, cpThinkOutput);
17239     } else {
17240       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17241                 WhiteOnMove(moveNumber) ? " " : ".. ",
17242                 parseList[moveNumber], res);
17243         DisplayMessage(message, cpThinkOutput);
17244     }
17245 }
17246
17247 void
17248 DisplayComment (int moveNumber, char *text)
17249 {
17250     char title[MSG_SIZ];
17251
17252     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17253       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17254     } else {
17255       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17256               WhiteOnMove(moveNumber) ? " " : ".. ",
17257               parseList[moveNumber]);
17258     }
17259     if (text != NULL && (appData.autoDisplayComment || commentUp))
17260         CommentPopUp(title, text);
17261 }
17262
17263 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17264  * might be busy thinking or pondering.  It can be omitted if your
17265  * gnuchess is configured to stop thinking immediately on any user
17266  * input.  However, that gnuchess feature depends on the FIONREAD
17267  * ioctl, which does not work properly on some flavors of Unix.
17268  */
17269 void
17270 Attention (ChessProgramState *cps)
17271 {
17272 #if ATTENTION
17273     if (!cps->useSigint) return;
17274     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17275     switch (gameMode) {
17276       case MachinePlaysWhite:
17277       case MachinePlaysBlack:
17278       case TwoMachinesPlay:
17279       case IcsPlayingWhite:
17280       case IcsPlayingBlack:
17281       case AnalyzeMode:
17282       case AnalyzeFile:
17283         /* Skip if we know it isn't thinking */
17284         if (!cps->maybeThinking) return;
17285         if (appData.debugMode)
17286           fprintf(debugFP, "Interrupting %s\n", cps->which);
17287         InterruptChildProcess(cps->pr);
17288         cps->maybeThinking = FALSE;
17289         break;
17290       default:
17291         break;
17292     }
17293 #endif /*ATTENTION*/
17294 }
17295
17296 int
17297 CheckFlags ()
17298 {
17299     if (whiteTimeRemaining <= 0) {
17300         if (!whiteFlag) {
17301             whiteFlag = TRUE;
17302             if (appData.icsActive) {
17303                 if (appData.autoCallFlag &&
17304                     gameMode == IcsPlayingBlack && !blackFlag) {
17305                   SendToICS(ics_prefix);
17306                   SendToICS("flag\n");
17307                 }
17308             } else {
17309                 if (blackFlag) {
17310                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17311                 } else {
17312                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17313                     if (appData.autoCallFlag) {
17314                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17315                         return TRUE;
17316                     }
17317                 }
17318             }
17319         }
17320     }
17321     if (blackTimeRemaining <= 0) {
17322         if (!blackFlag) {
17323             blackFlag = TRUE;
17324             if (appData.icsActive) {
17325                 if (appData.autoCallFlag &&
17326                     gameMode == IcsPlayingWhite && !whiteFlag) {
17327                   SendToICS(ics_prefix);
17328                   SendToICS("flag\n");
17329                 }
17330             } else {
17331                 if (whiteFlag) {
17332                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17333                 } else {
17334                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17335                     if (appData.autoCallFlag) {
17336                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17337                         return TRUE;
17338                     }
17339                 }
17340             }
17341         }
17342     }
17343     return FALSE;
17344 }
17345
17346 void
17347 CheckTimeControl ()
17348 {
17349     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17350         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17351
17352     /*
17353      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17354      */
17355     if ( !WhiteOnMove(forwardMostMove) ) {
17356         /* White made time control */
17357         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17358         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17359         /* [HGM] time odds: correct new time quota for time odds! */
17360                                             / WhitePlayer()->timeOdds;
17361         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17362     } else {
17363         lastBlack -= blackTimeRemaining;
17364         /* Black made time control */
17365         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17366                                             / WhitePlayer()->other->timeOdds;
17367         lastWhite = whiteTimeRemaining;
17368     }
17369 }
17370
17371 void
17372 DisplayBothClocks ()
17373 {
17374     int wom = gameMode == EditPosition ?
17375       !blackPlaysFirst : WhiteOnMove(currentMove);
17376     DisplayWhiteClock(whiteTimeRemaining, wom);
17377     DisplayBlackClock(blackTimeRemaining, !wom);
17378 }
17379
17380
17381 /* Timekeeping seems to be a portability nightmare.  I think everyone
17382    has ftime(), but I'm really not sure, so I'm including some ifdefs
17383    to use other calls if you don't.  Clocks will be less accurate if
17384    you have neither ftime nor gettimeofday.
17385 */
17386
17387 /* VS 2008 requires the #include outside of the function */
17388 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17389 #include <sys/timeb.h>
17390 #endif
17391
17392 /* Get the current time as a TimeMark */
17393 void
17394 GetTimeMark (TimeMark *tm)
17395 {
17396 #if HAVE_GETTIMEOFDAY
17397
17398     struct timeval timeVal;
17399     struct timezone timeZone;
17400
17401     gettimeofday(&timeVal, &timeZone);
17402     tm->sec = (long) timeVal.tv_sec;
17403     tm->ms = (int) (timeVal.tv_usec / 1000L);
17404
17405 #else /*!HAVE_GETTIMEOFDAY*/
17406 #if HAVE_FTIME
17407
17408 // include <sys/timeb.h> / moved to just above start of function
17409     struct timeb timeB;
17410
17411     ftime(&timeB);
17412     tm->sec = (long) timeB.time;
17413     tm->ms = (int) timeB.millitm;
17414
17415 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17416     tm->sec = (long) time(NULL);
17417     tm->ms = 0;
17418 #endif
17419 #endif
17420 }
17421
17422 /* Return the difference in milliseconds between two
17423    time marks.  We assume the difference will fit in a long!
17424 */
17425 long
17426 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17427 {
17428     return 1000L*(tm2->sec - tm1->sec) +
17429            (long) (tm2->ms - tm1->ms);
17430 }
17431
17432
17433 /*
17434  * Code to manage the game clocks.
17435  *
17436  * In tournament play, black starts the clock and then white makes a move.
17437  * We give the human user a slight advantage if he is playing white---the
17438  * clocks don't run until he makes his first move, so it takes zero time.
17439  * Also, we don't account for network lag, so we could get out of sync
17440  * with GNU Chess's clock -- but then, referees are always right.
17441  */
17442
17443 static TimeMark tickStartTM;
17444 static long intendedTickLength;
17445
17446 long
17447 NextTickLength (long timeRemaining)
17448 {
17449     long nominalTickLength, nextTickLength;
17450
17451     if (timeRemaining > 0L && timeRemaining <= 10000L)
17452       nominalTickLength = 100L;
17453     else
17454       nominalTickLength = 1000L;
17455     nextTickLength = timeRemaining % nominalTickLength;
17456     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17457
17458     return nextTickLength;
17459 }
17460
17461 /* Adjust clock one minute up or down */
17462 void
17463 AdjustClock (Boolean which, int dir)
17464 {
17465     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17466     if(which) blackTimeRemaining += 60000*dir;
17467     else      whiteTimeRemaining += 60000*dir;
17468     DisplayBothClocks();
17469     adjustedClock = TRUE;
17470 }
17471
17472 /* Stop clocks and reset to a fresh time control */
17473 void
17474 ResetClocks ()
17475 {
17476     (void) StopClockTimer();
17477     if (appData.icsActive) {
17478         whiteTimeRemaining = blackTimeRemaining = 0;
17479     } else if (searchTime) {
17480         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17481         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17482     } else { /* [HGM] correct new time quote for time odds */
17483         whiteTC = blackTC = fullTimeControlString;
17484         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17485         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17486     }
17487     if (whiteFlag || blackFlag) {
17488         DisplayTitle("");
17489         whiteFlag = blackFlag = FALSE;
17490     }
17491     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17492     DisplayBothClocks();
17493     adjustedClock = FALSE;
17494 }
17495
17496 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17497
17498 /* Decrement running clock by amount of time that has passed */
17499 void
17500 DecrementClocks ()
17501 {
17502     long timeRemaining;
17503     long lastTickLength, fudge;
17504     TimeMark now;
17505
17506     if (!appData.clockMode) return;
17507     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17508
17509     GetTimeMark(&now);
17510
17511     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17512
17513     /* Fudge if we woke up a little too soon */
17514     fudge = intendedTickLength - lastTickLength;
17515     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17516
17517     if (WhiteOnMove(forwardMostMove)) {
17518         if(whiteNPS >= 0) lastTickLength = 0;
17519         timeRemaining = whiteTimeRemaining -= lastTickLength;
17520         if(timeRemaining < 0 && !appData.icsActive) {
17521             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17522             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17523                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17524                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17525             }
17526         }
17527         DisplayWhiteClock(whiteTimeRemaining - fudge,
17528                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17529     } else {
17530         if(blackNPS >= 0) lastTickLength = 0;
17531         timeRemaining = blackTimeRemaining -= lastTickLength;
17532         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17533             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17534             if(suddenDeath) {
17535                 blackStartMove = forwardMostMove;
17536                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17537             }
17538         }
17539         DisplayBlackClock(blackTimeRemaining - fudge,
17540                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17541     }
17542     if (CheckFlags()) return;
17543
17544     if(twoBoards) { // count down secondary board's clocks as well
17545         activePartnerTime -= lastTickLength;
17546         partnerUp = 1;
17547         if(activePartner == 'W')
17548             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17549         else
17550             DisplayBlackClock(activePartnerTime, TRUE);
17551         partnerUp = 0;
17552     }
17553
17554     tickStartTM = now;
17555     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17556     StartClockTimer(intendedTickLength);
17557
17558     /* if the time remaining has fallen below the alarm threshold, sound the
17559      * alarm. if the alarm has sounded and (due to a takeback or time control
17560      * with increment) the time remaining has increased to a level above the
17561      * threshold, reset the alarm so it can sound again.
17562      */
17563
17564     if (appData.icsActive && appData.icsAlarm) {
17565
17566         /* make sure we are dealing with the user's clock */
17567         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17568                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17569            )) return;
17570
17571         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17572             alarmSounded = FALSE;
17573         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17574             PlayAlarmSound();
17575             alarmSounded = TRUE;
17576         }
17577     }
17578 }
17579
17580
17581 /* A player has just moved, so stop the previously running
17582    clock and (if in clock mode) start the other one.
17583    We redisplay both clocks in case we're in ICS mode, because
17584    ICS gives us an update to both clocks after every move.
17585    Note that this routine is called *after* forwardMostMove
17586    is updated, so the last fractional tick must be subtracted
17587    from the color that is *not* on move now.
17588 */
17589 void
17590 SwitchClocks (int newMoveNr)
17591 {
17592     long lastTickLength;
17593     TimeMark now;
17594     int flagged = FALSE;
17595
17596     GetTimeMark(&now);
17597
17598     if (StopClockTimer() && appData.clockMode) {
17599         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17600         if (!WhiteOnMove(forwardMostMove)) {
17601             if(blackNPS >= 0) lastTickLength = 0;
17602             blackTimeRemaining -= lastTickLength;
17603            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17604 //         if(pvInfoList[forwardMostMove].time == -1)
17605                  pvInfoList[forwardMostMove].time =               // use GUI time
17606                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17607         } else {
17608            if(whiteNPS >= 0) lastTickLength = 0;
17609            whiteTimeRemaining -= lastTickLength;
17610            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17611 //         if(pvInfoList[forwardMostMove].time == -1)
17612                  pvInfoList[forwardMostMove].time =
17613                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17614         }
17615         flagged = CheckFlags();
17616     }
17617     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17618     CheckTimeControl();
17619
17620     if (flagged || !appData.clockMode) return;
17621
17622     switch (gameMode) {
17623       case MachinePlaysBlack:
17624       case MachinePlaysWhite:
17625       case BeginningOfGame:
17626         if (pausing) return;
17627         break;
17628
17629       case EditGame:
17630       case PlayFromGameFile:
17631       case IcsExamining:
17632         return;
17633
17634       default:
17635         break;
17636     }
17637
17638     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17639         if(WhiteOnMove(forwardMostMove))
17640              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17641         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17642     }
17643
17644     tickStartTM = now;
17645     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17646       whiteTimeRemaining : blackTimeRemaining);
17647     StartClockTimer(intendedTickLength);
17648 }
17649
17650
17651 /* Stop both clocks */
17652 void
17653 StopClocks ()
17654 {
17655     long lastTickLength;
17656     TimeMark now;
17657
17658     if (!StopClockTimer()) return;
17659     if (!appData.clockMode) return;
17660
17661     GetTimeMark(&now);
17662
17663     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17664     if (WhiteOnMove(forwardMostMove)) {
17665         if(whiteNPS >= 0) lastTickLength = 0;
17666         whiteTimeRemaining -= lastTickLength;
17667         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17668     } else {
17669         if(blackNPS >= 0) lastTickLength = 0;
17670         blackTimeRemaining -= lastTickLength;
17671         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17672     }
17673     CheckFlags();
17674 }
17675
17676 /* Start clock of player on move.  Time may have been reset, so
17677    if clock is already running, stop and restart it. */
17678 void
17679 StartClocks ()
17680 {
17681     (void) StopClockTimer(); /* in case it was running already */
17682     DisplayBothClocks();
17683     if (CheckFlags()) return;
17684
17685     if (!appData.clockMode) return;
17686     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17687
17688     GetTimeMark(&tickStartTM);
17689     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17690       whiteTimeRemaining : blackTimeRemaining);
17691
17692    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17693     whiteNPS = blackNPS = -1;
17694     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17695        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17696         whiteNPS = first.nps;
17697     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17698        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17699         blackNPS = first.nps;
17700     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17701         whiteNPS = second.nps;
17702     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17703         blackNPS = second.nps;
17704     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17705
17706     StartClockTimer(intendedTickLength);
17707 }
17708
17709 char *
17710 TimeString (long ms)
17711 {
17712     long second, minute, hour, day;
17713     char *sign = "";
17714     static char buf[32];
17715
17716     if (ms > 0 && ms <= 9900) {
17717       /* convert milliseconds to tenths, rounding up */
17718       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17719
17720       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17721       return buf;
17722     }
17723
17724     /* convert milliseconds to seconds, rounding up */
17725     /* use floating point to avoid strangeness of integer division
17726        with negative dividends on many machines */
17727     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17728
17729     if (second < 0) {
17730         sign = "-";
17731         second = -second;
17732     }
17733
17734     day = second / (60 * 60 * 24);
17735     second = second % (60 * 60 * 24);
17736     hour = second / (60 * 60);
17737     second = second % (60 * 60);
17738     minute = second / 60;
17739     second = second % 60;
17740
17741     if (day > 0)
17742       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17743               sign, day, hour, minute, second);
17744     else if (hour > 0)
17745       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17746     else
17747       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17748
17749     return buf;
17750 }
17751
17752
17753 /*
17754  * This is necessary because some C libraries aren't ANSI C compliant yet.
17755  */
17756 char *
17757 StrStr (char *string, char *match)
17758 {
17759     int i, length;
17760
17761     length = strlen(match);
17762
17763     for (i = strlen(string) - length; i >= 0; i--, string++)
17764       if (!strncmp(match, string, length))
17765         return string;
17766
17767     return NULL;
17768 }
17769
17770 char *
17771 StrCaseStr (char *string, char *match)
17772 {
17773     int i, j, length;
17774
17775     length = strlen(match);
17776
17777     for (i = strlen(string) - length; i >= 0; i--, string++) {
17778         for (j = 0; j < length; j++) {
17779             if (ToLower(match[j]) != ToLower(string[j]))
17780               break;
17781         }
17782         if (j == length) return string;
17783     }
17784
17785     return NULL;
17786 }
17787
17788 #ifndef _amigados
17789 int
17790 StrCaseCmp (char *s1, char *s2)
17791 {
17792     char c1, c2;
17793
17794     for (;;) {
17795         c1 = ToLower(*s1++);
17796         c2 = ToLower(*s2++);
17797         if (c1 > c2) return 1;
17798         if (c1 < c2) return -1;
17799         if (c1 == NULLCHAR) return 0;
17800     }
17801 }
17802
17803
17804 int
17805 ToLower (int c)
17806 {
17807     return isupper(c) ? tolower(c) : c;
17808 }
17809
17810
17811 int
17812 ToUpper (int c)
17813 {
17814     return islower(c) ? toupper(c) : c;
17815 }
17816 #endif /* !_amigados    */
17817
17818 char *
17819 StrSave (char *s)
17820 {
17821   char *ret;
17822
17823   if ((ret = (char *) malloc(strlen(s) + 1)))
17824     {
17825       safeStrCpy(ret, s, strlen(s)+1);
17826     }
17827   return ret;
17828 }
17829
17830 char *
17831 StrSavePtr (char *s, char **savePtr)
17832 {
17833     if (*savePtr) {
17834         free(*savePtr);
17835     }
17836     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17837       safeStrCpy(*savePtr, s, strlen(s)+1);
17838     }
17839     return(*savePtr);
17840 }
17841
17842 char *
17843 PGNDate ()
17844 {
17845     time_t clock;
17846     struct tm *tm;
17847     char buf[MSG_SIZ];
17848
17849     clock = time((time_t *)NULL);
17850     tm = localtime(&clock);
17851     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17852             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17853     return StrSave(buf);
17854 }
17855
17856
17857 char *
17858 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17859 {
17860     int i, j, fromX, fromY, toX, toY;
17861     int whiteToPlay;
17862     char buf[MSG_SIZ];
17863     char *p, *q;
17864     int emptycount;
17865     ChessSquare piece;
17866
17867     whiteToPlay = (gameMode == EditPosition) ?
17868       !blackPlaysFirst : (move % 2 == 0);
17869     p = buf;
17870
17871     /* Piece placement data */
17872     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17873         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17874         emptycount = 0;
17875         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17876             if (boards[move][i][j] == EmptySquare) {
17877                 emptycount++;
17878             } else { ChessSquare piece = boards[move][i][j];
17879                 if (emptycount > 0) {
17880                     if(emptycount<10) /* [HGM] can be >= 10 */
17881                         *p++ = '0' + emptycount;
17882                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17883                     emptycount = 0;
17884                 }
17885                 if(PieceToChar(piece) == '+') {
17886                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17887                     *p++ = '+';
17888                     piece = (ChessSquare)(CHUDEMOTED piece);
17889                 }
17890                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17891                 if(*p = PieceSuffix(piece)) p++;
17892                 if(p[-1] == '~') {
17893                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17894                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17895                     *p++ = '~';
17896                 }
17897             }
17898         }
17899         if (emptycount > 0) {
17900             if(emptycount<10) /* [HGM] can be >= 10 */
17901                 *p++ = '0' + emptycount;
17902             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17903             emptycount = 0;
17904         }
17905         *p++ = '/';
17906     }
17907     *(p - 1) = ' ';
17908
17909     /* [HGM] print Crazyhouse or Shogi holdings */
17910     if( gameInfo.holdingsWidth ) {
17911         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17912         q = p;
17913         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17914             piece = boards[move][i][BOARD_WIDTH-1];
17915             if( piece != EmptySquare )
17916               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17917                   *p++ = PieceToChar(piece);
17918         }
17919         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17920             piece = boards[move][BOARD_HEIGHT-i-1][0];
17921             if( piece != EmptySquare )
17922               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17923                   *p++ = PieceToChar(piece);
17924         }
17925
17926         if( q == p ) *p++ = '-';
17927         *p++ = ']';
17928         *p++ = ' ';
17929     }
17930
17931     /* Active color */
17932     *p++ = whiteToPlay ? 'w' : 'b';
17933     *p++ = ' ';
17934
17935   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17936     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17937   } else {
17938   if(nrCastlingRights) {
17939      int handW=0, handB=0;
17940      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17941         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17942         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17943      }
17944      q = p;
17945      if(appData.fischerCastling) {
17946         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17947            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17948                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17949         } else {
17950        /* [HGM] write directly from rights */
17951            if(boards[move][CASTLING][2] != NoRights &&
17952               boards[move][CASTLING][0] != NoRights   )
17953                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17954            if(boards[move][CASTLING][2] != NoRights &&
17955               boards[move][CASTLING][1] != NoRights   )
17956                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17957         }
17958         if(handB) {
17959            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17960                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17961         } else {
17962            if(boards[move][CASTLING][5] != NoRights &&
17963               boards[move][CASTLING][3] != NoRights   )
17964                 *p++ = boards[move][CASTLING][3] + AAA;
17965            if(boards[move][CASTLING][5] != NoRights &&
17966               boards[move][CASTLING][4] != NoRights   )
17967                 *p++ = boards[move][CASTLING][4] + AAA;
17968         }
17969      } else {
17970
17971         /* [HGM] write true castling rights */
17972         if( nrCastlingRights == 6 ) {
17973             int q, k=0;
17974             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17975                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17976             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17977                  boards[move][CASTLING][2] != NoRights  );
17978             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17979                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17980                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17981                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17982             }
17983             if(q) *p++ = 'Q';
17984             k = 0;
17985             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17986                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17987             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17988                  boards[move][CASTLING][5] != NoRights  );
17989             if(handB) {
17990                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17991                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17992                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17993             }
17994             if(q) *p++ = 'q';
17995         }
17996      }
17997      if (q == p) *p++ = '-'; /* No castling rights */
17998      *p++ = ' ';
17999   }
18000
18001   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18002      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18003      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18004     /* En passant target square */
18005     if (move > backwardMostMove) {
18006         fromX = moveList[move - 1][0] - AAA;
18007         fromY = moveList[move - 1][1] - ONE;
18008         toX = moveList[move - 1][2] - AAA;
18009         toY = moveList[move - 1][3] - ONE;
18010         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18011             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18012             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18013             fromX == toX) {
18014             /* 2-square pawn move just happened */
18015             *p++ = toX + AAA;
18016             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18017         } else {
18018             *p++ = '-';
18019         }
18020     } else if(move == backwardMostMove) {
18021         // [HGM] perhaps we should always do it like this, and forget the above?
18022         if((signed char)boards[move][EP_STATUS] >= 0) {
18023             *p++ = boards[move][EP_STATUS] + AAA;
18024             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18025         } else {
18026             *p++ = '-';
18027         }
18028     } else {
18029         *p++ = '-';
18030     }
18031     *p++ = ' ';
18032   }
18033   }
18034
18035     if(moveCounts)
18036     {   int i = 0, j=move;
18037
18038         /* [HGM] find reversible plies */
18039         if (appData.debugMode) { int k;
18040             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18041             for(k=backwardMostMove; k<=forwardMostMove; k++)
18042                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18043
18044         }
18045
18046         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18047         if( j == backwardMostMove ) i += initialRulePlies;
18048         sprintf(p, "%d ", i);
18049         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18050
18051         /* Fullmove number */
18052         sprintf(p, "%d", (move / 2) + 1);
18053     } else *--p = NULLCHAR;
18054
18055     return StrSave(buf);
18056 }
18057
18058 Boolean
18059 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18060 {
18061     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18062     char *p, c;
18063     int emptycount, virgin[BOARD_FILES];
18064     ChessSquare piece;
18065
18066     p = fen;
18067
18068     /* Piece placement data */
18069     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18070         j = 0;
18071         for (;;) {
18072             if (*p == '/' || *p == ' ' || *p == '[' ) {
18073                 if(j > w) w = j;
18074                 emptycount = gameInfo.boardWidth - j;
18075                 while (emptycount--)
18076                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18077                 if (*p == '/') p++;
18078                 else if(autoSize) { // we stumbled unexpectedly into end of board
18079                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18080                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18081                     }
18082                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18083                 }
18084                 break;
18085 #if(BOARD_FILES >= 10)*0
18086             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18087                 p++; emptycount=10;
18088                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18089                 while (emptycount--)
18090                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18091 #endif
18092             } else if (*p == '*') {
18093                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18094             } else if (isdigit(*p)) {
18095                 emptycount = *p++ - '0';
18096                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18097                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18098                 while (emptycount--)
18099                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18100             } else if (*p == '<') {
18101                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18102                 else if (i != 0 || !shuffle) return FALSE;
18103                 p++;
18104             } else if (shuffle && *p == '>') {
18105                 p++; // for now ignore closing shuffle range, and assume rank-end
18106             } else if (*p == '?') {
18107                 if (j >= gameInfo.boardWidth) return FALSE;
18108                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18109                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18110             } else if (*p == '+' || isalpha(*p)) {
18111                 char *q, *s = SUFFIXES;
18112                 if (j >= gameInfo.boardWidth) return FALSE;
18113                 if(*p=='+') {
18114                     char c = *++p;
18115                     if(q = strchr(s, p[1])) p++;
18116                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18117                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18118                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18119                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18120                 } else {
18121                     char c = *p++;
18122                     if(q = strchr(s, *p)) p++;
18123                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18124                 }
18125
18126                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18127                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18128                     piece = (ChessSquare) (PROMOTED piece);
18129                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18130                     p++;
18131                 }
18132                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18133                 if(piece == WhiteKing) wKingRank = i;
18134                 if(piece == BlackKing) bKingRank = i;
18135             } else {
18136                 return FALSE;
18137             }
18138         }
18139     }
18140     while (*p == '/' || *p == ' ') p++;
18141
18142     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18143
18144     /* [HGM] by default clear Crazyhouse holdings, if present */
18145     if(gameInfo.holdingsWidth) {
18146        for(i=0; i<BOARD_HEIGHT; i++) {
18147            board[i][0]             = EmptySquare; /* black holdings */
18148            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18149            board[i][1]             = (ChessSquare) 0; /* black counts */
18150            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18151        }
18152     }
18153
18154     /* [HGM] look for Crazyhouse holdings here */
18155     while(*p==' ') p++;
18156     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18157         int swap=0, wcnt=0, bcnt=0;
18158         if(*p == '[') p++;
18159         if(*p == '<') swap++, p++;
18160         if(*p == '-' ) p++; /* empty holdings */ else {
18161             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18162             /* if we would allow FEN reading to set board size, we would   */
18163             /* have to add holdings and shift the board read so far here   */
18164             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18165                 p++;
18166                 if((int) piece >= (int) BlackPawn ) {
18167                     i = (int)piece - (int)BlackPawn;
18168                     i = PieceToNumber((ChessSquare)i);
18169                     if( i >= gameInfo.holdingsSize ) return FALSE;
18170                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18171                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18172                     bcnt++;
18173                 } else {
18174                     i = (int)piece - (int)WhitePawn;
18175                     i = PieceToNumber((ChessSquare)i);
18176                     if( i >= gameInfo.holdingsSize ) return FALSE;
18177                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18178                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18179                     wcnt++;
18180                 }
18181             }
18182             if(subst) { // substitute back-rank question marks by holdings pieces
18183                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18184                     int k, m, n = bcnt + 1;
18185                     if(board[0][j] == ClearBoard) {
18186                         if(!wcnt) return FALSE;
18187                         n = rand() % wcnt;
18188                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18189                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18190                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18191                             break;
18192                         }
18193                     }
18194                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18195                         if(!bcnt) return FALSE;
18196                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18197                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18198                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18199                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18200                             break;
18201                         }
18202                     }
18203                 }
18204                 subst = 0;
18205             }
18206         }
18207         if(*p == ']') p++;
18208     }
18209
18210     if(subst) return FALSE; // substitution requested, but no holdings
18211
18212     while(*p == ' ') p++;
18213
18214     /* Active color */
18215     c = *p++;
18216     if(appData.colorNickNames) {
18217       if( c == appData.colorNickNames[0] ) c = 'w'; else
18218       if( c == appData.colorNickNames[1] ) c = 'b';
18219     }
18220     switch (c) {
18221       case 'w':
18222         *blackPlaysFirst = FALSE;
18223         break;
18224       case 'b':
18225         *blackPlaysFirst = TRUE;
18226         break;
18227       default:
18228         return FALSE;
18229     }
18230
18231     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18232     /* return the extra info in global variiables             */
18233
18234     while(*p==' ') p++;
18235
18236     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18237         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18238         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18239     }
18240
18241     /* set defaults in case FEN is incomplete */
18242     board[EP_STATUS] = EP_UNKNOWN;
18243     for(i=0; i<nrCastlingRights; i++ ) {
18244         board[CASTLING][i] =
18245             appData.fischerCastling ? NoRights : initialRights[i];
18246     }   /* assume possible unless obviously impossible */
18247     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18248     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18249     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18250                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18251     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18252     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18253     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18254                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18255     FENrulePlies = 0;
18256
18257     if(nrCastlingRights) {
18258       int fischer = 0;
18259       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18260       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18261           /* castling indicator present, so default becomes no castlings */
18262           for(i=0; i<nrCastlingRights; i++ ) {
18263                  board[CASTLING][i] = NoRights;
18264           }
18265       }
18266       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18267              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18268              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18269              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18270         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18271
18272         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18273             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18274             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18275         }
18276         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18277             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18278         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18279                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18280         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18281                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18282         switch(c) {
18283           case'K':
18284               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18285               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18286               board[CASTLING][2] = whiteKingFile;
18287               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18288               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18289               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18290               break;
18291           case'Q':
18292               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18293               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18294               board[CASTLING][2] = whiteKingFile;
18295               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18296               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18297               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18298               break;
18299           case'k':
18300               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18301               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18302               board[CASTLING][5] = blackKingFile;
18303               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18304               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18305               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18306               break;
18307           case'q':
18308               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18309               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18310               board[CASTLING][5] = blackKingFile;
18311               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18312               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18313               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18314           case '-':
18315               break;
18316           default: /* FRC castlings */
18317               if(c >= 'a') { /* black rights */
18318                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18319                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18320                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18321                   if(i == BOARD_RGHT) break;
18322                   board[CASTLING][5] = i;
18323                   c -= AAA;
18324                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18325                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18326                   if(c > i)
18327                       board[CASTLING][3] = c;
18328                   else
18329                       board[CASTLING][4] = c;
18330               } else { /* white rights */
18331                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18332                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18333                     if(board[0][i] == WhiteKing) break;
18334                   if(i == BOARD_RGHT) break;
18335                   board[CASTLING][2] = i;
18336                   c -= AAA - 'a' + 'A';
18337                   if(board[0][c] >= WhiteKing) break;
18338                   if(c > i)
18339                       board[CASTLING][0] = c;
18340                   else
18341                       board[CASTLING][1] = c;
18342               }
18343         }
18344       }
18345       for(i=0; i<nrCastlingRights; i++)
18346         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18347       if(gameInfo.variant == VariantSChess)
18348         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18349       if(fischer && shuffle) appData.fischerCastling = TRUE;
18350     if (appData.debugMode) {
18351         fprintf(debugFP, "FEN castling rights:");
18352         for(i=0; i<nrCastlingRights; i++)
18353         fprintf(debugFP, " %d", board[CASTLING][i]);
18354         fprintf(debugFP, "\n");
18355     }
18356
18357       while(*p==' ') p++;
18358     }
18359
18360     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18361
18362     /* read e.p. field in games that know e.p. capture */
18363     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18364        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18365        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18366       if(*p=='-') {
18367         p++; board[EP_STATUS] = EP_NONE;
18368       } else {
18369          char c = *p++ - AAA;
18370
18371          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18372          if(*p >= '0' && *p <='9') p++;
18373          board[EP_STATUS] = c;
18374       }
18375     }
18376
18377
18378     if(sscanf(p, "%d", &i) == 1) {
18379         FENrulePlies = i; /* 50-move ply counter */
18380         /* (The move number is still ignored)    */
18381     }
18382
18383     return TRUE;
18384 }
18385
18386 void
18387 EditPositionPasteFEN (char *fen)
18388 {
18389   if (fen != NULL) {
18390     Board initial_position;
18391
18392     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18393       DisplayError(_("Bad FEN position in clipboard"), 0);
18394       return ;
18395     } else {
18396       int savedBlackPlaysFirst = blackPlaysFirst;
18397       EditPositionEvent();
18398       blackPlaysFirst = savedBlackPlaysFirst;
18399       CopyBoard(boards[0], initial_position);
18400       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18401       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18402       DisplayBothClocks();
18403       DrawPosition(FALSE, boards[currentMove]);
18404     }
18405   }
18406 }
18407
18408 static char cseq[12] = "\\   ";
18409
18410 Boolean
18411 set_cont_sequence (char *new_seq)
18412 {
18413     int len;
18414     Boolean ret;
18415
18416     // handle bad attempts to set the sequence
18417         if (!new_seq)
18418                 return 0; // acceptable error - no debug
18419
18420     len = strlen(new_seq);
18421     ret = (len > 0) && (len < sizeof(cseq));
18422     if (ret)
18423       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18424     else if (appData.debugMode)
18425       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18426     return ret;
18427 }
18428
18429 /*
18430     reformat a source message so words don't cross the width boundary.  internal
18431     newlines are not removed.  returns the wrapped size (no null character unless
18432     included in source message).  If dest is NULL, only calculate the size required
18433     for the dest buffer.  lp argument indicats line position upon entry, and it's
18434     passed back upon exit.
18435 */
18436 int
18437 wrap (char *dest, char *src, int count, int width, int *lp)
18438 {
18439     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18440
18441     cseq_len = strlen(cseq);
18442     old_line = line = *lp;
18443     ansi = len = clen = 0;
18444
18445     for (i=0; i < count; i++)
18446     {
18447         if (src[i] == '\033')
18448             ansi = 1;
18449
18450         // if we hit the width, back up
18451         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18452         {
18453             // store i & len in case the word is too long
18454             old_i = i, old_len = len;
18455
18456             // find the end of the last word
18457             while (i && src[i] != ' ' && src[i] != '\n')
18458             {
18459                 i--;
18460                 len--;
18461             }
18462
18463             // word too long?  restore i & len before splitting it
18464             if ((old_i-i+clen) >= width)
18465             {
18466                 i = old_i;
18467                 len = old_len;
18468             }
18469
18470             // extra space?
18471             if (i && src[i-1] == ' ')
18472                 len--;
18473
18474             if (src[i] != ' ' && src[i] != '\n')
18475             {
18476                 i--;
18477                 if (len)
18478                     len--;
18479             }
18480
18481             // now append the newline and continuation sequence
18482             if (dest)
18483                 dest[len] = '\n';
18484             len++;
18485             if (dest)
18486                 strncpy(dest+len, cseq, cseq_len);
18487             len += cseq_len;
18488             line = cseq_len;
18489             clen = cseq_len;
18490             continue;
18491         }
18492
18493         if (dest)
18494             dest[len] = src[i];
18495         len++;
18496         if (!ansi)
18497             line++;
18498         if (src[i] == '\n')
18499             line = 0;
18500         if (src[i] == 'm')
18501             ansi = 0;
18502     }
18503     if (dest && appData.debugMode)
18504     {
18505         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18506             count, width, line, len, *lp);
18507         show_bytes(debugFP, src, count);
18508         fprintf(debugFP, "\ndest: ");
18509         show_bytes(debugFP, dest, len);
18510         fprintf(debugFP, "\n");
18511     }
18512     *lp = dest ? line : old_line;
18513
18514     return len;
18515 }
18516
18517 // [HGM] vari: routines for shelving variations
18518 Boolean modeRestore = FALSE;
18519
18520 void
18521 PushInner (int firstMove, int lastMove)
18522 {
18523         int i, j, nrMoves = lastMove - firstMove;
18524
18525         // push current tail of game on stack
18526         savedResult[storedGames] = gameInfo.result;
18527         savedDetails[storedGames] = gameInfo.resultDetails;
18528         gameInfo.resultDetails = NULL;
18529         savedFirst[storedGames] = firstMove;
18530         savedLast [storedGames] = lastMove;
18531         savedFramePtr[storedGames] = framePtr;
18532         framePtr -= nrMoves; // reserve space for the boards
18533         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18534             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18535             for(j=0; j<MOVE_LEN; j++)
18536                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18537             for(j=0; j<2*MOVE_LEN; j++)
18538                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18539             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18540             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18541             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18542             pvInfoList[firstMove+i-1].depth = 0;
18543             commentList[framePtr+i] = commentList[firstMove+i];
18544             commentList[firstMove+i] = NULL;
18545         }
18546
18547         storedGames++;
18548         forwardMostMove = firstMove; // truncate game so we can start variation
18549 }
18550
18551 void
18552 PushTail (int firstMove, int lastMove)
18553 {
18554         if(appData.icsActive) { // only in local mode
18555                 forwardMostMove = currentMove; // mimic old ICS behavior
18556                 return;
18557         }
18558         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18559
18560         PushInner(firstMove, lastMove);
18561         if(storedGames == 1) GreyRevert(FALSE);
18562         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18563 }
18564
18565 void
18566 PopInner (Boolean annotate)
18567 {
18568         int i, j, nrMoves;
18569         char buf[8000], moveBuf[20];
18570
18571         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18572         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18573         nrMoves = savedLast[storedGames] - currentMove;
18574         if(annotate) {
18575                 int cnt = 10;
18576                 if(!WhiteOnMove(currentMove))
18577                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18578                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18579                 for(i=currentMove; i<forwardMostMove; i++) {
18580                         if(WhiteOnMove(i))
18581                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18582                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18583                         strcat(buf, moveBuf);
18584                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18585                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18586                 }
18587                 strcat(buf, ")");
18588         }
18589         for(i=1; i<=nrMoves; i++) { // copy last variation back
18590             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18591             for(j=0; j<MOVE_LEN; j++)
18592                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18593             for(j=0; j<2*MOVE_LEN; j++)
18594                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18595             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18596             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18597             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18598             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18599             commentList[currentMove+i] = commentList[framePtr+i];
18600             commentList[framePtr+i] = NULL;
18601         }
18602         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18603         framePtr = savedFramePtr[storedGames];
18604         gameInfo.result = savedResult[storedGames];
18605         if(gameInfo.resultDetails != NULL) {
18606             free(gameInfo.resultDetails);
18607       }
18608         gameInfo.resultDetails = savedDetails[storedGames];
18609         forwardMostMove = currentMove + nrMoves;
18610 }
18611
18612 Boolean
18613 PopTail (Boolean annotate)
18614 {
18615         if(appData.icsActive) return FALSE; // only in local mode
18616         if(!storedGames) return FALSE; // sanity
18617         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18618
18619         PopInner(annotate);
18620         if(currentMove < forwardMostMove) ForwardEvent(); else
18621         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18622
18623         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18624         return TRUE;
18625 }
18626
18627 void
18628 CleanupTail ()
18629 {       // remove all shelved variations
18630         int i;
18631         for(i=0; i<storedGames; i++) {
18632             if(savedDetails[i])
18633                 free(savedDetails[i]);
18634             savedDetails[i] = NULL;
18635         }
18636         for(i=framePtr; i<MAX_MOVES; i++) {
18637                 if(commentList[i]) free(commentList[i]);
18638                 commentList[i] = NULL;
18639         }
18640         framePtr = MAX_MOVES-1;
18641         storedGames = 0;
18642 }
18643
18644 void
18645 LoadVariation (int index, char *text)
18646 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18647         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18648         int level = 0, move;
18649
18650         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18651         // first find outermost bracketing variation
18652         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18653             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18654                 if(*p == '{') wait = '}'; else
18655                 if(*p == '[') wait = ']'; else
18656                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18657                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18658             }
18659             if(*p == wait) wait = NULLCHAR; // closing ]} found
18660             p++;
18661         }
18662         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18663         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18664         end[1] = NULLCHAR; // clip off comment beyond variation
18665         ToNrEvent(currentMove-1);
18666         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18667         // kludge: use ParsePV() to append variation to game
18668         move = currentMove;
18669         ParsePV(start, TRUE, TRUE);
18670         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18671         ClearPremoveHighlights();
18672         CommentPopDown();
18673         ToNrEvent(currentMove+1);
18674 }
18675
18676 void
18677 LoadTheme ()
18678 {
18679     char *p, *q, buf[MSG_SIZ];
18680     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18681         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18682         ParseArgsFromString(buf);
18683         ActivateTheme(TRUE); // also redo colors
18684         return;
18685     }
18686     p = nickName;
18687     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18688     {
18689         int len;
18690         q = appData.themeNames;
18691         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18692       if(appData.useBitmaps) {
18693         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18694                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18695                 appData.liteBackTextureMode,
18696                 appData.darkBackTextureMode );
18697       } else {
18698         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18699                 Col2Text(2),   // lightSquareColor
18700                 Col2Text(3) ); // darkSquareColor
18701       }
18702       if(appData.useBorder) {
18703         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18704                 appData.border);
18705       } else {
18706         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18707       }
18708       if(appData.useFont) {
18709         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18710                 appData.renderPiecesWithFont,
18711                 appData.fontToPieceTable,
18712                 Col2Text(9),    // appData.fontBackColorWhite
18713                 Col2Text(10) ); // appData.fontForeColorBlack
18714       } else {
18715         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18716                 appData.pieceDirectory);
18717         if(!appData.pieceDirectory[0])
18718           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18719                 Col2Text(0),   // whitePieceColor
18720                 Col2Text(1) ); // blackPieceColor
18721       }
18722       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18723                 Col2Text(4),   // highlightSquareColor
18724                 Col2Text(5) ); // premoveHighlightColor
18725         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18726         if(insert != q) insert[-1] = NULLCHAR;
18727         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18728         if(q)   free(q);
18729     }
18730     ActivateTheme(FALSE);
18731 }