Always accept piece commands for Falcon and Cobra
[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.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.chessProgram) { // [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];
2112     int len;
2113
2114     if (!e) return v;
2115
2116     /* [HGM] skip over optional board-size prefixes */
2117     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2118         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
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 && 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           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5145                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5146                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5147                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5148           SendToProgram(buf, cps);
5149       } else
5150       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5151         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5152           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5153           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5154                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5155         } else
5156           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5157                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5158         SendToProgram(buf, cps);
5159       }
5160       else SendToProgram(moveList[moveNum], cps);
5161       /* End of additions by Tord */
5162     }
5163
5164     /* [HGM] setting up the opening has brought engine in force mode! */
5165     /*       Send 'go' if we are in a mode where machine should play. */
5166     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5167         (gameMode == TwoMachinesPlay   ||
5168 #if ZIPPY
5169          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5170 #endif
5171          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5172         SendToProgram("go\n", cps);
5173   if (appData.debugMode) {
5174     fprintf(debugFP, "(extra)\n");
5175   }
5176     }
5177     setboardSpoiledMachineBlack = 0;
5178 }
5179
5180 void
5181 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5182 {
5183     char user_move[MSG_SIZ];
5184     char suffix[4];
5185
5186     if(gameInfo.variant == VariantSChess && promoChar) {
5187         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5188         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5189     } else suffix[0] = NULLCHAR;
5190
5191     switch (moveType) {
5192       default:
5193         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5194                 (int)moveType, fromX, fromY, toX, toY);
5195         DisplayError(user_move + strlen("say "), 0);
5196         break;
5197       case WhiteKingSideCastle:
5198       case BlackKingSideCastle:
5199       case WhiteQueenSideCastleWild:
5200       case BlackQueenSideCastleWild:
5201       /* PUSH Fabien */
5202       case WhiteHSideCastleFR:
5203       case BlackHSideCastleFR:
5204       /* POP Fabien */
5205         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5206         break;
5207       case WhiteQueenSideCastle:
5208       case BlackQueenSideCastle:
5209       case WhiteKingSideCastleWild:
5210       case BlackKingSideCastleWild:
5211       /* PUSH Fabien */
5212       case WhiteASideCastleFR:
5213       case BlackASideCastleFR:
5214       /* POP Fabien */
5215         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5216         break;
5217       case WhiteNonPromotion:
5218       case BlackNonPromotion:
5219         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5220         break;
5221       case WhitePromotion:
5222       case BlackPromotion:
5223         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5224            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5225           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5227                 PieceToChar(WhiteFerz));
5228         else if(gameInfo.variant == VariantGreat)
5229           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5231                 PieceToChar(WhiteMan));
5232         else
5233           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5234                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5235                 promoChar);
5236         break;
5237       case WhiteDrop:
5238       case BlackDrop:
5239       drop:
5240         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5241                  ToUpper(PieceToChar((ChessSquare) fromX)),
5242                  AAA + toX, ONE + toY);
5243         break;
5244       case IllegalMove:  /* could be a variant we don't quite understand */
5245         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5246       case NormalMove:
5247       case WhiteCapturesEnPassant:
5248       case BlackCapturesEnPassant:
5249         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5250                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5251         break;
5252     }
5253     SendToICS(user_move);
5254     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5255         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5256 }
5257
5258 void
5259 UploadGameEvent ()
5260 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5261     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5262     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5263     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5264       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5265       return;
5266     }
5267     if(gameMode != IcsExamining) { // is this ever not the case?
5268         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5269
5270         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5271           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5272         } else { // on FICS we must first go to general examine mode
5273           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5274         }
5275         if(gameInfo.variant != VariantNormal) {
5276             // try figure out wild number, as xboard names are not always valid on ICS
5277             for(i=1; i<=36; i++) {
5278               snprintf(buf, MSG_SIZ, "wild/%d", i);
5279                 if(StringToVariant(buf) == gameInfo.variant) break;
5280             }
5281             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5282             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5283             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5284         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5285         SendToICS(ics_prefix);
5286         SendToICS(buf);
5287         if(startedFromSetupPosition || backwardMostMove != 0) {
5288           fen = PositionToFEN(backwardMostMove, NULL, 1);
5289           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5290             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5291             SendToICS(buf);
5292           } else { // FICS: everything has to set by separate bsetup commands
5293             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5294             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5295             SendToICS(buf);
5296             if(!WhiteOnMove(backwardMostMove)) {
5297                 SendToICS("bsetup tomove black\n");
5298             }
5299             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5300             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5301             SendToICS(buf);
5302             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5303             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5304             SendToICS(buf);
5305             i = boards[backwardMostMove][EP_STATUS];
5306             if(i >= 0) { // set e.p.
5307               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5308                 SendToICS(buf);
5309             }
5310             bsetup++;
5311           }
5312         }
5313       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5314             SendToICS("bsetup done\n"); // switch to normal examining.
5315     }
5316     for(i = backwardMostMove; i<last; i++) {
5317         char buf[20];
5318         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5319         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5320             int len = strlen(moveList[i]);
5321             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5322             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5323         }
5324         SendToICS(buf);
5325     }
5326     SendToICS(ics_prefix);
5327     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5328 }
5329
5330 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5331
5332 void
5333 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5334 {
5335     if (rf == DROP_RANK) {
5336       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5337       sprintf(move, "%c@%c%c\n",
5338                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5339     } else {
5340         if (promoChar == 'x' || promoChar == NULLCHAR) {
5341           sprintf(move, "%c%c%c%c\n",
5342                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5343           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5344         } else {
5345             sprintf(move, "%c%c%c%c%c\n",
5346                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5347         }
5348     }
5349 }
5350
5351 void
5352 ProcessICSInitScript (FILE *f)
5353 {
5354     char buf[MSG_SIZ];
5355
5356     while (fgets(buf, MSG_SIZ, f)) {
5357         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5358     }
5359
5360     fclose(f);
5361 }
5362
5363
5364 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5365 int dragging;
5366 static ClickType lastClickType;
5367
5368 int
5369 Partner (ChessSquare *p)
5370 { // change piece into promotion partner if one shogi-promotes to the other
5371   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5372   ChessSquare partner;
5373   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5374   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5375   *p = partner;
5376   return 1;
5377 }
5378
5379 void
5380 Sweep (int step)
5381 {
5382     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5383     static int toggleFlag;
5384     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5385     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5386     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5387     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5388     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5389     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5390     do {
5391         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5392         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5393         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5394         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5395         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5396         if(!step) step = -1;
5397     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5398             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5399             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5400             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5401     if(toX >= 0) {
5402         int victim = boards[currentMove][toY][toX];
5403         boards[currentMove][toY][toX] = promoSweep;
5404         DrawPosition(FALSE, boards[currentMove]);
5405         boards[currentMove][toY][toX] = victim;
5406     } else
5407     ChangeDragPiece(promoSweep);
5408 }
5409
5410 int
5411 PromoScroll (int x, int y)
5412 {
5413   int step = 0;
5414
5415   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5416   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5417   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5418   if(!step) return FALSE;
5419   lastX = x; lastY = y;
5420   if((promoSweep < BlackPawn) == flipView) step = -step;
5421   if(step > 0) selectFlag = 1;
5422   if(!selectFlag) Sweep(step);
5423   return FALSE;
5424 }
5425
5426 void
5427 NextPiece (int step)
5428 {
5429     ChessSquare piece = boards[currentMove][toY][toX];
5430     do {
5431         pieceSweep -= step;
5432         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5433         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5434         if(!step) step = -1;
5435     } while(PieceToChar(pieceSweep) == '.');
5436     boards[currentMove][toY][toX] = pieceSweep;
5437     DrawPosition(FALSE, boards[currentMove]);
5438     boards[currentMove][toY][toX] = piece;
5439 }
5440 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5441 void
5442 AlphaRank (char *move, int n)
5443 {
5444 //    char *p = move, c; int x, y;
5445
5446     if (appData.debugMode) {
5447         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5448     }
5449
5450     if(move[1]=='*' &&
5451        move[2]>='0' && move[2]<='9' &&
5452        move[3]>='a' && move[3]<='x'    ) {
5453         move[1] = '@';
5454         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5455         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5456     } else
5457     if(move[0]>='0' && move[0]<='9' &&
5458        move[1]>='a' && move[1]<='x' &&
5459        move[2]>='0' && move[2]<='9' &&
5460        move[3]>='a' && move[3]<='x'    ) {
5461         /* input move, Shogi -> normal */
5462         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5463         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5464         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5465         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5466     } else
5467     if(move[1]=='@' &&
5468        move[3]>='0' && move[3]<='9' &&
5469        move[2]>='a' && move[2]<='x'    ) {
5470         move[1] = '*';
5471         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5472         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5473     } else
5474     if(
5475        move[0]>='a' && move[0]<='x' &&
5476        move[3]>='0' && move[3]<='9' &&
5477        move[2]>='a' && move[2]<='x'    ) {
5478          /* output move, normal -> Shogi */
5479         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5480         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5481         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5482         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5483         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5484     }
5485     if (appData.debugMode) {
5486         fprintf(debugFP, "   out = '%s'\n", move);
5487     }
5488 }
5489
5490 char yy_textstr[8000];
5491
5492 /* Parser for moves from gnuchess, ICS, or user typein box */
5493 Boolean
5494 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5495 {
5496     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5497
5498     switch (*moveType) {
5499       case WhitePromotion:
5500       case BlackPromotion:
5501       case WhiteNonPromotion:
5502       case BlackNonPromotion:
5503       case NormalMove:
5504       case FirstLeg:
5505       case WhiteCapturesEnPassant:
5506       case BlackCapturesEnPassant:
5507       case WhiteKingSideCastle:
5508       case WhiteQueenSideCastle:
5509       case BlackKingSideCastle:
5510       case BlackQueenSideCastle:
5511       case WhiteKingSideCastleWild:
5512       case WhiteQueenSideCastleWild:
5513       case BlackKingSideCastleWild:
5514       case BlackQueenSideCastleWild:
5515       /* Code added by Tord: */
5516       case WhiteHSideCastleFR:
5517       case WhiteASideCastleFR:
5518       case BlackHSideCastleFR:
5519       case BlackASideCastleFR:
5520       /* End of code added by Tord */
5521       case IllegalMove:         /* bug or odd chess variant */
5522         *fromX = currentMoveString[0] - AAA;
5523         *fromY = currentMoveString[1] - ONE;
5524         *toX = currentMoveString[2] - AAA;
5525         *toY = currentMoveString[3] - ONE;
5526         *promoChar = currentMoveString[4];
5527         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5528             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5529     if (appData.debugMode) {
5530         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5531     }
5532             *fromX = *fromY = *toX = *toY = 0;
5533             return FALSE;
5534         }
5535         if (appData.testLegality) {
5536           return (*moveType != IllegalMove);
5537         } else {
5538           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5539                          // [HGM] lion: if this is a double move we are less critical
5540                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5541         }
5542
5543       case WhiteDrop:
5544       case BlackDrop:
5545         *fromX = *moveType == WhiteDrop ?
5546           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5547           (int) CharToPiece(ToLower(currentMoveString[0]));
5548         *fromY = DROP_RANK;
5549         *toX = currentMoveString[2] - AAA;
5550         *toY = currentMoveString[3] - ONE;
5551         *promoChar = NULLCHAR;
5552         return TRUE;
5553
5554       case AmbiguousMove:
5555       case ImpossibleMove:
5556       case EndOfFile:
5557       case ElapsedTime:
5558       case Comment:
5559       case PGNTag:
5560       case NAG:
5561       case WhiteWins:
5562       case BlackWins:
5563       case GameIsDrawn:
5564       default:
5565     if (appData.debugMode) {
5566         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5567     }
5568         /* bug? */
5569         *fromX = *fromY = *toX = *toY = 0;
5570         *promoChar = NULLCHAR;
5571         return FALSE;
5572     }
5573 }
5574
5575 Boolean pushed = FALSE;
5576 char *lastParseAttempt;
5577
5578 void
5579 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5580 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5581   int fromX, fromY, toX, toY; char promoChar;
5582   ChessMove moveType;
5583   Boolean valid;
5584   int nr = 0;
5585
5586   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5587   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5588     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5589     pushed = TRUE;
5590   }
5591   endPV = forwardMostMove;
5592   do {
5593     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5594     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5595     lastParseAttempt = pv;
5596     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5597     if(!valid && nr == 0 &&
5598        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5599         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5600         // Hande case where played move is different from leading PV move
5601         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5602         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5603         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5604         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5605           endPV += 2; // if position different, keep this
5606           moveList[endPV-1][0] = fromX + AAA;
5607           moveList[endPV-1][1] = fromY + ONE;
5608           moveList[endPV-1][2] = toX + AAA;
5609           moveList[endPV-1][3] = toY + ONE;
5610           parseList[endPV-1][0] = NULLCHAR;
5611           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5612         }
5613       }
5614     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5615     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5616     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5617     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5618         valid++; // allow comments in PV
5619         continue;
5620     }
5621     nr++;
5622     if(endPV+1 > framePtr) break; // no space, truncate
5623     if(!valid) break;
5624     endPV++;
5625     CopyBoard(boards[endPV], boards[endPV-1]);
5626     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5627     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5628     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5629     CoordsToAlgebraic(boards[endPV - 1],
5630                              PosFlags(endPV - 1),
5631                              fromY, fromX, toY, toX, promoChar,
5632                              parseList[endPV - 1]);
5633   } while(valid);
5634   if(atEnd == 2) return; // used hidden, for PV conversion
5635   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5636   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5637   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5638                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5639   DrawPosition(TRUE, boards[currentMove]);
5640 }
5641
5642 int
5643 MultiPV (ChessProgramState *cps)
5644 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5645         int i;
5646         for(i=0; i<cps->nrOptions; i++)
5647             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5648                 return i;
5649         return -1;
5650 }
5651
5652 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5653
5654 Boolean
5655 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5656 {
5657         int startPV, multi, lineStart, origIndex = index;
5658         char *p, buf2[MSG_SIZ];
5659         ChessProgramState *cps = (pane ? &second : &first);
5660
5661         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5662         lastX = x; lastY = y;
5663         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5664         lineStart = startPV = index;
5665         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5666         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5667         index = startPV;
5668         do{ while(buf[index] && buf[index] != '\n') index++;
5669         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5670         buf[index] = 0;
5671         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5672                 int n = cps->option[multi].value;
5673                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5674                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5675                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5676                 cps->option[multi].value = n;
5677                 *start = *end = 0;
5678                 return FALSE;
5679         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5680                 ExcludeClick(origIndex - lineStart);
5681                 return FALSE;
5682         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5683                 Collapse(origIndex - lineStart);
5684                 return FALSE;
5685         }
5686         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5687         *start = startPV; *end = index-1;
5688         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5689         return TRUE;
5690 }
5691
5692 char *
5693 PvToSAN (char *pv)
5694 {
5695         static char buf[10*MSG_SIZ];
5696         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5697         *buf = NULLCHAR;
5698         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5699         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5700         for(i = forwardMostMove; i<endPV; i++){
5701             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5702             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5703             k += strlen(buf+k);
5704         }
5705         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5706         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5707         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5708         endPV = savedEnd;
5709         return buf;
5710 }
5711
5712 Boolean
5713 LoadPV (int x, int y)
5714 { // called on right mouse click to load PV
5715   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5716   lastX = x; lastY = y;
5717   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5718   extendGame = FALSE;
5719   return TRUE;
5720 }
5721
5722 void
5723 UnLoadPV ()
5724 {
5725   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5726   if(endPV < 0) return;
5727   if(appData.autoCopyPV) CopyFENToClipboard();
5728   endPV = -1;
5729   if(extendGame && currentMove > forwardMostMove) {
5730         Boolean saveAnimate = appData.animate;
5731         if(pushed) {
5732             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5733                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5734             } else storedGames--; // abandon shelved tail of original game
5735         }
5736         pushed = FALSE;
5737         forwardMostMove = currentMove;
5738         currentMove = oldFMM;
5739         appData.animate = FALSE;
5740         ToNrEvent(forwardMostMove);
5741         appData.animate = saveAnimate;
5742   }
5743   currentMove = forwardMostMove;
5744   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5745   ClearPremoveHighlights();
5746   DrawPosition(TRUE, boards[currentMove]);
5747 }
5748
5749 void
5750 MovePV (int x, int y, int h)
5751 { // step through PV based on mouse coordinates (called on mouse move)
5752   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5753
5754   // we must somehow check if right button is still down (might be released off board!)
5755   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5756   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5757   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5758   if(!step) return;
5759   lastX = x; lastY = y;
5760
5761   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5762   if(endPV < 0) return;
5763   if(y < margin) step = 1; else
5764   if(y > h - margin) step = -1;
5765   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5766   currentMove += step;
5767   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5768   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5769                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5770   DrawPosition(FALSE, boards[currentMove]);
5771 }
5772
5773
5774 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5775 // All positions will have equal probability, but the current method will not provide a unique
5776 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5777 #define DARK 1
5778 #define LITE 2
5779 #define ANY 3
5780
5781 int squaresLeft[4];
5782 int piecesLeft[(int)BlackPawn];
5783 int seed, nrOfShuffles;
5784
5785 void
5786 GetPositionNumber ()
5787 {       // sets global variable seed
5788         int i;
5789
5790         seed = appData.defaultFrcPosition;
5791         if(seed < 0) { // randomize based on time for negative FRC position numbers
5792                 for(i=0; i<50; i++) seed += random();
5793                 seed = random() ^ random() >> 8 ^ random() << 8;
5794                 if(seed<0) seed = -seed;
5795         }
5796 }
5797
5798 int
5799 put (Board board, int pieceType, int rank, int n, int shade)
5800 // put the piece on the (n-1)-th empty squares of the given shade
5801 {
5802         int i;
5803
5804         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5805                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5806                         board[rank][i] = (ChessSquare) pieceType;
5807                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5808                         squaresLeft[ANY]--;
5809                         piecesLeft[pieceType]--;
5810                         return i;
5811                 }
5812         }
5813         return -1;
5814 }
5815
5816
5817 void
5818 AddOnePiece (Board board, int pieceType, int rank, int shade)
5819 // calculate where the next piece goes, (any empty square), and put it there
5820 {
5821         int i;
5822
5823         i = seed % squaresLeft[shade];
5824         nrOfShuffles *= squaresLeft[shade];
5825         seed /= squaresLeft[shade];
5826         put(board, pieceType, rank, i, shade);
5827 }
5828
5829 void
5830 AddTwoPieces (Board board, int pieceType, int rank)
5831 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5832 {
5833         int i, n=squaresLeft[ANY], j=n-1, k;
5834
5835         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5836         i = seed % k;  // pick one
5837         nrOfShuffles *= k;
5838         seed /= k;
5839         while(i >= j) i -= j--;
5840         j = n - 1 - j; i += j;
5841         put(board, pieceType, rank, j, ANY);
5842         put(board, pieceType, rank, i, ANY);
5843 }
5844
5845 void
5846 SetUpShuffle (Board board, int number)
5847 {
5848         int i, p, first=1;
5849
5850         GetPositionNumber(); nrOfShuffles = 1;
5851
5852         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5853         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5854         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5855
5856         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5857
5858         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5859             p = (int) board[0][i];
5860             if(p < (int) BlackPawn) piecesLeft[p] ++;
5861             board[0][i] = EmptySquare;
5862         }
5863
5864         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5865             // shuffles restricted to allow normal castling put KRR first
5866             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5867                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5868             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5869                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5870             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5871                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5872             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5873                 put(board, WhiteRook, 0, 0, ANY);
5874             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5875         }
5876
5877         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5878             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5879             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5880                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5881                 while(piecesLeft[p] >= 2) {
5882                     AddOnePiece(board, p, 0, LITE);
5883                     AddOnePiece(board, p, 0, DARK);
5884                 }
5885                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5886             }
5887
5888         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5889             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5890             // but we leave King and Rooks for last, to possibly obey FRC restriction
5891             if(p == (int)WhiteRook) continue;
5892             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5893             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5894         }
5895
5896         // now everything is placed, except perhaps King (Unicorn) and Rooks
5897
5898         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5899             // Last King gets castling rights
5900             while(piecesLeft[(int)WhiteUnicorn]) {
5901                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5902                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5903             }
5904
5905             while(piecesLeft[(int)WhiteKing]) {
5906                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5907                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5908             }
5909
5910
5911         } else {
5912             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5913             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5914         }
5915
5916         // Only Rooks can be left; simply place them all
5917         while(piecesLeft[(int)WhiteRook]) {
5918                 i = put(board, WhiteRook, 0, 0, ANY);
5919                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5920                         if(first) {
5921                                 first=0;
5922                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5923                         }
5924                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5925                 }
5926         }
5927         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5928             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5929         }
5930
5931         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5932 }
5933
5934 int
5935 SetCharTable (char *table, const char * map)
5936 /* [HGM] moved here from winboard.c because of its general usefulness */
5937 /*       Basically a safe strcpy that uses the last character as King */
5938 {
5939     int result = FALSE; int NrPieces;
5940
5941     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5942                     && NrPieces >= 12 && !(NrPieces&1)) {
5943         int i; /* [HGM] Accept even length from 12 to 34 */
5944
5945         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5946         for( i=0; i<NrPieces/2-1; i++ ) {
5947             table[i] = map[i];
5948             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5949         }
5950         table[(int) WhiteKing]  = map[NrPieces/2-1];
5951         table[(int) BlackKing]  = map[NrPieces-1];
5952
5953         result = TRUE;
5954     }
5955
5956     return result;
5957 }
5958
5959 void
5960 Prelude (Board board)
5961 {       // [HGM] superchess: random selection of exo-pieces
5962         int i, j, k; ChessSquare p;
5963         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5964
5965         GetPositionNumber(); // use FRC position number
5966
5967         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5968             SetCharTable(pieceToChar, appData.pieceToCharTable);
5969             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5970                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5971         }
5972
5973         j = seed%4;                 seed /= 4;
5974         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5975         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5976         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5977         j = seed%3 + (seed%3 >= j); seed /= 3;
5978         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5979         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5980         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5981         j = seed%3;                 seed /= 3;
5982         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5983         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5984         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5985         j = seed%2 + (seed%2 >= j); seed /= 2;
5986         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5987         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5988         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5989         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5990         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5991         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5992         put(board, exoPieces[0],    0, 0, ANY);
5993         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5994 }
5995
5996 void
5997 InitPosition (int redraw)
5998 {
5999     ChessSquare (* pieces)[BOARD_FILES];
6000     int i, j, pawnRow=1, pieceRows=1, overrule,
6001     oldx = gameInfo.boardWidth,
6002     oldy = gameInfo.boardHeight,
6003     oldh = gameInfo.holdingsWidth;
6004     static int oldv;
6005
6006     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6007
6008     /* [AS] Initialize pv info list [HGM] and game status */
6009     {
6010         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6011             pvInfoList[i].depth = 0;
6012             boards[i][EP_STATUS] = EP_NONE;
6013             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6014         }
6015
6016         initialRulePlies = 0; /* 50-move counter start */
6017
6018         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6019         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6020     }
6021
6022
6023     /* [HGM] logic here is completely changed. In stead of full positions */
6024     /* the initialized data only consist of the two backranks. The switch */
6025     /* selects which one we will use, which is than copied to the Board   */
6026     /* initialPosition, which for the rest is initialized by Pawns and    */
6027     /* empty squares. This initial position is then copied to boards[0],  */
6028     /* possibly after shuffling, so that it remains available.            */
6029
6030     gameInfo.holdingsWidth = 0; /* default board sizes */
6031     gameInfo.boardWidth    = 8;
6032     gameInfo.boardHeight   = 8;
6033     gameInfo.holdingsSize  = 0;
6034     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6035     for(i=0; i<BOARD_FILES-6; i++)
6036       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6037     initialPosition[EP_STATUS] = EP_NONE;
6038     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6039     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6040     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6041          SetCharTable(pieceNickName, appData.pieceNickNames);
6042     else SetCharTable(pieceNickName, "............");
6043     pieces = FIDEArray;
6044
6045     switch (gameInfo.variant) {
6046     case VariantFischeRandom:
6047       shuffleOpenings = TRUE;
6048       appData.fischerCastling = TRUE;
6049     default:
6050       break;
6051     case VariantShatranj:
6052       pieces = ShatranjArray;
6053       nrCastlingRights = 0;
6054       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6055       break;
6056     case VariantMakruk:
6057       pieces = makrukArray;
6058       nrCastlingRights = 0;
6059       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6060       break;
6061     case VariantASEAN:
6062       pieces = aseanArray;
6063       nrCastlingRights = 0;
6064       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6065       break;
6066     case VariantTwoKings:
6067       pieces = twoKingsArray;
6068       break;
6069     case VariantGrand:
6070       pieces = GrandArray;
6071       nrCastlingRights = 0;
6072       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6073       gameInfo.boardWidth = 10;
6074       gameInfo.boardHeight = 10;
6075       gameInfo.holdingsSize = 7;
6076       break;
6077     case VariantCapaRandom:
6078       shuffleOpenings = TRUE;
6079       appData.fischerCastling = TRUE;
6080     case VariantCapablanca:
6081       pieces = CapablancaArray;
6082       gameInfo.boardWidth = 10;
6083       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6084       break;
6085     case VariantGothic:
6086       pieces = GothicArray;
6087       gameInfo.boardWidth = 10;
6088       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6089       break;
6090     case VariantSChess:
6091       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6092       gameInfo.holdingsSize = 7;
6093       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6094       break;
6095     case VariantJanus:
6096       pieces = JanusArray;
6097       gameInfo.boardWidth = 10;
6098       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6099       nrCastlingRights = 6;
6100         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6101         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6102         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6103         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6104         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6105         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6106       break;
6107     case VariantFalcon:
6108       pieces = FalconArray;
6109       gameInfo.boardWidth = 10;
6110       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6111       break;
6112     case VariantXiangqi:
6113       pieces = XiangqiArray;
6114       gameInfo.boardWidth  = 9;
6115       gameInfo.boardHeight = 10;
6116       nrCastlingRights = 0;
6117       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6118       break;
6119     case VariantShogi:
6120       pieces = ShogiArray;
6121       gameInfo.boardWidth  = 9;
6122       gameInfo.boardHeight = 9;
6123       gameInfo.holdingsSize = 7;
6124       nrCastlingRights = 0;
6125       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6126       break;
6127     case VariantChu:
6128       pieces = ChuArray; pieceRows = 3;
6129       gameInfo.boardWidth  = 12;
6130       gameInfo.boardHeight = 12;
6131       nrCastlingRights = 0;
6132       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6133                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6134       break;
6135     case VariantCourier:
6136       pieces = CourierArray;
6137       gameInfo.boardWidth  = 12;
6138       nrCastlingRights = 0;
6139       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6140       break;
6141     case VariantKnightmate:
6142       pieces = KnightmateArray;
6143       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6144       break;
6145     case VariantSpartan:
6146       pieces = SpartanArray;
6147       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6148       break;
6149     case VariantLion:
6150       pieces = lionArray;
6151       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6152       break;
6153     case VariantChuChess:
6154       pieces = ChuChessArray;
6155       gameInfo.boardWidth = 10;
6156       gameInfo.boardHeight = 10;
6157       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6158       break;
6159     case VariantFairy:
6160       pieces = fairyArray;
6161       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6162       break;
6163     case VariantGreat:
6164       pieces = GreatArray;
6165       gameInfo.boardWidth = 10;
6166       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6167       gameInfo.holdingsSize = 8;
6168       break;
6169     case VariantSuper:
6170       pieces = FIDEArray;
6171       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6172       gameInfo.holdingsSize = 8;
6173       startedFromSetupPosition = TRUE;
6174       break;
6175     case VariantCrazyhouse:
6176     case VariantBughouse:
6177       pieces = FIDEArray;
6178       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6179       gameInfo.holdingsSize = 5;
6180       break;
6181     case VariantWildCastle:
6182       pieces = FIDEArray;
6183       /* !!?shuffle with kings guaranteed to be on d or e file */
6184       shuffleOpenings = 1;
6185       break;
6186     case VariantNoCastle:
6187       pieces = FIDEArray;
6188       nrCastlingRights = 0;
6189       /* !!?unconstrained back-rank shuffle */
6190       shuffleOpenings = 1;
6191       break;
6192     }
6193
6194     overrule = 0;
6195     if(appData.NrFiles >= 0) {
6196         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6197         gameInfo.boardWidth = appData.NrFiles;
6198     }
6199     if(appData.NrRanks >= 0) {
6200         gameInfo.boardHeight = appData.NrRanks;
6201     }
6202     if(appData.holdingsSize >= 0) {
6203         i = appData.holdingsSize;
6204         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6205         gameInfo.holdingsSize = i;
6206     }
6207     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6208     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6209         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6210
6211     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6212     if(pawnRow < 1) pawnRow = 1;
6213     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6214        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6215     if(gameInfo.variant == VariantChu) pawnRow = 3;
6216
6217     /* User pieceToChar list overrules defaults */
6218     if(appData.pieceToCharTable != NULL)
6219         SetCharTable(pieceToChar, appData.pieceToCharTable);
6220
6221     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6222
6223         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6224             s = (ChessSquare) 0; /* account holding counts in guard band */
6225         for( i=0; i<BOARD_HEIGHT; i++ )
6226             initialPosition[i][j] = s;
6227
6228         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6229         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6230         initialPosition[pawnRow][j] = WhitePawn;
6231         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6232         if(gameInfo.variant == VariantXiangqi) {
6233             if(j&1) {
6234                 initialPosition[pawnRow][j] =
6235                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6236                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6237                    initialPosition[2][j] = WhiteCannon;
6238                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6239                 }
6240             }
6241         }
6242         if(gameInfo.variant == VariantChu) {
6243              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6244                initialPosition[pawnRow+1][j] = WhiteCobra,
6245                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6246              for(i=1; i<pieceRows; i++) {
6247                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6248                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6249              }
6250         }
6251         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6252             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6253                initialPosition[0][j] = WhiteRook;
6254                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6255             }
6256         }
6257         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6258     }
6259     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6260     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6261
6262             j=BOARD_LEFT+1;
6263             initialPosition[1][j] = WhiteBishop;
6264             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6265             j=BOARD_RGHT-2;
6266             initialPosition[1][j] = WhiteRook;
6267             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6268     }
6269
6270     if( nrCastlingRights == -1) {
6271         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6272         /*       This sets default castling rights from none to normal corners   */
6273         /* Variants with other castling rights must set them themselves above    */
6274         nrCastlingRights = 6;
6275
6276         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6277         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6278         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6279         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6280         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6281         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6282      }
6283
6284      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6285      if(gameInfo.variant == VariantGreat) { // promotion commoners
6286         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6287         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6288         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6289         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6290      }
6291      if( gameInfo.variant == VariantSChess ) {
6292       initialPosition[1][0] = BlackMarshall;
6293       initialPosition[2][0] = BlackAngel;
6294       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6295       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6296       initialPosition[1][1] = initialPosition[2][1] =
6297       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6298      }
6299   if (appData.debugMode) {
6300     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6301   }
6302     if(shuffleOpenings) {
6303         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6304         startedFromSetupPosition = TRUE;
6305     }
6306     if(startedFromPositionFile) {
6307       /* [HGM] loadPos: use PositionFile for every new game */
6308       CopyBoard(initialPosition, filePosition);
6309       for(i=0; i<nrCastlingRights; i++)
6310           initialRights[i] = filePosition[CASTLING][i];
6311       startedFromSetupPosition = TRUE;
6312     }
6313
6314     CopyBoard(boards[0], initialPosition);
6315
6316     if(oldx != gameInfo.boardWidth ||
6317        oldy != gameInfo.boardHeight ||
6318        oldv != gameInfo.variant ||
6319        oldh != gameInfo.holdingsWidth
6320                                          )
6321             InitDrawingSizes(-2 ,0);
6322
6323     oldv = gameInfo.variant;
6324     if (redraw)
6325       DrawPosition(TRUE, boards[currentMove]);
6326 }
6327
6328 void
6329 SendBoard (ChessProgramState *cps, int moveNum)
6330 {
6331     char message[MSG_SIZ];
6332
6333     if (cps->useSetboard) {
6334       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6335       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6336       SendToProgram(message, cps);
6337       free(fen);
6338
6339     } else {
6340       ChessSquare *bp;
6341       int i, j, left=0, right=BOARD_WIDTH;
6342       /* Kludge to set black to move, avoiding the troublesome and now
6343        * deprecated "black" command.
6344        */
6345       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6346         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6347
6348       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6349
6350       SendToProgram("edit\n", cps);
6351       SendToProgram("#\n", cps);
6352       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6353         bp = &boards[moveNum][i][left];
6354         for (j = left; j < right; j++, bp++) {
6355           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6356           if ((int) *bp < (int) BlackPawn) {
6357             if(j == BOARD_RGHT+1)
6358                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6359             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6360             if(message[0] == '+' || message[0] == '~') {
6361               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6362                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6363                         AAA + j, ONE + i);
6364             }
6365             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6366                 message[1] = BOARD_RGHT   - 1 - j + '1';
6367                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6368             }
6369             SendToProgram(message, cps);
6370           }
6371         }
6372       }
6373
6374       SendToProgram("c\n", cps);
6375       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6376         bp = &boards[moveNum][i][left];
6377         for (j = left; j < right; j++, bp++) {
6378           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6379           if (((int) *bp != (int) EmptySquare)
6380               && ((int) *bp >= (int) BlackPawn)) {
6381             if(j == BOARD_LEFT-2)
6382                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6383             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6384                     AAA + j, ONE + i);
6385             if(message[0] == '+' || message[0] == '~') {
6386               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6387                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6388                         AAA + j, ONE + i);
6389             }
6390             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6391                 message[1] = BOARD_RGHT   - 1 - j + '1';
6392                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6393             }
6394             SendToProgram(message, cps);
6395           }
6396         }
6397       }
6398
6399       SendToProgram(".\n", cps);
6400     }
6401     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6402 }
6403
6404 char exclusionHeader[MSG_SIZ];
6405 int exCnt, excludePtr;
6406 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6407 static Exclusion excluTab[200];
6408 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6409
6410 static void
6411 WriteMap (int s)
6412 {
6413     int j;
6414     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6415     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6416 }
6417
6418 static void
6419 ClearMap ()
6420 {
6421     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6422     excludePtr = 24; exCnt = 0;
6423     WriteMap(0);
6424 }
6425
6426 static void
6427 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6428 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6429     char buf[2*MOVE_LEN], *p;
6430     Exclusion *e = excluTab;
6431     int i;
6432     for(i=0; i<exCnt; i++)
6433         if(e[i].ff == fromX && e[i].fr == fromY &&
6434            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6435     if(i == exCnt) { // was not in exclude list; add it
6436         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6437         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6438             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6439             return; // abort
6440         }
6441         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6442         excludePtr++; e[i].mark = excludePtr++;
6443         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6444         exCnt++;
6445     }
6446     exclusionHeader[e[i].mark] = state;
6447 }
6448
6449 static int
6450 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6451 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6452     char buf[MSG_SIZ];
6453     int j, k;
6454     ChessMove moveType;
6455     if((signed char)promoChar == -1) { // kludge to indicate best move
6456         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6457             return 1; // if unparsable, abort
6458     }
6459     // update exclusion map (resolving toggle by consulting existing state)
6460     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6461     j = k%8; k >>= 3;
6462     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6463     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6464          excludeMap[k] |=   1<<j;
6465     else excludeMap[k] &= ~(1<<j);
6466     // update header
6467     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6468     // inform engine
6469     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6470     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6471     SendToBoth(buf);
6472     return (state == '+');
6473 }
6474
6475 static void
6476 ExcludeClick (int index)
6477 {
6478     int i, j;
6479     Exclusion *e = excluTab;
6480     if(index < 25) { // none, best or tail clicked
6481         if(index < 13) { // none: include all
6482             WriteMap(0); // clear map
6483             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6484             SendToBoth("include all\n"); // and inform engine
6485         } else if(index > 18) { // tail
6486             if(exclusionHeader[19] == '-') { // tail was excluded
6487                 SendToBoth("include all\n");
6488                 WriteMap(0); // clear map completely
6489                 // now re-exclude selected moves
6490                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6491                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6492             } else { // tail was included or in mixed state
6493                 SendToBoth("exclude all\n");
6494                 WriteMap(0xFF); // fill map completely
6495                 // now re-include selected moves
6496                 j = 0; // count them
6497                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6498                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6499                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6500             }
6501         } else { // best
6502             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6503         }
6504     } else {
6505         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6506             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6507             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6508             break;
6509         }
6510     }
6511 }
6512
6513 ChessSquare
6514 DefaultPromoChoice (int white)
6515 {
6516     ChessSquare result;
6517     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6518        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6519         result = WhiteFerz; // no choice
6520     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6521         result= WhiteKing; // in Suicide Q is the last thing we want
6522     else if(gameInfo.variant == VariantSpartan)
6523         result = white ? WhiteQueen : WhiteAngel;
6524     else result = WhiteQueen;
6525     if(!white) result = WHITE_TO_BLACK result;
6526     return result;
6527 }
6528
6529 static int autoQueen; // [HGM] oneclick
6530
6531 int
6532 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6533 {
6534     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6535     /* [HGM] add Shogi promotions */
6536     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6537     ChessSquare piece, partner;
6538     ChessMove moveType;
6539     Boolean premove;
6540
6541     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6542     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6543
6544     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6545       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6546         return FALSE;
6547
6548     piece = boards[currentMove][fromY][fromX];
6549     if(gameInfo.variant == VariantChu) {
6550         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6551         promotionZoneSize = BOARD_HEIGHT/3;
6552         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6553     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6554         promotionZoneSize = BOARD_HEIGHT/3;
6555         highestPromotingPiece = (int)WhiteAlfil;
6556     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6557         promotionZoneSize = 3;
6558     }
6559
6560     // Treat Lance as Pawn when it is not representing Amazon or Lance
6561     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6562         if(piece == WhiteLance) piece = WhitePawn; else
6563         if(piece == BlackLance) piece = BlackPawn;
6564     }
6565
6566     // next weed out all moves that do not touch the promotion zone at all
6567     if((int)piece >= BlackPawn) {
6568         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6569              return FALSE;
6570         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6571         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6572     } else {
6573         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6574            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6575         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6576              return FALSE;
6577     }
6578
6579     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6580
6581     // weed out mandatory Shogi promotions
6582     if(gameInfo.variant == VariantShogi) {
6583         if(piece >= BlackPawn) {
6584             if(toY == 0 && piece == BlackPawn ||
6585                toY == 0 && piece == BlackQueen ||
6586                toY <= 1 && piece == BlackKnight) {
6587                 *promoChoice = '+';
6588                 return FALSE;
6589             }
6590         } else {
6591             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6592                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6593                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6594                 *promoChoice = '+';
6595                 return FALSE;
6596             }
6597         }
6598     }
6599
6600     // weed out obviously illegal Pawn moves
6601     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6602         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6603         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6604         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6605         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6606         // note we are not allowed to test for valid (non-)capture, due to premove
6607     }
6608
6609     // we either have a choice what to promote to, or (in Shogi) whether to promote
6610     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6611        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6612         ChessSquare p=BlackFerz;  // no choice
6613         while(p < EmptySquare) {  //but make sure we use piece that exists
6614             *promoChoice = PieceToChar(p++);
6615             if(*promoChoice != '.') break;
6616         }
6617         return FALSE;
6618     }
6619     // no sense asking what we must promote to if it is going to explode...
6620     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6621         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6622         return FALSE;
6623     }
6624     // give caller the default choice even if we will not make it
6625     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6626     partner = piece; // pieces can promote if the pieceToCharTable says so
6627     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6628     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6629     if(        sweepSelect && gameInfo.variant != VariantGreat
6630                            && gameInfo.variant != VariantGrand
6631                            && gameInfo.variant != VariantSuper) return FALSE;
6632     if(autoQueen) return FALSE; // predetermined
6633
6634     // suppress promotion popup on illegal moves that are not premoves
6635     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6636               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6637     if(appData.testLegality && !premove) {
6638         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6639                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6640         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6641         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6642             return FALSE;
6643     }
6644
6645     return TRUE;
6646 }
6647
6648 int
6649 InPalace (int row, int column)
6650 {   /* [HGM] for Xiangqi */
6651     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6652          column < (BOARD_WIDTH + 4)/2 &&
6653          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6654     return FALSE;
6655 }
6656
6657 int
6658 PieceForSquare (int x, int y)
6659 {
6660   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6661      return -1;
6662   else
6663      return boards[currentMove][y][x];
6664 }
6665
6666 int
6667 OKToStartUserMove (int x, int y)
6668 {
6669     ChessSquare from_piece;
6670     int white_piece;
6671
6672     if (matchMode) return FALSE;
6673     if (gameMode == EditPosition) return TRUE;
6674
6675     if (x >= 0 && y >= 0)
6676       from_piece = boards[currentMove][y][x];
6677     else
6678       from_piece = EmptySquare;
6679
6680     if (from_piece == EmptySquare) return FALSE;
6681
6682     white_piece = (int)from_piece >= (int)WhitePawn &&
6683       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6684
6685     switch (gameMode) {
6686       case AnalyzeFile:
6687       case TwoMachinesPlay:
6688       case EndOfGame:
6689         return FALSE;
6690
6691       case IcsObserving:
6692       case IcsIdle:
6693         return FALSE;
6694
6695       case MachinePlaysWhite:
6696       case IcsPlayingBlack:
6697         if (appData.zippyPlay) return FALSE;
6698         if (white_piece) {
6699             DisplayMoveError(_("You are playing Black"));
6700             return FALSE;
6701         }
6702         break;
6703
6704       case MachinePlaysBlack:
6705       case IcsPlayingWhite:
6706         if (appData.zippyPlay) return FALSE;
6707         if (!white_piece) {
6708             DisplayMoveError(_("You are playing White"));
6709             return FALSE;
6710         }
6711         break;
6712
6713       case PlayFromGameFile:
6714             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6715       case EditGame:
6716         if (!white_piece && WhiteOnMove(currentMove)) {
6717             DisplayMoveError(_("It is White's turn"));
6718             return FALSE;
6719         }
6720         if (white_piece && !WhiteOnMove(currentMove)) {
6721             DisplayMoveError(_("It is Black's turn"));
6722             return FALSE;
6723         }
6724         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6725             /* Editing correspondence game history */
6726             /* Could disallow this or prompt for confirmation */
6727             cmailOldMove = -1;
6728         }
6729         break;
6730
6731       case BeginningOfGame:
6732         if (appData.icsActive) return FALSE;
6733         if (!appData.noChessProgram) {
6734             if (!white_piece) {
6735                 DisplayMoveError(_("You are playing White"));
6736                 return FALSE;
6737             }
6738         }
6739         break;
6740
6741       case Training:
6742         if (!white_piece && WhiteOnMove(currentMove)) {
6743             DisplayMoveError(_("It is White's turn"));
6744             return FALSE;
6745         }
6746         if (white_piece && !WhiteOnMove(currentMove)) {
6747             DisplayMoveError(_("It is Black's turn"));
6748             return FALSE;
6749         }
6750         break;
6751
6752       default:
6753       case IcsExamining:
6754         break;
6755     }
6756     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6757         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6758         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6759         && gameMode != AnalyzeFile && gameMode != Training) {
6760         DisplayMoveError(_("Displayed position is not current"));
6761         return FALSE;
6762     }
6763     return TRUE;
6764 }
6765
6766 Boolean
6767 OnlyMove (int *x, int *y, Boolean captures)
6768 {
6769     DisambiguateClosure cl;
6770     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6771     switch(gameMode) {
6772       case MachinePlaysBlack:
6773       case IcsPlayingWhite:
6774       case BeginningOfGame:
6775         if(!WhiteOnMove(currentMove)) return FALSE;
6776         break;
6777       case MachinePlaysWhite:
6778       case IcsPlayingBlack:
6779         if(WhiteOnMove(currentMove)) return FALSE;
6780         break;
6781       case EditGame:
6782         break;
6783       default:
6784         return FALSE;
6785     }
6786     cl.pieceIn = EmptySquare;
6787     cl.rfIn = *y;
6788     cl.ffIn = *x;
6789     cl.rtIn = -1;
6790     cl.ftIn = -1;
6791     cl.promoCharIn = NULLCHAR;
6792     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6793     if( cl.kind == NormalMove ||
6794         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6795         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6796         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6797       fromX = cl.ff;
6798       fromY = cl.rf;
6799       *x = cl.ft;
6800       *y = cl.rt;
6801       return TRUE;
6802     }
6803     if(cl.kind != ImpossibleMove) return FALSE;
6804     cl.pieceIn = EmptySquare;
6805     cl.rfIn = -1;
6806     cl.ffIn = -1;
6807     cl.rtIn = *y;
6808     cl.ftIn = *x;
6809     cl.promoCharIn = NULLCHAR;
6810     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6811     if( cl.kind == NormalMove ||
6812         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6813         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6814         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6815       fromX = cl.ff;
6816       fromY = cl.rf;
6817       *x = cl.ft;
6818       *y = cl.rt;
6819       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6820       return TRUE;
6821     }
6822     return FALSE;
6823 }
6824
6825 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6826 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6827 int lastLoadGameUseList = FALSE;
6828 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6829 ChessMove lastLoadGameStart = EndOfFile;
6830 int doubleClick;
6831 Boolean addToBookFlag;
6832
6833 void
6834 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6835 {
6836     ChessMove moveType;
6837     ChessSquare pup;
6838     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6839
6840     /* Check if the user is playing in turn.  This is complicated because we
6841        let the user "pick up" a piece before it is his turn.  So the piece he
6842        tried to pick up may have been captured by the time he puts it down!
6843        Therefore we use the color the user is supposed to be playing in this
6844        test, not the color of the piece that is currently on the starting
6845        square---except in EditGame mode, where the user is playing both
6846        sides; fortunately there the capture race can't happen.  (It can
6847        now happen in IcsExamining mode, but that's just too bad.  The user
6848        will get a somewhat confusing message in that case.)
6849        */
6850
6851     switch (gameMode) {
6852       case AnalyzeFile:
6853       case TwoMachinesPlay:
6854       case EndOfGame:
6855       case IcsObserving:
6856       case IcsIdle:
6857         /* We switched into a game mode where moves are not accepted,
6858            perhaps while the mouse button was down. */
6859         return;
6860
6861       case MachinePlaysWhite:
6862         /* User is moving for Black */
6863         if (WhiteOnMove(currentMove)) {
6864             DisplayMoveError(_("It is White's turn"));
6865             return;
6866         }
6867         break;
6868
6869       case MachinePlaysBlack:
6870         /* User is moving for White */
6871         if (!WhiteOnMove(currentMove)) {
6872             DisplayMoveError(_("It is Black's turn"));
6873             return;
6874         }
6875         break;
6876
6877       case PlayFromGameFile:
6878             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6879       case EditGame:
6880       case IcsExamining:
6881       case BeginningOfGame:
6882       case AnalyzeMode:
6883       case Training:
6884         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6885         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6886             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6887             /* User is moving for Black */
6888             if (WhiteOnMove(currentMove)) {
6889                 DisplayMoveError(_("It is White's turn"));
6890                 return;
6891             }
6892         } else {
6893             /* User is moving for White */
6894             if (!WhiteOnMove(currentMove)) {
6895                 DisplayMoveError(_("It is Black's turn"));
6896                 return;
6897             }
6898         }
6899         break;
6900
6901       case IcsPlayingBlack:
6902         /* User is moving for Black */
6903         if (WhiteOnMove(currentMove)) {
6904             if (!appData.premove) {
6905                 DisplayMoveError(_("It is White's turn"));
6906             } else if (toX >= 0 && toY >= 0) {
6907                 premoveToX = toX;
6908                 premoveToY = toY;
6909                 premoveFromX = fromX;
6910                 premoveFromY = fromY;
6911                 premovePromoChar = promoChar;
6912                 gotPremove = 1;
6913                 if (appData.debugMode)
6914                     fprintf(debugFP, "Got premove: fromX %d,"
6915                             "fromY %d, toX %d, toY %d\n",
6916                             fromX, fromY, toX, toY);
6917             }
6918             return;
6919         }
6920         break;
6921
6922       case IcsPlayingWhite:
6923         /* User is moving for White */
6924         if (!WhiteOnMove(currentMove)) {
6925             if (!appData.premove) {
6926                 DisplayMoveError(_("It is Black's turn"));
6927             } else if (toX >= 0 && toY >= 0) {
6928                 premoveToX = toX;
6929                 premoveToY = toY;
6930                 premoveFromX = fromX;
6931                 premoveFromY = fromY;
6932                 premovePromoChar = promoChar;
6933                 gotPremove = 1;
6934                 if (appData.debugMode)
6935                     fprintf(debugFP, "Got premove: fromX %d,"
6936                             "fromY %d, toX %d, toY %d\n",
6937                             fromX, fromY, toX, toY);
6938             }
6939             return;
6940         }
6941         break;
6942
6943       default:
6944         break;
6945
6946       case EditPosition:
6947         /* EditPosition, empty square, or different color piece;
6948            click-click move is possible */
6949         if (toX == -2 || toY == -2) {
6950             boards[0][fromY][fromX] = EmptySquare;
6951             DrawPosition(FALSE, boards[currentMove]);
6952             return;
6953         } else if (toX >= 0 && toY >= 0) {
6954             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6955                 ChessSquare q, p = boards[0][rf][ff];
6956                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6957                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6958                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6959                 if(PieceToChar(q) == '+') gatingPiece = p;
6960             }
6961             boards[0][toY][toX] = boards[0][fromY][fromX];
6962             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6963                 if(boards[0][fromY][0] != EmptySquare) {
6964                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6965                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6966                 }
6967             } else
6968             if(fromX == BOARD_RGHT+1) {
6969                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6970                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6971                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6972                 }
6973             } else
6974             boards[0][fromY][fromX] = gatingPiece;
6975             DrawPosition(FALSE, boards[currentMove]);
6976             return;
6977         }
6978         return;
6979     }
6980
6981     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6982     pup = boards[currentMove][toY][toX];
6983
6984     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6985     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6986          if( pup != EmptySquare ) return;
6987          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6988            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6989                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6990            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6991            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6992            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6993            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6994          fromY = DROP_RANK;
6995     }
6996
6997     /* [HGM] always test for legality, to get promotion info */
6998     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6999                                          fromY, fromX, toY, toX, promoChar);
7000
7001     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7002
7003     /* [HGM] but possibly ignore an IllegalMove result */
7004     if (appData.testLegality) {
7005         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7006             DisplayMoveError(_("Illegal move"));
7007             return;
7008         }
7009     }
7010
7011     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7012         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7013              ClearPremoveHighlights(); // was included
7014         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7015         return;
7016     }
7017
7018     if(addToBookFlag) { // adding moves to book
7019         char buf[MSG_SIZ], move[MSG_SIZ];
7020         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7021         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7022         AddBookMove(buf);
7023         addToBookFlag = FALSE;
7024         ClearHighlights();
7025         return;
7026     }
7027
7028     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7029 }
7030
7031 /* Common tail of UserMoveEvent and DropMenuEvent */
7032 int
7033 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7034 {
7035     char *bookHit = 0;
7036
7037     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7038         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7039         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7040         if(WhiteOnMove(currentMove)) {
7041             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7042         } else {
7043             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7044         }
7045     }
7046
7047     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7048        move type in caller when we know the move is a legal promotion */
7049     if(moveType == NormalMove && promoChar)
7050         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7051
7052     /* [HGM] <popupFix> The following if has been moved here from
7053        UserMoveEvent(). Because it seemed to belong here (why not allow
7054        piece drops in training games?), and because it can only be
7055        performed after it is known to what we promote. */
7056     if (gameMode == Training) {
7057       /* compare the move played on the board to the next move in the
7058        * game. If they match, display the move and the opponent's response.
7059        * If they don't match, display an error message.
7060        */
7061       int saveAnimate;
7062       Board testBoard;
7063       CopyBoard(testBoard, boards[currentMove]);
7064       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7065
7066       if (CompareBoards(testBoard, boards[currentMove+1])) {
7067         ForwardInner(currentMove+1);
7068
7069         /* Autoplay the opponent's response.
7070          * if appData.animate was TRUE when Training mode was entered,
7071          * the response will be animated.
7072          */
7073         saveAnimate = appData.animate;
7074         appData.animate = animateTraining;
7075         ForwardInner(currentMove+1);
7076         appData.animate = saveAnimate;
7077
7078         /* check for the end of the game */
7079         if (currentMove >= forwardMostMove) {
7080           gameMode = PlayFromGameFile;
7081           ModeHighlight();
7082           SetTrainingModeOff();
7083           DisplayInformation(_("End of game"));
7084         }
7085       } else {
7086         DisplayError(_("Incorrect move"), 0);
7087       }
7088       return 1;
7089     }
7090
7091   /* Ok, now we know that the move is good, so we can kill
7092      the previous line in Analysis Mode */
7093   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7094                                 && currentMove < forwardMostMove) {
7095     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7096     else forwardMostMove = currentMove;
7097   }
7098
7099   ClearMap();
7100
7101   /* If we need the chess program but it's dead, restart it */
7102   ResurrectChessProgram();
7103
7104   /* A user move restarts a paused game*/
7105   if (pausing)
7106     PauseEvent();
7107
7108   thinkOutput[0] = NULLCHAR;
7109
7110   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7111
7112   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7113     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7114     return 1;
7115   }
7116
7117   if (gameMode == BeginningOfGame) {
7118     if (appData.noChessProgram) {
7119       gameMode = EditGame;
7120       SetGameInfo();
7121     } else {
7122       char buf[MSG_SIZ];
7123       gameMode = MachinePlaysBlack;
7124       StartClocks();
7125       SetGameInfo();
7126       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7127       DisplayTitle(buf);
7128       if (first.sendName) {
7129         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7130         SendToProgram(buf, &first);
7131       }
7132       StartClocks();
7133     }
7134     ModeHighlight();
7135   }
7136
7137   /* Relay move to ICS or chess engine */
7138   if (appData.icsActive) {
7139     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7140         gameMode == IcsExamining) {
7141       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7142         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7143         SendToICS("draw ");
7144         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7145       }
7146       // also send plain move, in case ICS does not understand atomic claims
7147       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7148       ics_user_moved = 1;
7149     }
7150   } else {
7151     if (first.sendTime && (gameMode == BeginningOfGame ||
7152                            gameMode == MachinePlaysWhite ||
7153                            gameMode == MachinePlaysBlack)) {
7154       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7155     }
7156     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7157          // [HGM] book: if program might be playing, let it use book
7158         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7159         first.maybeThinking = TRUE;
7160     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7161         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7162         SendBoard(&first, currentMove+1);
7163         if(second.analyzing) {
7164             if(!second.useSetboard) SendToProgram("undo\n", &second);
7165             SendBoard(&second, currentMove+1);
7166         }
7167     } else {
7168         SendMoveToProgram(forwardMostMove-1, &first);
7169         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7170     }
7171     if (currentMove == cmailOldMove + 1) {
7172       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7173     }
7174   }
7175
7176   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7177
7178   switch (gameMode) {
7179   case EditGame:
7180     if(appData.testLegality)
7181     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7182     case MT_NONE:
7183     case MT_CHECK:
7184       break;
7185     case MT_CHECKMATE:
7186     case MT_STAINMATE:
7187       if (WhiteOnMove(currentMove)) {
7188         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7189       } else {
7190         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7191       }
7192       break;
7193     case MT_STALEMATE:
7194       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7195       break;
7196     }
7197     break;
7198
7199   case MachinePlaysBlack:
7200   case MachinePlaysWhite:
7201     /* disable certain menu options while machine is thinking */
7202     SetMachineThinkingEnables();
7203     break;
7204
7205   default:
7206     break;
7207   }
7208
7209   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7210   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7211
7212   if(bookHit) { // [HGM] book: simulate book reply
7213         static char bookMove[MSG_SIZ]; // a bit generous?
7214
7215         programStats.nodes = programStats.depth = programStats.time =
7216         programStats.score = programStats.got_only_move = 0;
7217         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7218
7219         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7220         strcat(bookMove, bookHit);
7221         HandleMachineMove(bookMove, &first);
7222   }
7223   return 1;
7224 }
7225
7226 void
7227 MarkByFEN(char *fen)
7228 {
7229         int r, f;
7230         if(!appData.markers || !appData.highlightDragging) return;
7231         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7232         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7233         while(*fen) {
7234             int s = 0;
7235             marker[r][f] = 0;
7236             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7237             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7238             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7239             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7240             if(*fen == 'T') marker[r][f++] = 0; else
7241             if(*fen == 'Y') marker[r][f++] = 1; else
7242             if(*fen == 'G') marker[r][f++] = 3; else
7243             if(*fen == 'B') marker[r][f++] = 4; else
7244             if(*fen == 'C') marker[r][f++] = 5; else
7245             if(*fen == 'M') marker[r][f++] = 6; else
7246             if(*fen == 'W') marker[r][f++] = 7; else
7247             if(*fen == 'D') marker[r][f++] = 8; else
7248             if(*fen == 'R') marker[r][f++] = 2; else {
7249                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7250               f += s; fen -= s>0;
7251             }
7252             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7253             if(r < 0) break;
7254             fen++;
7255         }
7256         DrawPosition(TRUE, NULL);
7257 }
7258
7259 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7260
7261 void
7262 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7263 {
7264     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7265     Markers *m = (Markers *) closure;
7266     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7267         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7268                          || kind == WhiteCapturesEnPassant
7269                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7270     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7271 }
7272
7273 static int hoverSavedValid;
7274
7275 void
7276 MarkTargetSquares (int clear)
7277 {
7278   int x, y, sum=0;
7279   if(clear) { // no reason to ever suppress clearing
7280     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7281     hoverSavedValid = 0;
7282     if(!sum) return; // nothing was cleared,no redraw needed
7283   } else {
7284     int capt = 0;
7285     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7286        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7287     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7288     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7289       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7290       if(capt)
7291       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7292     }
7293   }
7294   DrawPosition(FALSE, NULL);
7295 }
7296
7297 int
7298 Explode (Board board, int fromX, int fromY, int toX, int toY)
7299 {
7300     if(gameInfo.variant == VariantAtomic &&
7301        (board[toY][toX] != EmptySquare ||                     // capture?
7302         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7303                          board[fromY][fromX] == BlackPawn   )
7304       )) {
7305         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7306         return TRUE;
7307     }
7308     return FALSE;
7309 }
7310
7311 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7312
7313 int
7314 CanPromote (ChessSquare piece, int y)
7315 {
7316         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7317         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7318         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7319         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7320            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7321            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7322          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7323         return (piece == BlackPawn && y <= zone ||
7324                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7325                 piece == BlackLance && y == 1 ||
7326                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7327 }
7328
7329 void
7330 HoverEvent (int xPix, int yPix, int x, int y)
7331 {
7332         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7333         int r, f;
7334         if(!first.highlight) return;
7335         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7336         if(x == oldX && y == oldY) return; // only do something if we enter new square
7337         oldFromX = fromX; oldFromY = fromY;
7338         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7339           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7340             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7341           hoverSavedValid = 1;
7342         } else if(oldX != x || oldY != y) {
7343           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7344           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7345           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7346             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7347           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7348             char buf[MSG_SIZ];
7349             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7350             SendToProgram(buf, &first);
7351           }
7352           oldX = x; oldY = y;
7353 //        SetHighlights(fromX, fromY, x, y);
7354         }
7355 }
7356
7357 void ReportClick(char *action, int x, int y)
7358 {
7359         char buf[MSG_SIZ]; // Inform engine of what user does
7360         int r, f;
7361         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7362           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7363             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7364         if(!first.highlight || gameMode == EditPosition) return;
7365         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7366         SendToProgram(buf, &first);
7367 }
7368
7369 void
7370 LeftClick (ClickType clickType, int xPix, int yPix)
7371 {
7372     int x, y;
7373     Boolean saveAnimate;
7374     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7375     char promoChoice = NULLCHAR;
7376     ChessSquare piece;
7377     static TimeMark lastClickTime, prevClickTime;
7378
7379     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7380
7381     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7382
7383     if (clickType == Press) ErrorPopDown();
7384     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7385
7386     x = EventToSquare(xPix, BOARD_WIDTH);
7387     y = EventToSquare(yPix, BOARD_HEIGHT);
7388     if (!flipView && y >= 0) {
7389         y = BOARD_HEIGHT - 1 - y;
7390     }
7391     if (flipView && x >= 0) {
7392         x = BOARD_WIDTH - 1 - x;
7393     }
7394
7395     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7396         defaultPromoChoice = promoSweep;
7397         promoSweep = EmptySquare;   // terminate sweep
7398         promoDefaultAltered = TRUE;
7399         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7400     }
7401
7402     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7403         if(clickType == Release) return; // ignore upclick of click-click destination
7404         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7405         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7406         if(gameInfo.holdingsWidth &&
7407                 (WhiteOnMove(currentMove)
7408                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7409                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7410             // click in right holdings, for determining promotion piece
7411             ChessSquare p = boards[currentMove][y][x];
7412             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7413             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7414             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7415                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7416                 fromX = fromY = -1;
7417                 return;
7418             }
7419         }
7420         DrawPosition(FALSE, boards[currentMove]);
7421         return;
7422     }
7423
7424     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7425     if(clickType == Press
7426             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7427               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7428               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7429         return;
7430
7431     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7432         // could be static click on premove from-square: abort premove
7433         gotPremove = 0;
7434         ClearPremoveHighlights();
7435     }
7436
7437     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7438         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7439
7440     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7441         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7442                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7443         defaultPromoChoice = DefaultPromoChoice(side);
7444     }
7445
7446     autoQueen = appData.alwaysPromoteToQueen;
7447
7448     if (fromX == -1) {
7449       int originalY = y;
7450       gatingPiece = EmptySquare;
7451       if (clickType != Press) {
7452         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7453             DragPieceEnd(xPix, yPix); dragging = 0;
7454             DrawPosition(FALSE, NULL);
7455         }
7456         return;
7457       }
7458       doubleClick = FALSE;
7459       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7460         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7461       }
7462       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7463       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7464          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7465          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7466             /* First square */
7467             if (OKToStartUserMove(fromX, fromY)) {
7468                 second = 0;
7469                 ReportClick("lift", x, y);
7470                 MarkTargetSquares(0);
7471                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7472                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7473                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7474                     promoSweep = defaultPromoChoice;
7475                     selectFlag = 0; lastX = xPix; lastY = yPix;
7476                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7477                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7478                 }
7479                 if (appData.highlightDragging) {
7480                     SetHighlights(fromX, fromY, -1, -1);
7481                 } else {
7482                     ClearHighlights();
7483                 }
7484             } else fromX = fromY = -1;
7485             return;
7486         }
7487     }
7488
7489     /* fromX != -1 */
7490     if (clickType == Press && gameMode != EditPosition) {
7491         ChessSquare fromP;
7492         ChessSquare toP;
7493         int frc;
7494
7495         // ignore off-board to clicks
7496         if(y < 0 || x < 0) return;
7497
7498         /* Check if clicking again on the same color piece */
7499         fromP = boards[currentMove][fromY][fromX];
7500         toP = boards[currentMove][y][x];
7501         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7502         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7503            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7504              WhitePawn <= toP && toP <= WhiteKing &&
7505              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7506              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7507             (BlackPawn <= fromP && fromP <= BlackKing &&
7508              BlackPawn <= toP && toP <= BlackKing &&
7509              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7510              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7511             /* Clicked again on same color piece -- changed his mind */
7512             second = (x == fromX && y == fromY);
7513             killX = killY = -1;
7514             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7515                 second = FALSE; // first double-click rather than scond click
7516                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7517             }
7518             promoDefaultAltered = FALSE;
7519             MarkTargetSquares(1);
7520            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7521             if (appData.highlightDragging) {
7522                 SetHighlights(x, y, -1, -1);
7523             } else {
7524                 ClearHighlights();
7525             }
7526             if (OKToStartUserMove(x, y)) {
7527                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7528                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7529                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7530                  gatingPiece = boards[currentMove][fromY][fromX];
7531                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7532                 fromX = x;
7533                 fromY = y; dragging = 1;
7534                 ReportClick("lift", x, y);
7535                 MarkTargetSquares(0);
7536                 DragPieceBegin(xPix, yPix, FALSE);
7537                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7538                     promoSweep = defaultPromoChoice;
7539                     selectFlag = 0; lastX = xPix; lastY = yPix;
7540                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7541                 }
7542             }
7543            }
7544            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7545            second = FALSE;
7546         }
7547         // ignore clicks on holdings
7548         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7549     }
7550
7551     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7552         DragPieceEnd(xPix, yPix); dragging = 0;
7553         if(clearFlag) {
7554             // a deferred attempt to click-click move an empty square on top of a piece
7555             boards[currentMove][y][x] = EmptySquare;
7556             ClearHighlights();
7557             DrawPosition(FALSE, boards[currentMove]);
7558             fromX = fromY = -1; clearFlag = 0;
7559             return;
7560         }
7561         if (appData.animateDragging) {
7562             /* Undo animation damage if any */
7563             DrawPosition(FALSE, NULL);
7564         }
7565         if (second || sweepSelecting) {
7566             /* Second up/down in same square; just abort move */
7567             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7568             second = sweepSelecting = 0;
7569             fromX = fromY = -1;
7570             gatingPiece = EmptySquare;
7571             MarkTargetSquares(1);
7572             ClearHighlights();
7573             gotPremove = 0;
7574             ClearPremoveHighlights();
7575         } else {
7576             /* First upclick in same square; start click-click mode */
7577             SetHighlights(x, y, -1, -1);
7578         }
7579         return;
7580     }
7581
7582     clearFlag = 0;
7583
7584     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7585        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7586         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7587         DisplayMessage(_("only marked squares are legal"),"");
7588         DrawPosition(TRUE, NULL);
7589         return; // ignore to-click
7590     }
7591
7592     /* we now have a different from- and (possibly off-board) to-square */
7593     /* Completed move */
7594     if(!sweepSelecting) {
7595         toX = x;
7596         toY = y;
7597     }
7598
7599     piece = boards[currentMove][fromY][fromX];
7600
7601     saveAnimate = appData.animate;
7602     if (clickType == Press) {
7603         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7604         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7605             // must be Edit Position mode with empty-square selected
7606             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7607             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7608             return;
7609         }
7610         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7611             return;
7612         }
7613         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7614             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7615         } else
7616         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7617         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7618           if(appData.sweepSelect) {
7619             promoSweep = defaultPromoChoice;
7620             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7621             selectFlag = 0; lastX = xPix; lastY = yPix;
7622             Sweep(0); // Pawn that is going to promote: preview promotion piece
7623             sweepSelecting = 1;
7624             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7625             MarkTargetSquares(1);
7626           }
7627           return; // promo popup appears on up-click
7628         }
7629         /* Finish clickclick move */
7630         if (appData.animate || appData.highlightLastMove) {
7631             SetHighlights(fromX, fromY, toX, toY);
7632         } else {
7633             ClearHighlights();
7634         }
7635     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7636         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7637         if (appData.animate || appData.highlightLastMove) {
7638             SetHighlights(fromX, fromY, toX, toY);
7639         } else {
7640             ClearHighlights();
7641         }
7642     } else {
7643 #if 0
7644 // [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
7645         /* Finish drag move */
7646         if (appData.highlightLastMove) {
7647             SetHighlights(fromX, fromY, toX, toY);
7648         } else {
7649             ClearHighlights();
7650         }
7651 #endif
7652         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7653         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7654           dragging *= 2;            // flag button-less dragging if we are dragging
7655           MarkTargetSquares(1);
7656           if(x == killX && y == killY) killX = killY = -1; else {
7657             killX = x; killY = y;     //remeber this square as intermediate
7658             ReportClick("put", x, y); // and inform engine
7659             ReportClick("lift", x, y);
7660             MarkTargetSquares(0);
7661             return;
7662           }
7663         }
7664         DragPieceEnd(xPix, yPix); dragging = 0;
7665         /* Don't animate move and drag both */
7666         appData.animate = FALSE;
7667     }
7668
7669     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7670     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7671         ChessSquare piece = boards[currentMove][fromY][fromX];
7672         if(gameMode == EditPosition && piece != EmptySquare &&
7673            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7674             int n;
7675
7676             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7677                 n = PieceToNumber(piece - (int)BlackPawn);
7678                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7679                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7680                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7681             } else
7682             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7683                 n = PieceToNumber(piece);
7684                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7685                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7686                 boards[currentMove][n][BOARD_WIDTH-2]++;
7687             }
7688             boards[currentMove][fromY][fromX] = EmptySquare;
7689         }
7690         ClearHighlights();
7691         fromX = fromY = -1;
7692         MarkTargetSquares(1);
7693         DrawPosition(TRUE, boards[currentMove]);
7694         return;
7695     }
7696
7697     // off-board moves should not be highlighted
7698     if(x < 0 || y < 0) ClearHighlights();
7699     else ReportClick("put", x, y);
7700
7701     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7702
7703     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7704         SetHighlights(fromX, fromY, toX, toY);
7705         MarkTargetSquares(1);
7706         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7707             // [HGM] super: promotion to captured piece selected from holdings
7708             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7709             promotionChoice = TRUE;
7710             // kludge follows to temporarily execute move on display, without promoting yet
7711             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7712             boards[currentMove][toY][toX] = p;
7713             DrawPosition(FALSE, boards[currentMove]);
7714             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7715             boards[currentMove][toY][toX] = q;
7716             DisplayMessage("Click in holdings to choose piece", "");
7717             return;
7718         }
7719         PromotionPopUp(promoChoice);
7720     } else {
7721         int oldMove = currentMove;
7722         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7723         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7724         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7725         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7726            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7727             DrawPosition(TRUE, boards[currentMove]);
7728         MarkTargetSquares(1);
7729         fromX = fromY = -1;
7730     }
7731     appData.animate = saveAnimate;
7732     if (appData.animate || appData.animateDragging) {
7733         /* Undo animation damage if needed */
7734         DrawPosition(FALSE, NULL);
7735     }
7736 }
7737
7738 int
7739 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7740 {   // front-end-free part taken out of PieceMenuPopup
7741     int whichMenu; int xSqr, ySqr;
7742
7743     if(seekGraphUp) { // [HGM] seekgraph
7744         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7745         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7746         return -2;
7747     }
7748
7749     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7750          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7751         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7752         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7753         if(action == Press)   {
7754             originalFlip = flipView;
7755             flipView = !flipView; // temporarily flip board to see game from partners perspective
7756             DrawPosition(TRUE, partnerBoard);
7757             DisplayMessage(partnerStatus, "");
7758             partnerUp = TRUE;
7759         } else if(action == Release) {
7760             flipView = originalFlip;
7761             DrawPosition(TRUE, boards[currentMove]);
7762             partnerUp = FALSE;
7763         }
7764         return -2;
7765     }
7766
7767     xSqr = EventToSquare(x, BOARD_WIDTH);
7768     ySqr = EventToSquare(y, BOARD_HEIGHT);
7769     if (action == Release) {
7770         if(pieceSweep != EmptySquare) {
7771             EditPositionMenuEvent(pieceSweep, toX, toY);
7772             pieceSweep = EmptySquare;
7773         } else UnLoadPV(); // [HGM] pv
7774     }
7775     if (action != Press) return -2; // return code to be ignored
7776     switch (gameMode) {
7777       case IcsExamining:
7778         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7779       case EditPosition:
7780         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7781         if (xSqr < 0 || ySqr < 0) return -1;
7782         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7783         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7784         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7785         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7786         NextPiece(0);
7787         return 2; // grab
7788       case IcsObserving:
7789         if(!appData.icsEngineAnalyze) return -1;
7790       case IcsPlayingWhite:
7791       case IcsPlayingBlack:
7792         if(!appData.zippyPlay) goto noZip;
7793       case AnalyzeMode:
7794       case AnalyzeFile:
7795       case MachinePlaysWhite:
7796       case MachinePlaysBlack:
7797       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7798         if (!appData.dropMenu) {
7799           LoadPV(x, y);
7800           return 2; // flag front-end to grab mouse events
7801         }
7802         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7803            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7804       case EditGame:
7805       noZip:
7806         if (xSqr < 0 || ySqr < 0) return -1;
7807         if (!appData.dropMenu || appData.testLegality &&
7808             gameInfo.variant != VariantBughouse &&
7809             gameInfo.variant != VariantCrazyhouse) return -1;
7810         whichMenu = 1; // drop menu
7811         break;
7812       default:
7813         return -1;
7814     }
7815
7816     if (((*fromX = xSqr) < 0) ||
7817         ((*fromY = ySqr) < 0)) {
7818         *fromX = *fromY = -1;
7819         return -1;
7820     }
7821     if (flipView)
7822       *fromX = BOARD_WIDTH - 1 - *fromX;
7823     else
7824       *fromY = BOARD_HEIGHT - 1 - *fromY;
7825
7826     return whichMenu;
7827 }
7828
7829 void
7830 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7831 {
7832 //    char * hint = lastHint;
7833     FrontEndProgramStats stats;
7834
7835     stats.which = cps == &first ? 0 : 1;
7836     stats.depth = cpstats->depth;
7837     stats.nodes = cpstats->nodes;
7838     stats.score = cpstats->score;
7839     stats.time = cpstats->time;
7840     stats.pv = cpstats->movelist;
7841     stats.hint = lastHint;
7842     stats.an_move_index = 0;
7843     stats.an_move_count = 0;
7844
7845     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7846         stats.hint = cpstats->move_name;
7847         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7848         stats.an_move_count = cpstats->nr_moves;
7849     }
7850
7851     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
7852
7853     SetProgramStats( &stats );
7854 }
7855
7856 void
7857 ClearEngineOutputPane (int which)
7858 {
7859     static FrontEndProgramStats dummyStats;
7860     dummyStats.which = which;
7861     dummyStats.pv = "#";
7862     SetProgramStats( &dummyStats );
7863 }
7864
7865 #define MAXPLAYERS 500
7866
7867 char *
7868 TourneyStandings (int display)
7869 {
7870     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7871     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7872     char result, *p, *names[MAXPLAYERS];
7873
7874     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7875         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7876     names[0] = p = strdup(appData.participants);
7877     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7878
7879     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7880
7881     while(result = appData.results[nr]) {
7882         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7883         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7884         wScore = bScore = 0;
7885         switch(result) {
7886           case '+': wScore = 2; break;
7887           case '-': bScore = 2; break;
7888           case '=': wScore = bScore = 1; break;
7889           case ' ':
7890           case '*': return strdup("busy"); // tourney not finished
7891         }
7892         score[w] += wScore;
7893         score[b] += bScore;
7894         games[w]++;
7895         games[b]++;
7896         nr++;
7897     }
7898     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7899     for(w=0; w<nPlayers; w++) {
7900         bScore = -1;
7901         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7902         ranking[w] = b; points[w] = bScore; score[b] = -2;
7903     }
7904     p = malloc(nPlayers*34+1);
7905     for(w=0; w<nPlayers && w<display; w++)
7906         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7907     free(names[0]);
7908     return p;
7909 }
7910
7911 void
7912 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7913 {       // count all piece types
7914         int p, f, r;
7915         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7916         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7917         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7918                 p = board[r][f];
7919                 pCnt[p]++;
7920                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7921                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7922                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7923                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7924                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7925                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7926         }
7927 }
7928
7929 int
7930 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7931 {
7932         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7933         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7934
7935         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7936         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7937         if(myPawns == 2 && nMine == 3) // KPP
7938             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7939         if(myPawns == 1 && nMine == 2) // KP
7940             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7941         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7942             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7943         if(myPawns) return FALSE;
7944         if(pCnt[WhiteRook+side])
7945             return pCnt[BlackRook-side] ||
7946                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7947                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7948                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7949         if(pCnt[WhiteCannon+side]) {
7950             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7951             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7952         }
7953         if(pCnt[WhiteKnight+side])
7954             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7955         return FALSE;
7956 }
7957
7958 int
7959 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7960 {
7961         VariantClass v = gameInfo.variant;
7962
7963         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7964         if(v == VariantShatranj) return TRUE; // always winnable through baring
7965         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7966         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7967
7968         if(v == VariantXiangqi) {
7969                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7970
7971                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7972                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7973                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7974                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7975                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7976                 if(stale) // we have at least one last-rank P plus perhaps C
7977                     return majors // KPKX
7978                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7979                 else // KCA*E*
7980                     return pCnt[WhiteFerz+side] // KCAK
7981                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7982                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7983                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7984
7985         } else if(v == VariantKnightmate) {
7986                 if(nMine == 1) return FALSE;
7987                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7988         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7989                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7990
7991                 if(nMine == 1) return FALSE; // bare King
7992                 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
7993                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7994                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7995                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7996                 if(pCnt[WhiteKnight+side])
7997                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7998                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7999                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8000                 if(nBishops)
8001                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8002                 if(pCnt[WhiteAlfil+side])
8003                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8004                 if(pCnt[WhiteWazir+side])
8005                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8006         }
8007
8008         return TRUE;
8009 }
8010
8011 int
8012 CompareWithRights (Board b1, Board b2)
8013 {
8014     int rights = 0;
8015     if(!CompareBoards(b1, b2)) return FALSE;
8016     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8017     /* compare castling rights */
8018     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8019            rights++; /* King lost rights, while rook still had them */
8020     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8021         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8022            rights++; /* but at least one rook lost them */
8023     }
8024     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8025            rights++;
8026     if( b1[CASTLING][5] != NoRights ) {
8027         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8028            rights++;
8029     }
8030     return rights == 0;
8031 }
8032
8033 int
8034 Adjudicate (ChessProgramState *cps)
8035 {       // [HGM] some adjudications useful with buggy engines
8036         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8037         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8038         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8039         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8040         int k, drop, count = 0; static int bare = 1;
8041         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8042         Boolean canAdjudicate = !appData.icsActive;
8043
8044         // most tests only when we understand the game, i.e. legality-checking on
8045             if( appData.testLegality )
8046             {   /* [HGM] Some more adjudications for obstinate engines */
8047                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8048                 static int moveCount = 6;
8049                 ChessMove result;
8050                 char *reason = NULL;
8051
8052                 /* Count what is on board. */
8053                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8054
8055                 /* Some material-based adjudications that have to be made before stalemate test */
8056                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8057                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8058                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8059                      if(canAdjudicate && appData.checkMates) {
8060                          if(engineOpponent)
8061                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8062                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8063                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8064                          return 1;
8065                      }
8066                 }
8067
8068                 /* Bare King in Shatranj (loses) or Losers (wins) */
8069                 if( nrW == 1 || nrB == 1) {
8070                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8071                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8072                      if(canAdjudicate && appData.checkMates) {
8073                          if(engineOpponent)
8074                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8075                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8076                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8077                          return 1;
8078                      }
8079                   } else
8080                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8081                   {    /* bare King */
8082                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8083                         if(canAdjudicate && appData.checkMates) {
8084                             /* but only adjudicate if adjudication enabled */
8085                             if(engineOpponent)
8086                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8087                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8088                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8089                             return 1;
8090                         }
8091                   }
8092                 } else bare = 1;
8093
8094
8095             // don't wait for engine to announce game end if we can judge ourselves
8096             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8097               case MT_CHECK:
8098                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8099                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8100                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8101                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8102                             checkCnt++;
8103                         if(checkCnt >= 2) {
8104                             reason = "Xboard adjudication: 3rd check";
8105                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8106                             break;
8107                         }
8108                     }
8109                 }
8110               case MT_NONE:
8111               default:
8112                 break;
8113               case MT_STEALMATE:
8114               case MT_STALEMATE:
8115               case MT_STAINMATE:
8116                 reason = "Xboard adjudication: Stalemate";
8117                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8118                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8119                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8120                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8121                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8122                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8123                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8124                                                                         EP_CHECKMATE : EP_WINS);
8125                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8126                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8127                 }
8128                 break;
8129               case MT_CHECKMATE:
8130                 reason = "Xboard adjudication: Checkmate";
8131                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8132                 if(gameInfo.variant == VariantShogi) {
8133                     if(forwardMostMove > backwardMostMove
8134                        && moveList[forwardMostMove-1][1] == '@'
8135                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8136                         reason = "XBoard adjudication: pawn-drop mate";
8137                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8138                     }
8139                 }
8140                 break;
8141             }
8142
8143                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8144                     case EP_STALEMATE:
8145                         result = GameIsDrawn; break;
8146                     case EP_CHECKMATE:
8147                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8148                     case EP_WINS:
8149                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8150                     default:
8151                         result = EndOfFile;
8152                 }
8153                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8154                     if(engineOpponent)
8155                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8156                     GameEnds( result, reason, GE_XBOARD );
8157                     return 1;
8158                 }
8159
8160                 /* Next absolutely insufficient mating material. */
8161                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8162                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8163                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8164
8165                      /* always flag draws, for judging claims */
8166                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8167
8168                      if(canAdjudicate && appData.materialDraws) {
8169                          /* but only adjudicate them if adjudication enabled */
8170                          if(engineOpponent) {
8171                            SendToProgram("force\n", engineOpponent); // suppress reply
8172                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8173                          }
8174                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8175                          return 1;
8176                      }
8177                 }
8178
8179                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8180                 if(gameInfo.variant == VariantXiangqi ?
8181                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8182                  : nrW + nrB == 4 &&
8183                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8184                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8185                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8186                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8187                    ) ) {
8188                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8189                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8190                           if(engineOpponent) {
8191                             SendToProgram("force\n", engineOpponent); // suppress reply
8192                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8193                           }
8194                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8195                           return 1;
8196                      }
8197                 } else moveCount = 6;
8198             }
8199
8200         // Repetition draws and 50-move rule can be applied independently of legality testing
8201
8202                 /* Check for rep-draws */
8203                 count = 0;
8204                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8205                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8206                 for(k = forwardMostMove-2;
8207                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8208                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8209                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8210                     k-=2)
8211                 {   int rights=0;
8212                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8213                         /* compare castling rights */
8214                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8215                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8216                                 rights++; /* King lost rights, while rook still had them */
8217                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8218                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8219                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8220                                    rights++; /* but at least one rook lost them */
8221                         }
8222                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8223                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8224                                 rights++;
8225                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8226                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8227                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8228                                    rights++;
8229                         }
8230                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8231                             && appData.drawRepeats > 1) {
8232                              /* adjudicate after user-specified nr of repeats */
8233                              int result = GameIsDrawn;
8234                              char *details = "XBoard adjudication: repetition draw";
8235                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8236                                 // [HGM] xiangqi: check for forbidden perpetuals
8237                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8238                                 for(m=forwardMostMove; m>k; m-=2) {
8239                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8240                                         ourPerpetual = 0; // the current mover did not always check
8241                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8242                                         hisPerpetual = 0; // the opponent did not always check
8243                                 }
8244                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8245                                                                         ourPerpetual, hisPerpetual);
8246                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8247                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8248                                     details = "Xboard adjudication: perpetual checking";
8249                                 } else
8250                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8251                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8252                                 } else
8253                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8254                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8255                                         result = BlackWins;
8256                                         details = "Xboard adjudication: repetition";
8257                                     }
8258                                 } else // it must be XQ
8259                                 // Now check for perpetual chases
8260                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8261                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8262                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8263                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8264                                         static char resdet[MSG_SIZ];
8265                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8266                                         details = resdet;
8267                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8268                                     } else
8269                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8270                                         break; // Abort repetition-checking loop.
8271                                 }
8272                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8273                              }
8274                              if(engineOpponent) {
8275                                SendToProgram("force\n", engineOpponent); // suppress reply
8276                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8277                              }
8278                              GameEnds( result, details, GE_XBOARD );
8279                              return 1;
8280                         }
8281                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8282                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8283                     }
8284                 }
8285
8286                 /* Now we test for 50-move draws. Determine ply count */
8287                 count = forwardMostMove;
8288                 /* look for last irreversble move */
8289                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8290                     count--;
8291                 /* if we hit starting position, add initial plies */
8292                 if( count == backwardMostMove )
8293                     count -= initialRulePlies;
8294                 count = forwardMostMove - count;
8295                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8296                         // adjust reversible move counter for checks in Xiangqi
8297                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8298                         if(i < backwardMostMove) i = backwardMostMove;
8299                         while(i <= forwardMostMove) {
8300                                 lastCheck = inCheck; // check evasion does not count
8301                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8302                                 if(inCheck || lastCheck) count--; // check does not count
8303                                 i++;
8304                         }
8305                 }
8306                 if( count >= 100)
8307                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8308                          /* this is used to judge if draw claims are legal */
8309                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8310                          if(engineOpponent) {
8311                            SendToProgram("force\n", engineOpponent); // suppress reply
8312                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8313                          }
8314                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8315                          return 1;
8316                 }
8317
8318                 /* if draw offer is pending, treat it as a draw claim
8319                  * when draw condition present, to allow engines a way to
8320                  * claim draws before making their move to avoid a race
8321                  * condition occurring after their move
8322                  */
8323                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8324                          char *p = NULL;
8325                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8326                              p = "Draw claim: 50-move rule";
8327                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8328                              p = "Draw claim: 3-fold repetition";
8329                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8330                              p = "Draw claim: insufficient mating material";
8331                          if( p != NULL && canAdjudicate) {
8332                              if(engineOpponent) {
8333                                SendToProgram("force\n", engineOpponent); // suppress reply
8334                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8335                              }
8336                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8337                              return 1;
8338                          }
8339                 }
8340
8341                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8342                     if(engineOpponent) {
8343                       SendToProgram("force\n", engineOpponent); // suppress reply
8344                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8345                     }
8346                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8347                     return 1;
8348                 }
8349         return 0;
8350 }
8351
8352 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8353 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8354 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8355
8356 static int
8357 BitbaseProbe ()
8358 {
8359     int pieces[10], squares[10], cnt=0, r, f, res;
8360     static int loaded;
8361     static PPROBE_EGBB probeBB;
8362     if(!appData.testLegality) return 10;
8363     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8364     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8365     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8366     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8367         ChessSquare piece = boards[forwardMostMove][r][f];
8368         int black = (piece >= BlackPawn);
8369         int type = piece - black*BlackPawn;
8370         if(piece == EmptySquare) continue;
8371         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8372         if(type == WhiteKing) type = WhiteQueen + 1;
8373         type = egbbCode[type];
8374         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8375         pieces[cnt] = type + black*6;
8376         if(++cnt > 5) return 11;
8377     }
8378     pieces[cnt] = squares[cnt] = 0;
8379     // probe EGBB
8380     if(loaded == 2) return 13; // loading failed before
8381     if(loaded == 0) {
8382         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8383         HMODULE lib;
8384         PLOAD_EGBB loadBB;
8385         loaded = 2; // prepare for failure
8386         if(!path) return 13; // no egbb installed
8387         strncpy(buf, path + 8, MSG_SIZ);
8388         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8389         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8390         lib = LoadLibrary(buf);
8391         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8392         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8393         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8394         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8395         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8396         loaded = 1; // success!
8397     }
8398     res = probeBB(forwardMostMove & 1, pieces, squares);
8399     return res > 0 ? 1 : res < 0 ? -1 : 0;
8400 }
8401
8402 char *
8403 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8404 {   // [HGM] book: this routine intercepts moves to simulate book replies
8405     char *bookHit = NULL;
8406
8407     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8408         char buf[MSG_SIZ];
8409         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8410         SendToProgram(buf, cps);
8411     }
8412     //first determine if the incoming move brings opponent into his book
8413     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8414         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8415     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8416     if(bookHit != NULL && !cps->bookSuspend) {
8417         // make sure opponent is not going to reply after receiving move to book position
8418         SendToProgram("force\n", cps);
8419         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8420     }
8421     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8422     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8423     // now arrange restart after book miss
8424     if(bookHit) {
8425         // after a book hit we never send 'go', and the code after the call to this routine
8426         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8427         char buf[MSG_SIZ], *move = bookHit;
8428         if(cps->useSAN) {
8429             int fromX, fromY, toX, toY;
8430             char promoChar;
8431             ChessMove moveType;
8432             move = buf + 30;
8433             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8434                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8435                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8436                                     PosFlags(forwardMostMove),
8437                                     fromY, fromX, toY, toX, promoChar, move);
8438             } else {
8439                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8440                 bookHit = NULL;
8441             }
8442         }
8443         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8444         SendToProgram(buf, cps);
8445         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8446     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8447         SendToProgram("go\n", cps);
8448         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8449     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8450         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8451             SendToProgram("go\n", cps);
8452         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8453     }
8454     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8455 }
8456
8457 int
8458 LoadError (char *errmess, ChessProgramState *cps)
8459 {   // unloads engine and switches back to -ncp mode if it was first
8460     if(cps->initDone) return FALSE;
8461     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8462     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8463     cps->pr = NoProc;
8464     if(cps == &first) {
8465         appData.noChessProgram = TRUE;
8466         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8467         gameMode = BeginningOfGame; ModeHighlight();
8468         SetNCPMode();
8469     }
8470     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8471     DisplayMessage("", ""); // erase waiting message
8472     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8473     return TRUE;
8474 }
8475
8476 char *savedMessage;
8477 ChessProgramState *savedState;
8478 void
8479 DeferredBookMove (void)
8480 {
8481         if(savedState->lastPing != savedState->lastPong)
8482                     ScheduleDelayedEvent(DeferredBookMove, 10);
8483         else
8484         HandleMachineMove(savedMessage, savedState);
8485 }
8486
8487 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8488 static ChessProgramState *stalledEngine;
8489 static char stashedInputMove[MSG_SIZ];
8490
8491 void
8492 HandleMachineMove (char *message, ChessProgramState *cps)
8493 {
8494     static char firstLeg[20];
8495     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8496     char realname[MSG_SIZ];
8497     int fromX, fromY, toX, toY;
8498     ChessMove moveType;
8499     char promoChar, roar;
8500     char *p, *pv=buf1;
8501     int machineWhite, oldError;
8502     char *bookHit;
8503
8504     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8505         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8506         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8507             DisplayError(_("Invalid pairing from pairing engine"), 0);
8508             return;
8509         }
8510         pairingReceived = 1;
8511         NextMatchGame();
8512         return; // Skim the pairing messages here.
8513     }
8514
8515     oldError = cps->userError; cps->userError = 0;
8516
8517 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8518     /*
8519      * Kludge to ignore BEL characters
8520      */
8521     while (*message == '\007') message++;
8522
8523     /*
8524      * [HGM] engine debug message: ignore lines starting with '#' character
8525      */
8526     if(cps->debug && *message == '#') return;
8527
8528     /*
8529      * Look for book output
8530      */
8531     if (cps == &first && bookRequested) {
8532         if (message[0] == '\t' || message[0] == ' ') {
8533             /* Part of the book output is here; append it */
8534             strcat(bookOutput, message);
8535             strcat(bookOutput, "  \n");
8536             return;
8537         } else if (bookOutput[0] != NULLCHAR) {
8538             /* All of book output has arrived; display it */
8539             char *p = bookOutput;
8540             while (*p != NULLCHAR) {
8541                 if (*p == '\t') *p = ' ';
8542                 p++;
8543             }
8544             DisplayInformation(bookOutput);
8545             bookRequested = FALSE;
8546             /* Fall through to parse the current output */
8547         }
8548     }
8549
8550     /*
8551      * Look for machine move.
8552      */
8553     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8554         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8555     {
8556         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8557             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8558             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8559             stalledEngine = cps;
8560             if(appData.ponderNextMove) { // bring opponent out of ponder
8561                 if(gameMode == TwoMachinesPlay) {
8562                     if(cps->other->pause)
8563                         PauseEngine(cps->other);
8564                     else
8565                         SendToProgram("easy\n", cps->other);
8566                 }
8567             }
8568             StopClocks();
8569             return;
8570         }
8571
8572         /* This method is only useful on engines that support ping */
8573         if (cps->lastPing != cps->lastPong) {
8574           if (gameMode == BeginningOfGame) {
8575             /* Extra move from before last new; ignore */
8576             if (appData.debugMode) {
8577                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8578             }
8579           } else {
8580             if (appData.debugMode) {
8581                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8582                         cps->which, gameMode);
8583             }
8584
8585             SendToProgram("undo\n", cps);
8586           }
8587           return;
8588         }
8589
8590         switch (gameMode) {
8591           case BeginningOfGame:
8592             /* Extra move from before last reset; ignore */
8593             if (appData.debugMode) {
8594                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8595             }
8596             return;
8597
8598           case EndOfGame:
8599           case IcsIdle:
8600           default:
8601             /* Extra move after we tried to stop.  The mode test is
8602                not a reliable way of detecting this problem, but it's
8603                the best we can do on engines that don't support ping.
8604             */
8605             if (appData.debugMode) {
8606                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8607                         cps->which, gameMode);
8608             }
8609             SendToProgram("undo\n", cps);
8610             return;
8611
8612           case MachinePlaysWhite:
8613           case IcsPlayingWhite:
8614             machineWhite = TRUE;
8615             break;
8616
8617           case MachinePlaysBlack:
8618           case IcsPlayingBlack:
8619             machineWhite = FALSE;
8620             break;
8621
8622           case TwoMachinesPlay:
8623             machineWhite = (cps->twoMachinesColor[0] == 'w');
8624             break;
8625         }
8626         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8627             if (appData.debugMode) {
8628                 fprintf(debugFP,
8629                         "Ignoring move out of turn by %s, gameMode %d"
8630                         ", forwardMost %d\n",
8631                         cps->which, gameMode, forwardMostMove);
8632             }
8633             return;
8634         }
8635
8636         if(cps->alphaRank) AlphaRank(machineMove, 4);
8637
8638         // [HGM] lion: (some very limited) support for Alien protocol
8639         killX = killY = -1;
8640         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8641             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8642             return;
8643         } else if(firstLeg[0]) { // there was a previous leg;
8644             // only support case where same piece makes two step (and don't even test that!)
8645             char buf[20], *p = machineMove+1, *q = buf+1, f;
8646             safeStrCpy(buf, machineMove, 20);
8647             while(isdigit(*q)) q++; // find start of to-square
8648             safeStrCpy(machineMove, firstLeg, 20);
8649             while(isdigit(*p)) p++;
8650             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8651             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8652             firstLeg[0] = NULLCHAR;
8653         }
8654
8655         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8656                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8657             /* Machine move could not be parsed; ignore it. */
8658           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8659                     machineMove, _(cps->which));
8660             DisplayMoveError(buf1);
8661             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8662                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8663             if (gameMode == TwoMachinesPlay) {
8664               GameEnds(machineWhite ? BlackWins : WhiteWins,
8665                        buf1, GE_XBOARD);
8666             }
8667             return;
8668         }
8669
8670         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8671         /* So we have to redo legality test with true e.p. status here,  */
8672         /* to make sure an illegal e.p. capture does not slip through,   */
8673         /* to cause a forfeit on a justified illegal-move complaint      */
8674         /* of the opponent.                                              */
8675         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8676            ChessMove moveType;
8677            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8678                              fromY, fromX, toY, toX, promoChar);
8679             if(moveType == IllegalMove) {
8680               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8681                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8682                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8683                            buf1, GE_XBOARD);
8684                 return;
8685            } else if(!appData.fischerCastling)
8686            /* [HGM] Kludge to handle engines that send FRC-style castling
8687               when they shouldn't (like TSCP-Gothic) */
8688            switch(moveType) {
8689              case WhiteASideCastleFR:
8690              case BlackASideCastleFR:
8691                toX+=2;
8692                currentMoveString[2]++;
8693                break;
8694              case WhiteHSideCastleFR:
8695              case BlackHSideCastleFR:
8696                toX--;
8697                currentMoveString[2]--;
8698                break;
8699              default: ; // nothing to do, but suppresses warning of pedantic compilers
8700            }
8701         }
8702         hintRequested = FALSE;
8703         lastHint[0] = NULLCHAR;
8704         bookRequested = FALSE;
8705         /* Program may be pondering now */
8706         cps->maybeThinking = TRUE;
8707         if (cps->sendTime == 2) cps->sendTime = 1;
8708         if (cps->offeredDraw) cps->offeredDraw--;
8709
8710         /* [AS] Save move info*/
8711         pvInfoList[ forwardMostMove ].score = programStats.score;
8712         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8713         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8714
8715         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8716
8717         /* Test suites abort the 'game' after one move */
8718         if(*appData.finger) {
8719            static FILE *f;
8720            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8721            if(!f) f = fopen(appData.finger, "w");
8722            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8723            else { DisplayFatalError("Bad output file", errno, 0); return; }
8724            free(fen);
8725            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8726         }
8727
8728         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8729         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8730             int count = 0;
8731
8732             while( count < adjudicateLossPlies ) {
8733                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8734
8735                 if( count & 1 ) {
8736                     score = -score; /* Flip score for winning side */
8737                 }
8738
8739                 if( score > appData.adjudicateLossThreshold ) {
8740                     break;
8741                 }
8742
8743                 count++;
8744             }
8745
8746             if( count >= adjudicateLossPlies ) {
8747                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8748
8749                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8750                     "Xboard adjudication",
8751                     GE_XBOARD );
8752
8753                 return;
8754             }
8755         }
8756
8757         if(Adjudicate(cps)) {
8758             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8759             return; // [HGM] adjudicate: for all automatic game ends
8760         }
8761
8762 #if ZIPPY
8763         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8764             first.initDone) {
8765           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8766                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8767                 SendToICS("draw ");
8768                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8769           }
8770           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8771           ics_user_moved = 1;
8772           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8773                 char buf[3*MSG_SIZ];
8774
8775                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8776                         programStats.score / 100.,
8777                         programStats.depth,
8778                         programStats.time / 100.,
8779                         (unsigned int)programStats.nodes,
8780                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8781                         programStats.movelist);
8782                 SendToICS(buf);
8783           }
8784         }
8785 #endif
8786
8787         /* [AS] Clear stats for next move */
8788         ClearProgramStats();
8789         thinkOutput[0] = NULLCHAR;
8790         hiddenThinkOutputState = 0;
8791
8792         bookHit = NULL;
8793         if (gameMode == TwoMachinesPlay) {
8794             /* [HGM] relaying draw offers moved to after reception of move */
8795             /* and interpreting offer as claim if it brings draw condition */
8796             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8797                 SendToProgram("draw\n", cps->other);
8798             }
8799             if (cps->other->sendTime) {
8800                 SendTimeRemaining(cps->other,
8801                                   cps->other->twoMachinesColor[0] == 'w');
8802             }
8803             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8804             if (firstMove && !bookHit) {
8805                 firstMove = FALSE;
8806                 if (cps->other->useColors) {
8807                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8808                 }
8809                 SendToProgram("go\n", cps->other);
8810             }
8811             cps->other->maybeThinking = TRUE;
8812         }
8813
8814         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8815
8816         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8817
8818         if (!pausing && appData.ringBellAfterMoves) {
8819             if(!roar) RingBell();
8820         }
8821
8822         /*
8823          * Reenable menu items that were disabled while
8824          * machine was thinking
8825          */
8826         if (gameMode != TwoMachinesPlay)
8827             SetUserThinkingEnables();
8828
8829         // [HGM] book: after book hit opponent has received move and is now in force mode
8830         // force the book reply into it, and then fake that it outputted this move by jumping
8831         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8832         if(bookHit) {
8833                 static char bookMove[MSG_SIZ]; // a bit generous?
8834
8835                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8836                 strcat(bookMove, bookHit);
8837                 message = bookMove;
8838                 cps = cps->other;
8839                 programStats.nodes = programStats.depth = programStats.time =
8840                 programStats.score = programStats.got_only_move = 0;
8841                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8842
8843                 if(cps->lastPing != cps->lastPong) {
8844                     savedMessage = message; // args for deferred call
8845                     savedState = cps;
8846                     ScheduleDelayedEvent(DeferredBookMove, 10);
8847                     return;
8848                 }
8849                 goto FakeBookMove;
8850         }
8851
8852         return;
8853     }
8854
8855     /* Set special modes for chess engines.  Later something general
8856      *  could be added here; for now there is just one kludge feature,
8857      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8858      *  when "xboard" is given as an interactive command.
8859      */
8860     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8861         cps->useSigint = FALSE;
8862         cps->useSigterm = FALSE;
8863     }
8864     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8865       ParseFeatures(message+8, cps);
8866       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8867     }
8868
8869     if (!strncmp(message, "setup ", 6) && 
8870         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8871           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8872                                         ) { // [HGM] allow first engine to define opening position
8873       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8874       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8875       *buf = NULLCHAR;
8876       if(sscanf(message, "setup (%s", buf) == 1) {
8877         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8878         ASSIGN(appData.pieceToCharTable, buf);
8879       }
8880       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8881       if(dummy >= 3) {
8882         while(message[s] && message[s++] != ' ');
8883         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8884            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8885             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8886             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8887           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8888           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8889           startedFromSetupPosition = FALSE;
8890         }
8891       }
8892       if(startedFromSetupPosition) return;
8893       ParseFEN(boards[0], &dummy, message+s, FALSE);
8894       DrawPosition(TRUE, boards[0]);
8895       startedFromSetupPosition = TRUE;
8896       return;
8897     }
8898     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8899       ChessSquare piece = WhitePawn;
8900       char *p=buf2;
8901       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8902       piece += CharToPiece(*p) - WhitePawn;
8903       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8904       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8905       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8906       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8907       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8908       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8909                                                && gameInfo.variant != VariantFairy    ) return;
8910       if(piece < EmptySquare) {
8911         pieceDefs = TRUE;
8912         ASSIGN(pieceDesc[piece], buf1);
8913         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8914       }
8915       return;
8916     }
8917     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8918      * want this, I was asked to put it in, and obliged.
8919      */
8920     if (!strncmp(message, "setboard ", 9)) {
8921         Board initial_position;
8922
8923         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8924
8925         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8926             DisplayError(_("Bad FEN received from engine"), 0);
8927             return ;
8928         } else {
8929            Reset(TRUE, FALSE);
8930            CopyBoard(boards[0], initial_position);
8931            initialRulePlies = FENrulePlies;
8932            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8933            else gameMode = MachinePlaysBlack;
8934            DrawPosition(FALSE, boards[currentMove]);
8935         }
8936         return;
8937     }
8938
8939     /*
8940      * Look for communication commands
8941      */
8942     if (!strncmp(message, "telluser ", 9)) {
8943         if(message[9] == '\\' && message[10] == '\\')
8944             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8945         PlayTellSound();
8946         DisplayNote(message + 9);
8947         return;
8948     }
8949     if (!strncmp(message, "tellusererror ", 14)) {
8950         cps->userError = 1;
8951         if(message[14] == '\\' && message[15] == '\\')
8952             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8953         PlayTellSound();
8954         DisplayError(message + 14, 0);
8955         return;
8956     }
8957     if (!strncmp(message, "tellopponent ", 13)) {
8958       if (appData.icsActive) {
8959         if (loggedOn) {
8960           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8961           SendToICS(buf1);
8962         }
8963       } else {
8964         DisplayNote(message + 13);
8965       }
8966       return;
8967     }
8968     if (!strncmp(message, "tellothers ", 11)) {
8969       if (appData.icsActive) {
8970         if (loggedOn) {
8971           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8972           SendToICS(buf1);
8973         }
8974       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8975       return;
8976     }
8977     if (!strncmp(message, "tellall ", 8)) {
8978       if (appData.icsActive) {
8979         if (loggedOn) {
8980           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8981           SendToICS(buf1);
8982         }
8983       } else {
8984         DisplayNote(message + 8);
8985       }
8986       return;
8987     }
8988     if (strncmp(message, "warning", 7) == 0) {
8989         /* Undocumented feature, use tellusererror in new code */
8990         DisplayError(message, 0);
8991         return;
8992     }
8993     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8994         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8995         strcat(realname, " query");
8996         AskQuestion(realname, buf2, buf1, cps->pr);
8997         return;
8998     }
8999     /* Commands from the engine directly to ICS.  We don't allow these to be
9000      *  sent until we are logged on. Crafty kibitzes have been known to
9001      *  interfere with the login process.
9002      */
9003     if (loggedOn) {
9004         if (!strncmp(message, "tellics ", 8)) {
9005             SendToICS(message + 8);
9006             SendToICS("\n");
9007             return;
9008         }
9009         if (!strncmp(message, "tellicsnoalias ", 15)) {
9010             SendToICS(ics_prefix);
9011             SendToICS(message + 15);
9012             SendToICS("\n");
9013             return;
9014         }
9015         /* The following are for backward compatibility only */
9016         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9017             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9018             SendToICS(ics_prefix);
9019             SendToICS(message);
9020             SendToICS("\n");
9021             return;
9022         }
9023     }
9024     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9025         if(initPing == cps->lastPong) {
9026             if(gameInfo.variant == VariantUnknown) {
9027                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9028                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9029                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9030             }
9031             initPing = -1;
9032         }
9033         return;
9034     }
9035     if(!strncmp(message, "highlight ", 10)) {
9036         if(appData.testLegality && appData.markers) return;
9037         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9038         return;
9039     }
9040     if(!strncmp(message, "click ", 6)) {
9041         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9042         if(appData.testLegality || !appData.oneClick) return;
9043         sscanf(message+6, "%c%d%c", &f, &y, &c);
9044         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9045         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9046         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9047         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9048         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9049         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9050             LeftClick(Release, lastLeftX, lastLeftY);
9051         controlKey  = (c == ',');
9052         LeftClick(Press, x, y);
9053         LeftClick(Release, x, y);
9054         first.highlight = f;
9055         return;
9056     }
9057     /*
9058      * If the move is illegal, cancel it and redraw the board.
9059      * Also deal with other error cases.  Matching is rather loose
9060      * here to accommodate engines written before the spec.
9061      */
9062     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9063         strncmp(message, "Error", 5) == 0) {
9064         if (StrStr(message, "name") ||
9065             StrStr(message, "rating") || StrStr(message, "?") ||
9066             StrStr(message, "result") || StrStr(message, "board") ||
9067             StrStr(message, "bk") || StrStr(message, "computer") ||
9068             StrStr(message, "variant") || StrStr(message, "hint") ||
9069             StrStr(message, "random") || StrStr(message, "depth") ||
9070             StrStr(message, "accepted")) {
9071             return;
9072         }
9073         if (StrStr(message, "protover")) {
9074           /* Program is responding to input, so it's apparently done
9075              initializing, and this error message indicates it is
9076              protocol version 1.  So we don't need to wait any longer
9077              for it to initialize and send feature commands. */
9078           FeatureDone(cps, 1);
9079           cps->protocolVersion = 1;
9080           return;
9081         }
9082         cps->maybeThinking = FALSE;
9083
9084         if (StrStr(message, "draw")) {
9085             /* Program doesn't have "draw" command */
9086             cps->sendDrawOffers = 0;
9087             return;
9088         }
9089         if (cps->sendTime != 1 &&
9090             (StrStr(message, "time") || StrStr(message, "otim"))) {
9091           /* Program apparently doesn't have "time" or "otim" command */
9092           cps->sendTime = 0;
9093           return;
9094         }
9095         if (StrStr(message, "analyze")) {
9096             cps->analysisSupport = FALSE;
9097             cps->analyzing = FALSE;
9098 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9099             EditGameEvent(); // [HGM] try to preserve loaded game
9100             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9101             DisplayError(buf2, 0);
9102             return;
9103         }
9104         if (StrStr(message, "(no matching move)st")) {
9105           /* Special kludge for GNU Chess 4 only */
9106           cps->stKludge = TRUE;
9107           SendTimeControl(cps, movesPerSession, timeControl,
9108                           timeIncrement, appData.searchDepth,
9109                           searchTime);
9110           return;
9111         }
9112         if (StrStr(message, "(no matching move)sd")) {
9113           /* Special kludge for GNU Chess 4 only */
9114           cps->sdKludge = TRUE;
9115           SendTimeControl(cps, movesPerSession, timeControl,
9116                           timeIncrement, appData.searchDepth,
9117                           searchTime);
9118           return;
9119         }
9120         if (!StrStr(message, "llegal")) {
9121             return;
9122         }
9123         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9124             gameMode == IcsIdle) return;
9125         if (forwardMostMove <= backwardMostMove) return;
9126         if (pausing) PauseEvent();
9127       if(appData.forceIllegal) {
9128             // [HGM] illegal: machine refused move; force position after move into it
9129           SendToProgram("force\n", cps);
9130           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9131                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9132                 // when black is to move, while there might be nothing on a2 or black
9133                 // might already have the move. So send the board as if white has the move.
9134                 // But first we must change the stm of the engine, as it refused the last move
9135                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9136                 if(WhiteOnMove(forwardMostMove)) {
9137                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9138                     SendBoard(cps, forwardMostMove); // kludgeless board
9139                 } else {
9140                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9141                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9142                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9143                 }
9144           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9145             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9146                  gameMode == TwoMachinesPlay)
9147               SendToProgram("go\n", cps);
9148             return;
9149       } else
9150         if (gameMode == PlayFromGameFile) {
9151             /* Stop reading this game file */
9152             gameMode = EditGame;
9153             ModeHighlight();
9154         }
9155         /* [HGM] illegal-move claim should forfeit game when Xboard */
9156         /* only passes fully legal moves                            */
9157         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9158             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9159                                 "False illegal-move claim", GE_XBOARD );
9160             return; // do not take back move we tested as valid
9161         }
9162         currentMove = forwardMostMove-1;
9163         DisplayMove(currentMove-1); /* before DisplayMoveError */
9164         SwitchClocks(forwardMostMove-1); // [HGM] race
9165         DisplayBothClocks();
9166         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9167                 parseList[currentMove], _(cps->which));
9168         DisplayMoveError(buf1);
9169         DrawPosition(FALSE, boards[currentMove]);
9170
9171         SetUserThinkingEnables();
9172         return;
9173     }
9174     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9175         /* Program has a broken "time" command that
9176            outputs a string not ending in newline.
9177            Don't use it. */
9178         cps->sendTime = 0;
9179     }
9180     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9181         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9182             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9183     }
9184
9185     /*
9186      * If chess program startup fails, exit with an error message.
9187      * Attempts to recover here are futile. [HGM] Well, we try anyway
9188      */
9189     if ((StrStr(message, "unknown host") != NULL)
9190         || (StrStr(message, "No remote directory") != NULL)
9191         || (StrStr(message, "not found") != NULL)
9192         || (StrStr(message, "No such file") != NULL)
9193         || (StrStr(message, "can't alloc") != NULL)
9194         || (StrStr(message, "Permission denied") != NULL)) {
9195
9196         cps->maybeThinking = FALSE;
9197         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9198                 _(cps->which), cps->program, cps->host, message);
9199         RemoveInputSource(cps->isr);
9200         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9201             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9202             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9203         }
9204         return;
9205     }
9206
9207     /*
9208      * Look for hint output
9209      */
9210     if (sscanf(message, "Hint: %s", buf1) == 1) {
9211         if (cps == &first && hintRequested) {
9212             hintRequested = FALSE;
9213             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9214                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9215                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9216                                     PosFlags(forwardMostMove),
9217                                     fromY, fromX, toY, toX, promoChar, buf1);
9218                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9219                 DisplayInformation(buf2);
9220             } else {
9221                 /* Hint move could not be parsed!? */
9222               snprintf(buf2, sizeof(buf2),
9223                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9224                         buf1, _(cps->which));
9225                 DisplayError(buf2, 0);
9226             }
9227         } else {
9228           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9229         }
9230         return;
9231     }
9232
9233     /*
9234      * Ignore other messages if game is not in progress
9235      */
9236     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9237         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9238
9239     /*
9240      * look for win, lose, draw, or draw offer
9241      */
9242     if (strncmp(message, "1-0", 3) == 0) {
9243         char *p, *q, *r = "";
9244         p = strchr(message, '{');
9245         if (p) {
9246             q = strchr(p, '}');
9247             if (q) {
9248                 *q = NULLCHAR;
9249                 r = p + 1;
9250             }
9251         }
9252         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9253         return;
9254     } else if (strncmp(message, "0-1", 3) == 0) {
9255         char *p, *q, *r = "";
9256         p = strchr(message, '{');
9257         if (p) {
9258             q = strchr(p, '}');
9259             if (q) {
9260                 *q = NULLCHAR;
9261                 r = p + 1;
9262             }
9263         }
9264         /* Kludge for Arasan 4.1 bug */
9265         if (strcmp(r, "Black resigns") == 0) {
9266             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9267             return;
9268         }
9269         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9270         return;
9271     } else if (strncmp(message, "1/2", 3) == 0) {
9272         char *p, *q, *r = "";
9273         p = strchr(message, '{');
9274         if (p) {
9275             q = strchr(p, '}');
9276             if (q) {
9277                 *q = NULLCHAR;
9278                 r = p + 1;
9279             }
9280         }
9281
9282         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9283         return;
9284
9285     } else if (strncmp(message, "White resign", 12) == 0) {
9286         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9287         return;
9288     } else if (strncmp(message, "Black resign", 12) == 0) {
9289         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9290         return;
9291     } else if (strncmp(message, "White matches", 13) == 0 ||
9292                strncmp(message, "Black matches", 13) == 0   ) {
9293         /* [HGM] ignore GNUShogi noises */
9294         return;
9295     } else if (strncmp(message, "White", 5) == 0 &&
9296                message[5] != '(' &&
9297                StrStr(message, "Black") == NULL) {
9298         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9299         return;
9300     } else if (strncmp(message, "Black", 5) == 0 &&
9301                message[5] != '(') {
9302         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9303         return;
9304     } else if (strcmp(message, "resign") == 0 ||
9305                strcmp(message, "computer resigns") == 0) {
9306         switch (gameMode) {
9307           case MachinePlaysBlack:
9308           case IcsPlayingBlack:
9309             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9310             break;
9311           case MachinePlaysWhite:
9312           case IcsPlayingWhite:
9313             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9314             break;
9315           case TwoMachinesPlay:
9316             if (cps->twoMachinesColor[0] == 'w')
9317               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9318             else
9319               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9320             break;
9321           default:
9322             /* can't happen */
9323             break;
9324         }
9325         return;
9326     } else if (strncmp(message, "opponent mates", 14) == 0) {
9327         switch (gameMode) {
9328           case MachinePlaysBlack:
9329           case IcsPlayingBlack:
9330             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9331             break;
9332           case MachinePlaysWhite:
9333           case IcsPlayingWhite:
9334             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9335             break;
9336           case TwoMachinesPlay:
9337             if (cps->twoMachinesColor[0] == 'w')
9338               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9339             else
9340               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9341             break;
9342           default:
9343             /* can't happen */
9344             break;
9345         }
9346         return;
9347     } else if (strncmp(message, "computer mates", 14) == 0) {
9348         switch (gameMode) {
9349           case MachinePlaysBlack:
9350           case IcsPlayingBlack:
9351             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9352             break;
9353           case MachinePlaysWhite:
9354           case IcsPlayingWhite:
9355             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9356             break;
9357           case TwoMachinesPlay:
9358             if (cps->twoMachinesColor[0] == 'w')
9359               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9360             else
9361               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9362             break;
9363           default:
9364             /* can't happen */
9365             break;
9366         }
9367         return;
9368     } else if (strncmp(message, "checkmate", 9) == 0) {
9369         if (WhiteOnMove(forwardMostMove)) {
9370             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9371         } else {
9372             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9373         }
9374         return;
9375     } else if (strstr(message, "Draw") != NULL ||
9376                strstr(message, "game is a draw") != NULL) {
9377         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9378         return;
9379     } else if (strstr(message, "offer") != NULL &&
9380                strstr(message, "draw") != NULL) {
9381 #if ZIPPY
9382         if (appData.zippyPlay && first.initDone) {
9383             /* Relay offer to ICS */
9384             SendToICS(ics_prefix);
9385             SendToICS("draw\n");
9386         }
9387 #endif
9388         cps->offeredDraw = 2; /* valid until this engine moves twice */
9389         if (gameMode == TwoMachinesPlay) {
9390             if (cps->other->offeredDraw) {
9391                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9392             /* [HGM] in two-machine mode we delay relaying draw offer      */
9393             /* until after we also have move, to see if it is really claim */
9394             }
9395         } else if (gameMode == MachinePlaysWhite ||
9396                    gameMode == MachinePlaysBlack) {
9397           if (userOfferedDraw) {
9398             DisplayInformation(_("Machine accepts your draw offer"));
9399             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9400           } else {
9401             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9402           }
9403         }
9404     }
9405
9406
9407     /*
9408      * Look for thinking output
9409      */
9410     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9411           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9412                                 ) {
9413         int plylev, mvleft, mvtot, curscore, time;
9414         char mvname[MOVE_LEN];
9415         u64 nodes; // [DM]
9416         char plyext;
9417         int ignore = FALSE;
9418         int prefixHint = FALSE;
9419         mvname[0] = NULLCHAR;
9420
9421         switch (gameMode) {
9422           case MachinePlaysBlack:
9423           case IcsPlayingBlack:
9424             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9425             break;
9426           case MachinePlaysWhite:
9427           case IcsPlayingWhite:
9428             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9429             break;
9430           case AnalyzeMode:
9431           case AnalyzeFile:
9432             break;
9433           case IcsObserving: /* [DM] icsEngineAnalyze */
9434             if (!appData.icsEngineAnalyze) ignore = TRUE;
9435             break;
9436           case TwoMachinesPlay:
9437             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9438                 ignore = TRUE;
9439             }
9440             break;
9441           default:
9442             ignore = TRUE;
9443             break;
9444         }
9445
9446         if (!ignore) {
9447             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9448             buf1[0] = NULLCHAR;
9449             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9450                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9451
9452                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9453                     nodes += u64Const(0x100000000);
9454
9455                 if (plyext != ' ' && plyext != '\t') {
9456                     time *= 100;
9457                 }
9458
9459                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9460                 if( cps->scoreIsAbsolute &&
9461                     ( gameMode == MachinePlaysBlack ||
9462                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9463                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9464                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9465                      !WhiteOnMove(currentMove)
9466                     ) )
9467                 {
9468                     curscore = -curscore;
9469                 }
9470
9471                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9472
9473                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9474                         char buf[MSG_SIZ];
9475                         FILE *f;
9476                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9477                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9478                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9479                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9480                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9481                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9482                                 fclose(f);
9483                         }
9484                         else
9485                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9486                           DisplayError(_("failed writing PV"), 0);
9487                 }
9488
9489                 tempStats.depth = plylev;
9490                 tempStats.nodes = nodes;
9491                 tempStats.time = time;
9492                 tempStats.score = curscore;
9493                 tempStats.got_only_move = 0;
9494
9495                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9496                         int ticklen;
9497
9498                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9499                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9500                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9501                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9502                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9503                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9504                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9505                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9506                 }
9507
9508                 /* Buffer overflow protection */
9509                 if (pv[0] != NULLCHAR) {
9510                     if (strlen(pv) >= sizeof(tempStats.movelist)
9511                         && appData.debugMode) {
9512                         fprintf(debugFP,
9513                                 "PV is too long; using the first %u bytes.\n",
9514                                 (unsigned) sizeof(tempStats.movelist) - 1);
9515                     }
9516
9517                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9518                 } else {
9519                     sprintf(tempStats.movelist, " no PV\n");
9520                 }
9521
9522                 if (tempStats.seen_stat) {
9523                     tempStats.ok_to_send = 1;
9524                 }
9525
9526                 if (strchr(tempStats.movelist, '(') != NULL) {
9527                     tempStats.line_is_book = 1;
9528                     tempStats.nr_moves = 0;
9529                     tempStats.moves_left = 0;
9530                 } else {
9531                     tempStats.line_is_book = 0;
9532                 }
9533
9534                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9535                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9536
9537                 SendProgramStatsToFrontend( cps, &tempStats );
9538
9539                 /*
9540                     [AS] Protect the thinkOutput buffer from overflow... this
9541                     is only useful if buf1 hasn't overflowed first!
9542                 */
9543                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9544                          plylev,
9545                          (gameMode == TwoMachinesPlay ?
9546                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9547                          ((double) curscore) / 100.0,
9548                          prefixHint ? lastHint : "",
9549                          prefixHint ? " " : "" );
9550
9551                 if( buf1[0] != NULLCHAR ) {
9552                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9553
9554                     if( strlen(pv) > max_len ) {
9555                         if( appData.debugMode) {
9556                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9557                         }
9558                         pv[max_len+1] = '\0';
9559                     }
9560
9561                     strcat( thinkOutput, pv);
9562                 }
9563
9564                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9565                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9566                     DisplayMove(currentMove - 1);
9567                 }
9568                 return;
9569
9570             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9571                 /* crafty (9.25+) says "(only move) <move>"
9572                  * if there is only 1 legal move
9573                  */
9574                 sscanf(p, "(only move) %s", buf1);
9575                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9576                 sprintf(programStats.movelist, "%s (only move)", buf1);
9577                 programStats.depth = 1;
9578                 programStats.nr_moves = 1;
9579                 programStats.moves_left = 1;
9580                 programStats.nodes = 1;
9581                 programStats.time = 1;
9582                 programStats.got_only_move = 1;
9583
9584                 /* Not really, but we also use this member to
9585                    mean "line isn't going to change" (Crafty
9586                    isn't searching, so stats won't change) */
9587                 programStats.line_is_book = 1;
9588
9589                 SendProgramStatsToFrontend( cps, &programStats );
9590
9591                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9592                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9593                     DisplayMove(currentMove - 1);
9594                 }
9595                 return;
9596             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9597                               &time, &nodes, &plylev, &mvleft,
9598                               &mvtot, mvname) >= 5) {
9599                 /* The stat01: line is from Crafty (9.29+) in response
9600                    to the "." command */
9601                 programStats.seen_stat = 1;
9602                 cps->maybeThinking = TRUE;
9603
9604                 if (programStats.got_only_move || !appData.periodicUpdates)
9605                   return;
9606
9607                 programStats.depth = plylev;
9608                 programStats.time = time;
9609                 programStats.nodes = nodes;
9610                 programStats.moves_left = mvleft;
9611                 programStats.nr_moves = mvtot;
9612                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9613                 programStats.ok_to_send = 1;
9614                 programStats.movelist[0] = '\0';
9615
9616                 SendProgramStatsToFrontend( cps, &programStats );
9617
9618                 return;
9619
9620             } else if (strncmp(message,"++",2) == 0) {
9621                 /* Crafty 9.29+ outputs this */
9622                 programStats.got_fail = 2;
9623                 return;
9624
9625             } else if (strncmp(message,"--",2) == 0) {
9626                 /* Crafty 9.29+ outputs this */
9627                 programStats.got_fail = 1;
9628                 return;
9629
9630             } else if (thinkOutput[0] != NULLCHAR &&
9631                        strncmp(message, "    ", 4) == 0) {
9632                 unsigned message_len;
9633
9634                 p = message;
9635                 while (*p && *p == ' ') p++;
9636
9637                 message_len = strlen( p );
9638
9639                 /* [AS] Avoid buffer overflow */
9640                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9641                     strcat(thinkOutput, " ");
9642                     strcat(thinkOutput, p);
9643                 }
9644
9645                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9646                     strcat(programStats.movelist, " ");
9647                     strcat(programStats.movelist, p);
9648                 }
9649
9650                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9651                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9652                     DisplayMove(currentMove - 1);
9653                 }
9654                 return;
9655             }
9656         }
9657         else {
9658             buf1[0] = NULLCHAR;
9659
9660             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9661                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9662             {
9663                 ChessProgramStats cpstats;
9664
9665                 if (plyext != ' ' && plyext != '\t') {
9666                     time *= 100;
9667                 }
9668
9669                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9670                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9671                     curscore = -curscore;
9672                 }
9673
9674                 cpstats.depth = plylev;
9675                 cpstats.nodes = nodes;
9676                 cpstats.time = time;
9677                 cpstats.score = curscore;
9678                 cpstats.got_only_move = 0;
9679                 cpstats.movelist[0] = '\0';
9680
9681                 if (buf1[0] != NULLCHAR) {
9682                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9683                 }
9684
9685                 cpstats.ok_to_send = 0;
9686                 cpstats.line_is_book = 0;
9687                 cpstats.nr_moves = 0;
9688                 cpstats.moves_left = 0;
9689
9690                 SendProgramStatsToFrontend( cps, &cpstats );
9691             }
9692         }
9693     }
9694 }
9695
9696
9697 /* Parse a game score from the character string "game", and
9698    record it as the history of the current game.  The game
9699    score is NOT assumed to start from the standard position.
9700    The display is not updated in any way.
9701    */
9702 void
9703 ParseGameHistory (char *game)
9704 {
9705     ChessMove moveType;
9706     int fromX, fromY, toX, toY, boardIndex;
9707     char promoChar;
9708     char *p, *q;
9709     char buf[MSG_SIZ];
9710
9711     if (appData.debugMode)
9712       fprintf(debugFP, "Parsing game history: %s\n", game);
9713
9714     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9715     gameInfo.site = StrSave(appData.icsHost);
9716     gameInfo.date = PGNDate();
9717     gameInfo.round = StrSave("-");
9718
9719     /* Parse out names of players */
9720     while (*game == ' ') game++;
9721     p = buf;
9722     while (*game != ' ') *p++ = *game++;
9723     *p = NULLCHAR;
9724     gameInfo.white = StrSave(buf);
9725     while (*game == ' ') game++;
9726     p = buf;
9727     while (*game != ' ' && *game != '\n') *p++ = *game++;
9728     *p = NULLCHAR;
9729     gameInfo.black = StrSave(buf);
9730
9731     /* Parse moves */
9732     boardIndex = blackPlaysFirst ? 1 : 0;
9733     yynewstr(game);
9734     for (;;) {
9735         yyboardindex = boardIndex;
9736         moveType = (ChessMove) Myylex();
9737         switch (moveType) {
9738           case IllegalMove:             /* maybe suicide chess, etc. */
9739   if (appData.debugMode) {
9740     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9741     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9742     setbuf(debugFP, NULL);
9743   }
9744           case WhitePromotion:
9745           case BlackPromotion:
9746           case WhiteNonPromotion:
9747           case BlackNonPromotion:
9748           case NormalMove:
9749           case FirstLeg:
9750           case WhiteCapturesEnPassant:
9751           case BlackCapturesEnPassant:
9752           case WhiteKingSideCastle:
9753           case WhiteQueenSideCastle:
9754           case BlackKingSideCastle:
9755           case BlackQueenSideCastle:
9756           case WhiteKingSideCastleWild:
9757           case WhiteQueenSideCastleWild:
9758           case BlackKingSideCastleWild:
9759           case BlackQueenSideCastleWild:
9760           /* PUSH Fabien */
9761           case WhiteHSideCastleFR:
9762           case WhiteASideCastleFR:
9763           case BlackHSideCastleFR:
9764           case BlackASideCastleFR:
9765           /* POP Fabien */
9766             fromX = currentMoveString[0] - AAA;
9767             fromY = currentMoveString[1] - ONE;
9768             toX = currentMoveString[2] - AAA;
9769             toY = currentMoveString[3] - ONE;
9770             promoChar = currentMoveString[4];
9771             break;
9772           case WhiteDrop:
9773           case BlackDrop:
9774             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9775             fromX = moveType == WhiteDrop ?
9776               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9777             (int) CharToPiece(ToLower(currentMoveString[0]));
9778             fromY = DROP_RANK;
9779             toX = currentMoveString[2] - AAA;
9780             toY = currentMoveString[3] - ONE;
9781             promoChar = NULLCHAR;
9782             break;
9783           case AmbiguousMove:
9784             /* bug? */
9785             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9786   if (appData.debugMode) {
9787     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9788     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9789     setbuf(debugFP, NULL);
9790   }
9791             DisplayError(buf, 0);
9792             return;
9793           case ImpossibleMove:
9794             /* bug? */
9795             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9796   if (appData.debugMode) {
9797     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9798     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9799     setbuf(debugFP, NULL);
9800   }
9801             DisplayError(buf, 0);
9802             return;
9803           case EndOfFile:
9804             if (boardIndex < backwardMostMove) {
9805                 /* Oops, gap.  How did that happen? */
9806                 DisplayError(_("Gap in move list"), 0);
9807                 return;
9808             }
9809             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9810             if (boardIndex > forwardMostMove) {
9811                 forwardMostMove = boardIndex;
9812             }
9813             return;
9814           case ElapsedTime:
9815             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9816                 strcat(parseList[boardIndex-1], " ");
9817                 strcat(parseList[boardIndex-1], yy_text);
9818             }
9819             continue;
9820           case Comment:
9821           case PGNTag:
9822           case NAG:
9823           default:
9824             /* ignore */
9825             continue;
9826           case WhiteWins:
9827           case BlackWins:
9828           case GameIsDrawn:
9829           case GameUnfinished:
9830             if (gameMode == IcsExamining) {
9831                 if (boardIndex < backwardMostMove) {
9832                     /* Oops, gap.  How did that happen? */
9833                     return;
9834                 }
9835                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9836                 return;
9837             }
9838             gameInfo.result = moveType;
9839             p = strchr(yy_text, '{');
9840             if (p == NULL) p = strchr(yy_text, '(');
9841             if (p == NULL) {
9842                 p = yy_text;
9843                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9844             } else {
9845                 q = strchr(p, *p == '{' ? '}' : ')');
9846                 if (q != NULL) *q = NULLCHAR;
9847                 p++;
9848             }
9849             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9850             gameInfo.resultDetails = StrSave(p);
9851             continue;
9852         }
9853         if (boardIndex >= forwardMostMove &&
9854             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9855             backwardMostMove = blackPlaysFirst ? 1 : 0;
9856             return;
9857         }
9858         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9859                                  fromY, fromX, toY, toX, promoChar,
9860                                  parseList[boardIndex]);
9861         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9862         /* currentMoveString is set as a side-effect of yylex */
9863         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9864         strcat(moveList[boardIndex], "\n");
9865         boardIndex++;
9866         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9867         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9868           case MT_NONE:
9869           case MT_STALEMATE:
9870           default:
9871             break;
9872           case MT_CHECK:
9873             if(!IS_SHOGI(gameInfo.variant))
9874                 strcat(parseList[boardIndex - 1], "+");
9875             break;
9876           case MT_CHECKMATE:
9877           case MT_STAINMATE:
9878             strcat(parseList[boardIndex - 1], "#");
9879             break;
9880         }
9881     }
9882 }
9883
9884
9885 /* Apply a move to the given board  */
9886 void
9887 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9888 {
9889   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9890   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9891
9892     /* [HGM] compute & store e.p. status and castling rights for new position */
9893     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9894
9895       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9896       oldEP = (signed char)board[EP_STATUS];
9897       board[EP_STATUS] = EP_NONE;
9898       board[EP_FILE] = board[EP_RANK] = 100;
9899
9900   if (fromY == DROP_RANK) {
9901         /* must be first */
9902         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9903             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9904             return;
9905         }
9906         piece = board[toY][toX] = (ChessSquare) fromX;
9907   } else {
9908 //      ChessSquare victim;
9909       int i;
9910
9911       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9912 //           victim = board[killY][killX],
9913            board[killY][killX] = EmptySquare,
9914            board[EP_STATUS] = EP_CAPTURE;
9915
9916       if( board[toY][toX] != EmptySquare ) {
9917            board[EP_STATUS] = EP_CAPTURE;
9918            if( (fromX != toX || fromY != toY) && // not igui!
9919                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9920                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9921                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9922            }
9923       }
9924
9925       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9926            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9927                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9928       } else
9929       if( board[fromY][fromX] == WhitePawn ) {
9930            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9931                board[EP_STATUS] = EP_PAWN_MOVE;
9932            if( toY-fromY==2) {
9933                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
9934                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9935                         gameInfo.variant != VariantBerolina || toX < fromX)
9936                       board[EP_STATUS] = toX | berolina;
9937                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9938                         gameInfo.variant != VariantBerolina || toX > fromX)
9939                       board[EP_STATUS] = toX;
9940            }
9941       } else
9942       if( board[fromY][fromX] == BlackPawn ) {
9943            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9944                board[EP_STATUS] = EP_PAWN_MOVE;
9945            if( toY-fromY== -2) {
9946                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
9947                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9948                         gameInfo.variant != VariantBerolina || toX < fromX)
9949                       board[EP_STATUS] = toX | berolina;
9950                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9951                         gameInfo.variant != VariantBerolina || toX > fromX)
9952                       board[EP_STATUS] = toX;
9953            }
9954        }
9955
9956        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9957        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9958        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9959        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9960
9961        for(i=0; i<nrCastlingRights; i++) {
9962            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9963               board[CASTLING][i] == toX   && castlingRank[i] == toY
9964              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9965        }
9966
9967        if(gameInfo.variant == VariantSChess) { // update virginity
9968            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9969            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9970            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9971            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9972        }
9973
9974      if (fromX == toX && fromY == toY) return;
9975
9976      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9977      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9978      if(gameInfo.variant == VariantKnightmate)
9979          king += (int) WhiteUnicorn - (int) WhiteKing;
9980
9981     /* Code added by Tord: */
9982     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9983     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9984         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9985       board[EP_STATUS] = EP_NONE; // capture was fake!
9986       board[fromY][fromX] = EmptySquare;
9987       board[toY][toX] = EmptySquare;
9988       if((toX > fromX) != (piece == WhiteRook)) {
9989         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9990       } else {
9991         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9992       }
9993     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9994                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9995       board[EP_STATUS] = EP_NONE;
9996       board[fromY][fromX] = EmptySquare;
9997       board[toY][toX] = EmptySquare;
9998       if((toX > fromX) != (piece == BlackRook)) {
9999         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10000       } else {
10001         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10002       }
10003     /* End of code added by Tord */
10004
10005     } else if (board[fromY][fromX] == king
10006         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10007         && toY == fromY && toX > fromX+1) {
10008         board[fromY][fromX] = EmptySquare;
10009         board[toY][toX] = king;
10010         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10011         board[fromY][BOARD_RGHT-1] = EmptySquare;
10012     } else if (board[fromY][fromX] == king
10013         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10014                && toY == fromY && toX < fromX-1) {
10015         board[fromY][fromX] = EmptySquare;
10016         board[toY][toX] = king;
10017         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10018         board[fromY][BOARD_LEFT] = EmptySquare;
10019     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10020                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10021                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10022                ) {
10023         /* white pawn promotion */
10024         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10025         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10026             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10027         board[fromY][fromX] = EmptySquare;
10028     } else if ((fromY >= BOARD_HEIGHT>>1)
10029                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10030                && (toX != fromX)
10031                && gameInfo.variant != VariantXiangqi
10032                && gameInfo.variant != VariantBerolina
10033                && (board[fromY][fromX] == WhitePawn)
10034                && (board[toY][toX] == EmptySquare)) {
10035         board[fromY][fromX] = EmptySquare;
10036         board[toY][toX] = WhitePawn;
10037         captured = board[toY - 1][toX];
10038         board[toY - 1][toX] = EmptySquare;
10039     } else if ((fromY == BOARD_HEIGHT-4)
10040                && (toX == fromX)
10041                && gameInfo.variant == VariantBerolina
10042                && (board[fromY][fromX] == WhitePawn)
10043                && (board[toY][toX] == EmptySquare)) {
10044         board[fromY][fromX] = EmptySquare;
10045         board[toY][toX] = WhitePawn;
10046         if(oldEP & EP_BEROLIN_A) {
10047                 captured = board[fromY][fromX-1];
10048                 board[fromY][fromX-1] = EmptySquare;
10049         }else{  captured = board[fromY][fromX+1];
10050                 board[fromY][fromX+1] = EmptySquare;
10051         }
10052     } else if (board[fromY][fromX] == king
10053         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10054                && toY == fromY && toX > fromX+1) {
10055         board[fromY][fromX] = EmptySquare;
10056         board[toY][toX] = king;
10057         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10058         board[fromY][BOARD_RGHT-1] = EmptySquare;
10059     } else if (board[fromY][fromX] == king
10060         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10061                && toY == fromY && toX < fromX-1) {
10062         board[fromY][fromX] = EmptySquare;
10063         board[toY][toX] = king;
10064         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10065         board[fromY][BOARD_LEFT] = EmptySquare;
10066     } else if (fromY == 7 && fromX == 3
10067                && board[fromY][fromX] == BlackKing
10068                && toY == 7 && toX == 5) {
10069         board[fromY][fromX] = EmptySquare;
10070         board[toY][toX] = BlackKing;
10071         board[fromY][7] = EmptySquare;
10072         board[toY][4] = BlackRook;
10073     } else if (fromY == 7 && fromX == 3
10074                && board[fromY][fromX] == BlackKing
10075                && toY == 7 && toX == 1) {
10076         board[fromY][fromX] = EmptySquare;
10077         board[toY][toX] = BlackKing;
10078         board[fromY][0] = EmptySquare;
10079         board[toY][2] = BlackRook;
10080     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10081                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10082                && toY < promoRank && promoChar
10083                ) {
10084         /* black pawn promotion */
10085         board[toY][toX] = CharToPiece(ToLower(promoChar));
10086         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10087             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10088         board[fromY][fromX] = EmptySquare;
10089     } else if ((fromY < BOARD_HEIGHT>>1)
10090                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10091                && (toX != fromX)
10092                && gameInfo.variant != VariantXiangqi
10093                && gameInfo.variant != VariantBerolina
10094                && (board[fromY][fromX] == BlackPawn)
10095                && (board[toY][toX] == EmptySquare)) {
10096         board[fromY][fromX] = EmptySquare;
10097         board[toY][toX] = BlackPawn;
10098         captured = board[toY + 1][toX];
10099         board[toY + 1][toX] = EmptySquare;
10100     } else if ((fromY == 3)
10101                && (toX == fromX)
10102                && gameInfo.variant == VariantBerolina
10103                && (board[fromY][fromX] == BlackPawn)
10104                && (board[toY][toX] == EmptySquare)) {
10105         board[fromY][fromX] = EmptySquare;
10106         board[toY][toX] = BlackPawn;
10107         if(oldEP & EP_BEROLIN_A) {
10108                 captured = board[fromY][fromX-1];
10109                 board[fromY][fromX-1] = EmptySquare;
10110         }else{  captured = board[fromY][fromX+1];
10111                 board[fromY][fromX+1] = EmptySquare;
10112         }
10113     } else {
10114         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10115         board[fromY][fromX] = EmptySquare;
10116         board[toY][toX] = piece;
10117     }
10118   }
10119
10120     if (gameInfo.holdingsWidth != 0) {
10121
10122       /* !!A lot more code needs to be written to support holdings  */
10123       /* [HGM] OK, so I have written it. Holdings are stored in the */
10124       /* penultimate board files, so they are automaticlly stored   */
10125       /* in the game history.                                       */
10126       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10127                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10128         /* Delete from holdings, by decreasing count */
10129         /* and erasing image if necessary            */
10130         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10131         if(p < (int) BlackPawn) { /* white drop */
10132              p -= (int)WhitePawn;
10133                  p = PieceToNumber((ChessSquare)p);
10134              if(p >= gameInfo.holdingsSize) p = 0;
10135              if(--board[p][BOARD_WIDTH-2] <= 0)
10136                   board[p][BOARD_WIDTH-1] = EmptySquare;
10137              if((int)board[p][BOARD_WIDTH-2] < 0)
10138                         board[p][BOARD_WIDTH-2] = 0;
10139         } else {                  /* black drop */
10140              p -= (int)BlackPawn;
10141                  p = PieceToNumber((ChessSquare)p);
10142              if(p >= gameInfo.holdingsSize) p = 0;
10143              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10144                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10145              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10146                         board[BOARD_HEIGHT-1-p][1] = 0;
10147         }
10148       }
10149       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10150           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10151         /* [HGM] holdings: Add to holdings, if holdings exist */
10152         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10153                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10154                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10155         }
10156         p = (int) captured;
10157         if (p >= (int) BlackPawn) {
10158           p -= (int)BlackPawn;
10159           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10160                   /* Restore shogi-promoted piece to its original  first */
10161                   captured = (ChessSquare) (DEMOTED captured);
10162                   p = DEMOTED p;
10163           }
10164           p = PieceToNumber((ChessSquare)p);
10165           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10166           board[p][BOARD_WIDTH-2]++;
10167           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10168         } else {
10169           p -= (int)WhitePawn;
10170           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10171                   captured = (ChessSquare) (DEMOTED captured);
10172                   p = DEMOTED p;
10173           }
10174           p = PieceToNumber((ChessSquare)p);
10175           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10176           board[BOARD_HEIGHT-1-p][1]++;
10177           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10178         }
10179       }
10180     } else if (gameInfo.variant == VariantAtomic) {
10181       if (captured != EmptySquare) {
10182         int y, x;
10183         for (y = toY-1; y <= toY+1; y++) {
10184           for (x = toX-1; x <= toX+1; x++) {
10185             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10186                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10187               board[y][x] = EmptySquare;
10188             }
10189           }
10190         }
10191         board[toY][toX] = EmptySquare;
10192       }
10193     }
10194
10195     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10196         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10197     } else
10198     if(promoChar == '+') {
10199         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10200         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10201         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10202           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10203     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10204         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10205         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10206            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10207         board[toY][toX] = newPiece;
10208     }
10209     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10210                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10211         // [HGM] superchess: take promotion piece out of holdings
10212         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10213         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10214             if(!--board[k][BOARD_WIDTH-2])
10215                 board[k][BOARD_WIDTH-1] = EmptySquare;
10216         } else {
10217             if(!--board[BOARD_HEIGHT-1-k][1])
10218                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10219         }
10220     }
10221 }
10222
10223 /* Updates forwardMostMove */
10224 void
10225 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10226 {
10227     int x = toX, y = toY;
10228     char *s = parseList[forwardMostMove];
10229     ChessSquare p = boards[forwardMostMove][toY][toX];
10230 //    forwardMostMove++; // [HGM] bare: moved downstream
10231
10232     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10233     (void) CoordsToAlgebraic(boards[forwardMostMove],
10234                              PosFlags(forwardMostMove),
10235                              fromY, fromX, y, x, promoChar,
10236                              s);
10237     if(killX >= 0 && killY >= 0)
10238         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10239
10240     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10241         int timeLeft; static int lastLoadFlag=0; int king, piece;
10242         piece = boards[forwardMostMove][fromY][fromX];
10243         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10244         if(gameInfo.variant == VariantKnightmate)
10245             king += (int) WhiteUnicorn - (int) WhiteKing;
10246         if(forwardMostMove == 0) {
10247             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10248                 fprintf(serverMoves, "%s;", UserName());
10249             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10250                 fprintf(serverMoves, "%s;", second.tidy);
10251             fprintf(serverMoves, "%s;", first.tidy);
10252             if(gameMode == MachinePlaysWhite)
10253                 fprintf(serverMoves, "%s;", UserName());
10254             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10255                 fprintf(serverMoves, "%s;", second.tidy);
10256         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10257         lastLoadFlag = loadFlag;
10258         // print base move
10259         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10260         // print castling suffix
10261         if( toY == fromY && piece == king ) {
10262             if(toX-fromX > 1)
10263                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10264             if(fromX-toX >1)
10265                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10266         }
10267         // e.p. suffix
10268         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10269              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10270              boards[forwardMostMove][toY][toX] == EmptySquare
10271              && fromX != toX && fromY != toY)
10272                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10273         // promotion suffix
10274         if(promoChar != NULLCHAR) {
10275             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10276                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10277                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10278             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10279         }
10280         if(!loadFlag) {
10281                 char buf[MOVE_LEN*2], *p; int len;
10282             fprintf(serverMoves, "/%d/%d",
10283                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10284             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10285             else                      timeLeft = blackTimeRemaining/1000;
10286             fprintf(serverMoves, "/%d", timeLeft);
10287                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10288                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10289                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10290                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10291             fprintf(serverMoves, "/%s", buf);
10292         }
10293         fflush(serverMoves);
10294     }
10295
10296     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10297         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10298       return;
10299     }
10300     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10301     if (commentList[forwardMostMove+1] != NULL) {
10302         free(commentList[forwardMostMove+1]);
10303         commentList[forwardMostMove+1] = NULL;
10304     }
10305     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10306     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10307     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10308     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10309     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10310     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10311     adjustedClock = FALSE;
10312     gameInfo.result = GameUnfinished;
10313     if (gameInfo.resultDetails != NULL) {
10314         free(gameInfo.resultDetails);
10315         gameInfo.resultDetails = NULL;
10316     }
10317     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10318                               moveList[forwardMostMove - 1]);
10319     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10320       case MT_NONE:
10321       case MT_STALEMATE:
10322       default:
10323         break;
10324       case MT_CHECK:
10325         if(!IS_SHOGI(gameInfo.variant))
10326             strcat(parseList[forwardMostMove - 1], "+");
10327         break;
10328       case MT_CHECKMATE:
10329       case MT_STAINMATE:
10330         strcat(parseList[forwardMostMove - 1], "#");
10331         break;
10332     }
10333 }
10334
10335 /* Updates currentMove if not pausing */
10336 void
10337 ShowMove (int fromX, int fromY, int toX, int toY)
10338 {
10339     int instant = (gameMode == PlayFromGameFile) ?
10340         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10341     if(appData.noGUI) return;
10342     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10343         if (!instant) {
10344             if (forwardMostMove == currentMove + 1) {
10345                 AnimateMove(boards[forwardMostMove - 1],
10346                             fromX, fromY, toX, toY);
10347             }
10348         }
10349         currentMove = forwardMostMove;
10350     }
10351
10352     killX = killY = -1; // [HGM] lion: used up
10353
10354     if (instant) return;
10355
10356     DisplayMove(currentMove - 1);
10357     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10358             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10359                 SetHighlights(fromX, fromY, toX, toY);
10360             }
10361     }
10362     DrawPosition(FALSE, boards[currentMove]);
10363     DisplayBothClocks();
10364     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10365 }
10366
10367 void
10368 SendEgtPath (ChessProgramState *cps)
10369 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10370         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10371
10372         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10373
10374         while(*p) {
10375             char c, *q = name+1, *r, *s;
10376
10377             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10378             while(*p && *p != ',') *q++ = *p++;
10379             *q++ = ':'; *q = 0;
10380             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10381                 strcmp(name, ",nalimov:") == 0 ) {
10382                 // take nalimov path from the menu-changeable option first, if it is defined
10383               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10384                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10385             } else
10386             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10387                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10388                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10389                 s = r = StrStr(s, ":") + 1; // beginning of path info
10390                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10391                 c = *r; *r = 0;             // temporarily null-terminate path info
10392                     *--q = 0;               // strip of trailig ':' from name
10393                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10394                 *r = c;
10395                 SendToProgram(buf,cps);     // send egtbpath command for this format
10396             }
10397             if(*p == ',') p++; // read away comma to position for next format name
10398         }
10399 }
10400
10401 static int
10402 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10403 {
10404       int width = 8, height = 8, holdings = 0;             // most common sizes
10405       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10406       // correct the deviations default for each variant
10407       if( v == VariantXiangqi ) width = 9,  height = 10;
10408       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10409       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10410       if( v == VariantCapablanca || v == VariantCapaRandom ||
10411           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10412                                 width = 10;
10413       if( v == VariantCourier ) width = 12;
10414       if( v == VariantSuper )                            holdings = 8;
10415       if( v == VariantGreat )   width = 10,              holdings = 8;
10416       if( v == VariantSChess )                           holdings = 7;
10417       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10418       if( v == VariantChuChess) width = 10, height = 10;
10419       if( v == VariantChu )     width = 12, height = 12;
10420       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10421              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10422              holdingsSize >= 0 && holdingsSize != holdings;
10423 }
10424
10425 char variantError[MSG_SIZ];
10426
10427 char *
10428 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10429 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10430       char *p, *variant = VariantName(v);
10431       static char b[MSG_SIZ];
10432       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10433            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10434                                                holdingsSize, variant); // cook up sized variant name
10435            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10436            if(StrStr(list, b) == NULL) {
10437                // specific sized variant not known, check if general sizing allowed
10438                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10439                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10440                             boardWidth, boardHeight, holdingsSize, engine);
10441                    return NULL;
10442                }
10443                /* [HGM] here we really should compare with the maximum supported board size */
10444            }
10445       } else snprintf(b, MSG_SIZ,"%s", variant);
10446       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10447       p = StrStr(list, b);
10448       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10449       if(p == NULL) {
10450           // occurs not at all in list, or only as sub-string
10451           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10452           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10453               int l = strlen(variantError);
10454               char *q;
10455               while(p != list && p[-1] != ',') p--;
10456               q = strchr(p, ',');
10457               if(q) *q = NULLCHAR;
10458               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10459               if(q) *q= ',';
10460           }
10461           return NULL;
10462       }
10463       return b;
10464 }
10465
10466 void
10467 InitChessProgram (ChessProgramState *cps, int setup)
10468 /* setup needed to setup FRC opening position */
10469 {
10470     char buf[MSG_SIZ], *b;
10471     if (appData.noChessProgram) return;
10472     hintRequested = FALSE;
10473     bookRequested = FALSE;
10474
10475     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10476     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10477     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10478     if(cps->memSize) { /* [HGM] memory */
10479       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10480         SendToProgram(buf, cps);
10481     }
10482     SendEgtPath(cps); /* [HGM] EGT */
10483     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10484       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10485         SendToProgram(buf, cps);
10486     }
10487
10488     setboardSpoiledMachineBlack = FALSE;
10489     SendToProgram(cps->initString, cps);
10490     if (gameInfo.variant != VariantNormal &&
10491         gameInfo.variant != VariantLoadable
10492         /* [HGM] also send variant if board size non-standard */
10493         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10494
10495       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10496                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10497       if (b == NULL) {
10498         VariantClass v;
10499         char c, *q = cps->variants, *p = strchr(q, ',');
10500         if(p) *p = NULLCHAR;
10501         v = StringToVariant(q);
10502         DisplayError(variantError, 0);
10503         if(v != VariantUnknown && cps == &first) {
10504             int w, h, s;
10505             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10506                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10507             ASSIGN(appData.variant, q);
10508             Reset(TRUE, FALSE);
10509         }
10510         if(p) *p = ',';
10511         return;
10512       }
10513
10514       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10515       SendToProgram(buf, cps);
10516     }
10517     currentlyInitializedVariant = gameInfo.variant;
10518
10519     /* [HGM] send opening position in FRC to first engine */
10520     if(setup) {
10521           SendToProgram("force\n", cps);
10522           SendBoard(cps, 0);
10523           /* engine is now in force mode! Set flag to wake it up after first move. */
10524           setboardSpoiledMachineBlack = 1;
10525     }
10526
10527     if (cps->sendICS) {
10528       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10529       SendToProgram(buf, cps);
10530     }
10531     cps->maybeThinking = FALSE;
10532     cps->offeredDraw = 0;
10533     if (!appData.icsActive) {
10534         SendTimeControl(cps, movesPerSession, timeControl,
10535                         timeIncrement, appData.searchDepth,
10536                         searchTime);
10537     }
10538     if (appData.showThinking
10539         // [HGM] thinking: four options require thinking output to be sent
10540         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10541                                 ) {
10542         SendToProgram("post\n", cps);
10543     }
10544     SendToProgram("hard\n", cps);
10545     if (!appData.ponderNextMove) {
10546         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10547            it without being sure what state we are in first.  "hard"
10548            is not a toggle, so that one is OK.
10549          */
10550         SendToProgram("easy\n", cps);
10551     }
10552     if (cps->usePing) {
10553       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10554       SendToProgram(buf, cps);
10555     }
10556     cps->initDone = TRUE;
10557     ClearEngineOutputPane(cps == &second);
10558 }
10559
10560
10561 void
10562 ResendOptions (ChessProgramState *cps)
10563 { // send the stored value of the options
10564   int i;
10565   char buf[MSG_SIZ];
10566   Option *opt = cps->option;
10567   for(i=0; i<cps->nrOptions; i++, opt++) {
10568       switch(opt->type) {
10569         case Spin:
10570         case Slider:
10571         case CheckBox:
10572             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10573           break;
10574         case ComboBox:
10575           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10576           break;
10577         default:
10578             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10579           break;
10580         case Button:
10581         case SaveButton:
10582           continue;
10583       }
10584       SendToProgram(buf, cps);
10585   }
10586 }
10587
10588 void
10589 StartChessProgram (ChessProgramState *cps)
10590 {
10591     char buf[MSG_SIZ];
10592     int err;
10593
10594     if (appData.noChessProgram) return;
10595     cps->initDone = FALSE;
10596
10597     if (strcmp(cps->host, "localhost") == 0) {
10598         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10599     } else if (*appData.remoteShell == NULLCHAR) {
10600         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10601     } else {
10602         if (*appData.remoteUser == NULLCHAR) {
10603           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10604                     cps->program);
10605         } else {
10606           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10607                     cps->host, appData.remoteUser, cps->program);
10608         }
10609         err = StartChildProcess(buf, "", &cps->pr);
10610     }
10611
10612     if (err != 0) {
10613       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10614         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10615         if(cps != &first) return;
10616         appData.noChessProgram = TRUE;
10617         ThawUI();
10618         SetNCPMode();
10619 //      DisplayFatalError(buf, err, 1);
10620 //      cps->pr = NoProc;
10621 //      cps->isr = NULL;
10622         return;
10623     }
10624
10625     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10626     if (cps->protocolVersion > 1) {
10627       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10628       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10629         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10630         cps->comboCnt = 0;  //                and values of combo boxes
10631       }
10632       SendToProgram(buf, cps);
10633       if(cps->reload) ResendOptions(cps);
10634     } else {
10635       SendToProgram("xboard\n", cps);
10636     }
10637 }
10638
10639 void
10640 TwoMachinesEventIfReady P((void))
10641 {
10642   static int curMess = 0;
10643   if (first.lastPing != first.lastPong) {
10644     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10645     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10646     return;
10647   }
10648   if (second.lastPing != second.lastPong) {
10649     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10650     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10651     return;
10652   }
10653   DisplayMessage("", ""); curMess = 0;
10654   TwoMachinesEvent();
10655 }
10656
10657 char *
10658 MakeName (char *template)
10659 {
10660     time_t clock;
10661     struct tm *tm;
10662     static char buf[MSG_SIZ];
10663     char *p = buf;
10664     int i;
10665
10666     clock = time((time_t *)NULL);
10667     tm = localtime(&clock);
10668
10669     while(*p++ = *template++) if(p[-1] == '%') {
10670         switch(*template++) {
10671           case 0:   *p = 0; return buf;
10672           case 'Y': i = tm->tm_year+1900; break;
10673           case 'y': i = tm->tm_year-100; break;
10674           case 'M': i = tm->tm_mon+1; break;
10675           case 'd': i = tm->tm_mday; break;
10676           case 'h': i = tm->tm_hour; break;
10677           case 'm': i = tm->tm_min; break;
10678           case 's': i = tm->tm_sec; break;
10679           default:  i = 0;
10680         }
10681         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10682     }
10683     return buf;
10684 }
10685
10686 int
10687 CountPlayers (char *p)
10688 {
10689     int n = 0;
10690     while(p = strchr(p, '\n')) p++, n++; // count participants
10691     return n;
10692 }
10693
10694 FILE *
10695 WriteTourneyFile (char *results, FILE *f)
10696 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10697     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10698     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10699         // create a file with tournament description
10700         fprintf(f, "-participants {%s}\n", appData.participants);
10701         fprintf(f, "-seedBase %d\n", appData.seedBase);
10702         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10703         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10704         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10705         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10706         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10707         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10708         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10709         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10710         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10711         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10712         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10713         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10714         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10715         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10716         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10717         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10718         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10719         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10720         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10721         fprintf(f, "-smpCores %d\n", appData.smpCores);
10722         if(searchTime > 0)
10723                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10724         else {
10725                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10726                 fprintf(f, "-tc %s\n", appData.timeControl);
10727                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10728         }
10729         fprintf(f, "-results \"%s\"\n", results);
10730     }
10731     return f;
10732 }
10733
10734 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10735
10736 void
10737 Substitute (char *participants, int expunge)
10738 {
10739     int i, changed, changes=0, nPlayers=0;
10740     char *p, *q, *r, buf[MSG_SIZ];
10741     if(participants == NULL) return;
10742     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10743     r = p = participants; q = appData.participants;
10744     while(*p && *p == *q) {
10745         if(*p == '\n') r = p+1, nPlayers++;
10746         p++; q++;
10747     }
10748     if(*p) { // difference
10749         while(*p && *p++ != '\n');
10750         while(*q && *q++ != '\n');
10751       changed = nPlayers;
10752         changes = 1 + (strcmp(p, q) != 0);
10753     }
10754     if(changes == 1) { // a single engine mnemonic was changed
10755         q = r; while(*q) nPlayers += (*q++ == '\n');
10756         p = buf; while(*r && (*p = *r++) != '\n') p++;
10757         *p = NULLCHAR;
10758         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10759         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10760         if(mnemonic[i]) { // The substitute is valid
10761             FILE *f;
10762             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10763                 flock(fileno(f), LOCK_EX);
10764                 ParseArgsFromFile(f);
10765                 fseek(f, 0, SEEK_SET);
10766                 FREE(appData.participants); appData.participants = participants;
10767                 if(expunge) { // erase results of replaced engine
10768                     int len = strlen(appData.results), w, b, dummy;
10769                     for(i=0; i<len; i++) {
10770                         Pairing(i, nPlayers, &w, &b, &dummy);
10771                         if((w == changed || b == changed) && appData.results[i] == '*') {
10772                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10773                             fclose(f);
10774                             return;
10775                         }
10776                     }
10777                     for(i=0; i<len; i++) {
10778                         Pairing(i, nPlayers, &w, &b, &dummy);
10779                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10780                     }
10781                 }
10782                 WriteTourneyFile(appData.results, f);
10783                 fclose(f); // release lock
10784                 return;
10785             }
10786         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10787     }
10788     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10789     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10790     free(participants);
10791     return;
10792 }
10793
10794 int
10795 CheckPlayers (char *participants)
10796 {
10797         int i;
10798         char buf[MSG_SIZ], *p;
10799         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10800         while(p = strchr(participants, '\n')) {
10801             *p = NULLCHAR;
10802             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10803             if(!mnemonic[i]) {
10804                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10805                 *p = '\n';
10806                 DisplayError(buf, 0);
10807                 return 1;
10808             }
10809             *p = '\n';
10810             participants = p + 1;
10811         }
10812         return 0;
10813 }
10814
10815 int
10816 CreateTourney (char *name)
10817 {
10818         FILE *f;
10819         if(matchMode && strcmp(name, appData.tourneyFile)) {
10820              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10821         }
10822         if(name[0] == NULLCHAR) {
10823             if(appData.participants[0])
10824                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10825             return 0;
10826         }
10827         f = fopen(name, "r");
10828         if(f) { // file exists
10829             ASSIGN(appData.tourneyFile, name);
10830             ParseArgsFromFile(f); // parse it
10831         } else {
10832             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10833             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10834                 DisplayError(_("Not enough participants"), 0);
10835                 return 0;
10836             }
10837             if(CheckPlayers(appData.participants)) return 0;
10838             ASSIGN(appData.tourneyFile, name);
10839             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10840             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10841         }
10842         fclose(f);
10843         appData.noChessProgram = FALSE;
10844         appData.clockMode = TRUE;
10845         SetGNUMode();
10846         return 1;
10847 }
10848
10849 int
10850 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10851 {
10852     char buf[MSG_SIZ], *p, *q;
10853     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10854     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10855     skip = !all && group[0]; // if group requested, we start in skip mode
10856     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10857         p = names; q = buf; header = 0;
10858         while(*p && *p != '\n') *q++ = *p++;
10859         *q = 0;
10860         if(*p == '\n') p++;
10861         if(buf[0] == '#') {
10862             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10863             depth++; // we must be entering a new group
10864             if(all) continue; // suppress printing group headers when complete list requested
10865             header = 1;
10866             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10867         }
10868         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10869         if(engineList[i]) free(engineList[i]);
10870         engineList[i] = strdup(buf);
10871         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10872         if(engineMnemonic[i]) free(engineMnemonic[i]);
10873         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10874             strcat(buf, " (");
10875             sscanf(q + 8, "%s", buf + strlen(buf));
10876             strcat(buf, ")");
10877         }
10878         engineMnemonic[i] = strdup(buf);
10879         i++;
10880     }
10881     engineList[i] = engineMnemonic[i] = NULL;
10882     return i;
10883 }
10884
10885 // following implemented as macro to avoid type limitations
10886 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10887
10888 void
10889 SwapEngines (int n)
10890 {   // swap settings for first engine and other engine (so far only some selected options)
10891     int h;
10892     char *p;
10893     if(n == 0) return;
10894     SWAP(directory, p)
10895     SWAP(chessProgram, p)
10896     SWAP(isUCI, h)
10897     SWAP(hasOwnBookUCI, h)
10898     SWAP(protocolVersion, h)
10899     SWAP(reuse, h)
10900     SWAP(scoreIsAbsolute, h)
10901     SWAP(timeOdds, h)
10902     SWAP(logo, p)
10903     SWAP(pgnName, p)
10904     SWAP(pvSAN, h)
10905     SWAP(engOptions, p)
10906     SWAP(engInitString, p)
10907     SWAP(computerString, p)
10908     SWAP(features, p)
10909     SWAP(fenOverride, p)
10910     SWAP(NPS, h)
10911     SWAP(accumulateTC, h)
10912     SWAP(drawDepth, h)
10913     SWAP(host, p)
10914     SWAP(pseudo, h)
10915 }
10916
10917 int
10918 GetEngineLine (char *s, int n)
10919 {
10920     int i;
10921     char buf[MSG_SIZ];
10922     extern char *icsNames;
10923     if(!s || !*s) return 0;
10924     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10925     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10926     if(!mnemonic[i]) return 0;
10927     if(n == 11) return 1; // just testing if there was a match
10928     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10929     if(n == 1) SwapEngines(n);
10930     ParseArgsFromString(buf);
10931     if(n == 1) SwapEngines(n);
10932     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10933         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10934         ParseArgsFromString(buf);
10935     }
10936     return 1;
10937 }
10938
10939 int
10940 SetPlayer (int player, char *p)
10941 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10942     int i;
10943     char buf[MSG_SIZ], *engineName;
10944     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10945     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10946     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10947     if(mnemonic[i]) {
10948         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10949         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10950         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10951         ParseArgsFromString(buf);
10952     } else { // no engine with this nickname is installed!
10953         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10954         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10955         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10956         ModeHighlight();
10957         DisplayError(buf, 0);
10958         return 0;
10959     }
10960     free(engineName);
10961     return i;
10962 }
10963
10964 char *recentEngines;
10965
10966 void
10967 RecentEngineEvent (int nr)
10968 {
10969     int n;
10970 //    SwapEngines(1); // bump first to second
10971 //    ReplaceEngine(&second, 1); // and load it there
10972     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10973     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10974     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10975         ReplaceEngine(&first, 0);
10976         FloatToFront(&appData.recentEngineList, command[n]);
10977     }
10978 }
10979
10980 int
10981 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10982 {   // determine players from game number
10983     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10984
10985     if(appData.tourneyType == 0) {
10986         roundsPerCycle = (nPlayers - 1) | 1;
10987         pairingsPerRound = nPlayers / 2;
10988     } else if(appData.tourneyType > 0) {
10989         roundsPerCycle = nPlayers - appData.tourneyType;
10990         pairingsPerRound = appData.tourneyType;
10991     }
10992     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10993     gamesPerCycle = gamesPerRound * roundsPerCycle;
10994     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10995     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10996     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10997     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10998     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10999     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11000
11001     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11002     if(appData.roundSync) *syncInterval = gamesPerRound;
11003
11004     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11005
11006     if(appData.tourneyType == 0) {
11007         if(curPairing == (nPlayers-1)/2 ) {
11008             *whitePlayer = curRound;
11009             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11010         } else {
11011             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11012             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11013             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11014             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11015         }
11016     } else if(appData.tourneyType > 1) {
11017         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11018         *whitePlayer = curRound + appData.tourneyType;
11019     } else if(appData.tourneyType > 0) {
11020         *whitePlayer = curPairing;
11021         *blackPlayer = curRound + appData.tourneyType;
11022     }
11023
11024     // take care of white/black alternation per round.
11025     // For cycles and games this is already taken care of by default, derived from matchGame!
11026     return curRound & 1;
11027 }
11028
11029 int
11030 NextTourneyGame (int nr, int *swapColors)
11031 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11032     char *p, *q;
11033     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11034     FILE *tf;
11035     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11036     tf = fopen(appData.tourneyFile, "r");
11037     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11038     ParseArgsFromFile(tf); fclose(tf);
11039     InitTimeControls(); // TC might be altered from tourney file
11040
11041     nPlayers = CountPlayers(appData.participants); // count participants
11042     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11043     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11044
11045     if(syncInterval) {
11046         p = q = appData.results;
11047         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11048         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11049             DisplayMessage(_("Waiting for other game(s)"),"");
11050             waitingForGame = TRUE;
11051             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11052             return 0;
11053         }
11054         waitingForGame = FALSE;
11055     }
11056
11057     if(appData.tourneyType < 0) {
11058         if(nr>=0 && !pairingReceived) {
11059             char buf[1<<16];
11060             if(pairing.pr == NoProc) {
11061                 if(!appData.pairingEngine[0]) {
11062                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11063                     return 0;
11064                 }
11065                 StartChessProgram(&pairing); // starts the pairing engine
11066             }
11067             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11068             SendToProgram(buf, &pairing);
11069             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11070             SendToProgram(buf, &pairing);
11071             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11072         }
11073         pairingReceived = 0;                              // ... so we continue here
11074         *swapColors = 0;
11075         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11076         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11077         matchGame = 1; roundNr = nr / syncInterval + 1;
11078     }
11079
11080     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11081
11082     // redefine engines, engine dir, etc.
11083     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11084     if(first.pr == NoProc) {
11085       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11086       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11087     }
11088     if(second.pr == NoProc) {
11089       SwapEngines(1);
11090       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11091       SwapEngines(1);         // and make that valid for second engine by swapping
11092       InitEngine(&second, 1);
11093     }
11094     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11095     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11096     return OK;
11097 }
11098
11099 void
11100 NextMatchGame ()
11101 {   // performs game initialization that does not invoke engines, and then tries to start the game
11102     int res, firstWhite, swapColors = 0;
11103     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11104     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
11105         char buf[MSG_SIZ];
11106         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11107         if(strcmp(buf, currentDebugFile)) { // name has changed
11108             FILE *f = fopen(buf, "w");
11109             if(f) { // if opening the new file failed, just keep using the old one
11110                 ASSIGN(currentDebugFile, buf);
11111                 fclose(debugFP);
11112                 debugFP = f;
11113             }
11114             if(appData.serverFileName) {
11115                 if(serverFP) fclose(serverFP);
11116                 serverFP = fopen(appData.serverFileName, "w");
11117                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11118                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11119             }
11120         }
11121     }
11122     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11123     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11124     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11125     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11126     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11127     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11128     Reset(FALSE, first.pr != NoProc);
11129     res = LoadGameOrPosition(matchGame); // setup game
11130     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11131     if(!res) return; // abort when bad game/pos file
11132     TwoMachinesEvent();
11133 }
11134
11135 void
11136 UserAdjudicationEvent (int result)
11137 {
11138     ChessMove gameResult = GameIsDrawn;
11139
11140     if( result > 0 ) {
11141         gameResult = WhiteWins;
11142     }
11143     else if( result < 0 ) {
11144         gameResult = BlackWins;
11145     }
11146
11147     if( gameMode == TwoMachinesPlay ) {
11148         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11149     }
11150 }
11151
11152
11153 // [HGM] save: calculate checksum of game to make games easily identifiable
11154 int
11155 StringCheckSum (char *s)
11156 {
11157         int i = 0;
11158         if(s==NULL) return 0;
11159         while(*s) i = i*259 + *s++;
11160         return i;
11161 }
11162
11163 int
11164 GameCheckSum ()
11165 {
11166         int i, sum=0;
11167         for(i=backwardMostMove; i<forwardMostMove; i++) {
11168                 sum += pvInfoList[i].depth;
11169                 sum += StringCheckSum(parseList[i]);
11170                 sum += StringCheckSum(commentList[i]);
11171                 sum *= 261;
11172         }
11173         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11174         return sum + StringCheckSum(commentList[i]);
11175 } // end of save patch
11176
11177 void
11178 GameEnds (ChessMove result, char *resultDetails, int whosays)
11179 {
11180     GameMode nextGameMode;
11181     int isIcsGame;
11182     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11183
11184     if(endingGame) return; /* [HGM] crash: forbid recursion */
11185     endingGame = 1;
11186     if(twoBoards) { // [HGM] dual: switch back to one board
11187         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11188         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11189     }
11190     if (appData.debugMode) {
11191       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11192               result, resultDetails ? resultDetails : "(null)", whosays);
11193     }
11194
11195     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11196
11197     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11198
11199     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11200         /* If we are playing on ICS, the server decides when the
11201            game is over, but the engine can offer to draw, claim
11202            a draw, or resign.
11203          */
11204 #if ZIPPY
11205         if (appData.zippyPlay && first.initDone) {
11206             if (result == GameIsDrawn) {
11207                 /* In case draw still needs to be claimed */
11208                 SendToICS(ics_prefix);
11209                 SendToICS("draw\n");
11210             } else if (StrCaseStr(resultDetails, "resign")) {
11211                 SendToICS(ics_prefix);
11212                 SendToICS("resign\n");
11213             }
11214         }
11215 #endif
11216         endingGame = 0; /* [HGM] crash */
11217         return;
11218     }
11219
11220     /* If we're loading the game from a file, stop */
11221     if (whosays == GE_FILE) {
11222       (void) StopLoadGameTimer();
11223       gameFileFP = NULL;
11224     }
11225
11226     /* Cancel draw offers */
11227     first.offeredDraw = second.offeredDraw = 0;
11228
11229     /* If this is an ICS game, only ICS can really say it's done;
11230        if not, anyone can. */
11231     isIcsGame = (gameMode == IcsPlayingWhite ||
11232                  gameMode == IcsPlayingBlack ||
11233                  gameMode == IcsObserving    ||
11234                  gameMode == IcsExamining);
11235
11236     if (!isIcsGame || whosays == GE_ICS) {
11237         /* OK -- not an ICS game, or ICS said it was done */
11238         StopClocks();
11239         if (!isIcsGame && !appData.noChessProgram)
11240           SetUserThinkingEnables();
11241
11242         /* [HGM] if a machine claims the game end we verify this claim */
11243         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11244             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11245                 char claimer;
11246                 ChessMove trueResult = (ChessMove) -1;
11247
11248                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11249                                             first.twoMachinesColor[0] :
11250                                             second.twoMachinesColor[0] ;
11251
11252                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11253                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11254                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11255                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11256                 } else
11257                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11258                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11259                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11260                 } else
11261                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11262                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11263                 }
11264
11265                 // now verify win claims, but not in drop games, as we don't understand those yet
11266                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11267                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11268                     (result == WhiteWins && claimer == 'w' ||
11269                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11270                       if (appData.debugMode) {
11271                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11272                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11273                       }
11274                       if(result != trueResult) {
11275                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11276                               result = claimer == 'w' ? BlackWins : WhiteWins;
11277                               resultDetails = buf;
11278                       }
11279                 } else
11280                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11281                     && (forwardMostMove <= backwardMostMove ||
11282                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11283                         (claimer=='b')==(forwardMostMove&1))
11284                                                                                   ) {
11285                       /* [HGM] verify: draws that were not flagged are false claims */
11286                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11287                       result = claimer == 'w' ? BlackWins : WhiteWins;
11288                       resultDetails = buf;
11289                 }
11290                 /* (Claiming a loss is accepted no questions asked!) */
11291             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11292                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11293                 result = GameUnfinished;
11294                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11295             }
11296             /* [HGM] bare: don't allow bare King to win */
11297             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11298                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11299                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11300                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11301                && result != GameIsDrawn)
11302             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11303                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11304                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11305                         if(p >= 0 && p <= (int)WhiteKing) k++;
11306                 }
11307                 if (appData.debugMode) {
11308                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11309                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11310                 }
11311                 if(k <= 1) {
11312                         result = GameIsDrawn;
11313                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11314                         resultDetails = buf;
11315                 }
11316             }
11317         }
11318
11319
11320         if(serverMoves != NULL && !loadFlag) { char c = '=';
11321             if(result==WhiteWins) c = '+';
11322             if(result==BlackWins) c = '-';
11323             if(resultDetails != NULL)
11324                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11325         }
11326         if (resultDetails != NULL) {
11327             gameInfo.result = result;
11328             gameInfo.resultDetails = StrSave(resultDetails);
11329
11330             /* display last move only if game was not loaded from file */
11331             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11332                 DisplayMove(currentMove - 1);
11333
11334             if (forwardMostMove != 0) {
11335                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11336                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11337                                                                 ) {
11338                     if (*appData.saveGameFile != NULLCHAR) {
11339                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11340                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11341                         else
11342                         SaveGameToFile(appData.saveGameFile, TRUE);
11343                     } else if (appData.autoSaveGames) {
11344                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11345                     }
11346                     if (*appData.savePositionFile != NULLCHAR) {
11347                         SavePositionToFile(appData.savePositionFile);
11348                     }
11349                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11350                 }
11351             }
11352
11353             /* Tell program how game ended in case it is learning */
11354             /* [HGM] Moved this to after saving the PGN, just in case */
11355             /* engine died and we got here through time loss. In that */
11356             /* case we will get a fatal error writing the pipe, which */
11357             /* would otherwise lose us the PGN.                       */
11358             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11359             /* output during GameEnds should never be fatal anymore   */
11360             if (gameMode == MachinePlaysWhite ||
11361                 gameMode == MachinePlaysBlack ||
11362                 gameMode == TwoMachinesPlay ||
11363                 gameMode == IcsPlayingWhite ||
11364                 gameMode == IcsPlayingBlack ||
11365                 gameMode == BeginningOfGame) {
11366                 char buf[MSG_SIZ];
11367                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11368                         resultDetails);
11369                 if (first.pr != NoProc) {
11370                     SendToProgram(buf, &first);
11371                 }
11372                 if (second.pr != NoProc &&
11373                     gameMode == TwoMachinesPlay) {
11374                     SendToProgram(buf, &second);
11375                 }
11376             }
11377         }
11378
11379         if (appData.icsActive) {
11380             if (appData.quietPlay &&
11381                 (gameMode == IcsPlayingWhite ||
11382                  gameMode == IcsPlayingBlack)) {
11383                 SendToICS(ics_prefix);
11384                 SendToICS("set shout 1\n");
11385             }
11386             nextGameMode = IcsIdle;
11387             ics_user_moved = FALSE;
11388             /* clean up premove.  It's ugly when the game has ended and the
11389              * premove highlights are still on the board.
11390              */
11391             if (gotPremove) {
11392               gotPremove = FALSE;
11393               ClearPremoveHighlights();
11394               DrawPosition(FALSE, boards[currentMove]);
11395             }
11396             if (whosays == GE_ICS) {
11397                 switch (result) {
11398                 case WhiteWins:
11399                     if (gameMode == IcsPlayingWhite)
11400                         PlayIcsWinSound();
11401                     else if(gameMode == IcsPlayingBlack)
11402                         PlayIcsLossSound();
11403                     break;
11404                 case BlackWins:
11405                     if (gameMode == IcsPlayingBlack)
11406                         PlayIcsWinSound();
11407                     else if(gameMode == IcsPlayingWhite)
11408                         PlayIcsLossSound();
11409                     break;
11410                 case GameIsDrawn:
11411                     PlayIcsDrawSound();
11412                     break;
11413                 default:
11414                     PlayIcsUnfinishedSound();
11415                 }
11416             }
11417             if(appData.quitNext) { ExitEvent(0); return; }
11418         } else if (gameMode == EditGame ||
11419                    gameMode == PlayFromGameFile ||
11420                    gameMode == AnalyzeMode ||
11421                    gameMode == AnalyzeFile) {
11422             nextGameMode = gameMode;
11423         } else {
11424             nextGameMode = EndOfGame;
11425         }
11426         pausing = FALSE;
11427         ModeHighlight();
11428     } else {
11429         nextGameMode = gameMode;
11430     }
11431
11432     if (appData.noChessProgram) {
11433         gameMode = nextGameMode;
11434         ModeHighlight();
11435         endingGame = 0; /* [HGM] crash */
11436         return;
11437     }
11438
11439     if (first.reuse) {
11440         /* Put first chess program into idle state */
11441         if (first.pr != NoProc &&
11442             (gameMode == MachinePlaysWhite ||
11443              gameMode == MachinePlaysBlack ||
11444              gameMode == TwoMachinesPlay ||
11445              gameMode == IcsPlayingWhite ||
11446              gameMode == IcsPlayingBlack ||
11447              gameMode == BeginningOfGame)) {
11448             SendToProgram("force\n", &first);
11449             if (first.usePing) {
11450               char buf[MSG_SIZ];
11451               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11452               SendToProgram(buf, &first);
11453             }
11454         }
11455     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11456         /* Kill off first chess program */
11457         if (first.isr != NULL)
11458           RemoveInputSource(first.isr);
11459         first.isr = NULL;
11460
11461         if (first.pr != NoProc) {
11462             ExitAnalyzeMode();
11463             DoSleep( appData.delayBeforeQuit );
11464             SendToProgram("quit\n", &first);
11465             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11466             first.reload = TRUE;
11467         }
11468         first.pr = NoProc;
11469     }
11470     if (second.reuse) {
11471         /* Put second chess program into idle state */
11472         if (second.pr != NoProc &&
11473             gameMode == TwoMachinesPlay) {
11474             SendToProgram("force\n", &second);
11475             if (second.usePing) {
11476               char buf[MSG_SIZ];
11477               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11478               SendToProgram(buf, &second);
11479             }
11480         }
11481     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11482         /* Kill off second chess program */
11483         if (second.isr != NULL)
11484           RemoveInputSource(second.isr);
11485         second.isr = NULL;
11486
11487         if (second.pr != NoProc) {
11488             DoSleep( appData.delayBeforeQuit );
11489             SendToProgram("quit\n", &second);
11490             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11491             second.reload = TRUE;
11492         }
11493         second.pr = NoProc;
11494     }
11495
11496     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11497         char resChar = '=';
11498         switch (result) {
11499         case WhiteWins:
11500           resChar = '+';
11501           if (first.twoMachinesColor[0] == 'w') {
11502             first.matchWins++;
11503           } else {
11504             second.matchWins++;
11505           }
11506           break;
11507         case BlackWins:
11508           resChar = '-';
11509           if (first.twoMachinesColor[0] == 'b') {
11510             first.matchWins++;
11511           } else {
11512             second.matchWins++;
11513           }
11514           break;
11515         case GameUnfinished:
11516           resChar = ' ';
11517         default:
11518           break;
11519         }
11520
11521         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11522         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11523             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11524             ReserveGame(nextGame, resChar); // sets nextGame
11525             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11526             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11527         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11528
11529         if (nextGame <= appData.matchGames && !abortMatch) {
11530             gameMode = nextGameMode;
11531             matchGame = nextGame; // this will be overruled in tourney mode!
11532             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11533             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11534             endingGame = 0; /* [HGM] crash */
11535             return;
11536         } else {
11537             gameMode = nextGameMode;
11538             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11539                      first.tidy, second.tidy,
11540                      first.matchWins, second.matchWins,
11541                      appData.matchGames - (first.matchWins + second.matchWins));
11542             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11543             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11544             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11545             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11546                 first.twoMachinesColor = "black\n";
11547                 second.twoMachinesColor = "white\n";
11548             } else {
11549                 first.twoMachinesColor = "white\n";
11550                 second.twoMachinesColor = "black\n";
11551             }
11552         }
11553     }
11554     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11555         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11556       ExitAnalyzeMode();
11557     gameMode = nextGameMode;
11558     ModeHighlight();
11559     endingGame = 0;  /* [HGM] crash */
11560     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11561         if(matchMode == TRUE) { // match through command line: exit with or without popup
11562             if(ranking) {
11563                 ToNrEvent(forwardMostMove);
11564                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11565                 else ExitEvent(0);
11566             } else DisplayFatalError(buf, 0, 0);
11567         } else { // match through menu; just stop, with or without popup
11568             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11569             ModeHighlight();
11570             if(ranking){
11571                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11572             } else DisplayNote(buf);
11573       }
11574       if(ranking) free(ranking);
11575     }
11576 }
11577
11578 /* Assumes program was just initialized (initString sent).
11579    Leaves program in force mode. */
11580 void
11581 FeedMovesToProgram (ChessProgramState *cps, int upto)
11582 {
11583     int i;
11584
11585     if (appData.debugMode)
11586       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11587               startedFromSetupPosition ? "position and " : "",
11588               backwardMostMove, upto, cps->which);
11589     if(currentlyInitializedVariant != gameInfo.variant) {
11590       char buf[MSG_SIZ];
11591         // [HGM] variantswitch: make engine aware of new variant
11592         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11593                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11594                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11595         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11596         SendToProgram(buf, cps);
11597         currentlyInitializedVariant = gameInfo.variant;
11598     }
11599     SendToProgram("force\n", cps);
11600     if (startedFromSetupPosition) {
11601         SendBoard(cps, backwardMostMove);
11602     if (appData.debugMode) {
11603         fprintf(debugFP, "feedMoves\n");
11604     }
11605     }
11606     for (i = backwardMostMove; i < upto; i++) {
11607         SendMoveToProgram(i, cps);
11608     }
11609 }
11610
11611
11612 int
11613 ResurrectChessProgram ()
11614 {
11615      /* The chess program may have exited.
11616         If so, restart it and feed it all the moves made so far. */
11617     static int doInit = 0;
11618
11619     if (appData.noChessProgram) return 1;
11620
11621     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11622         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11623         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11624         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11625     } else {
11626         if (first.pr != NoProc) return 1;
11627         StartChessProgram(&first);
11628     }
11629     InitChessProgram(&first, FALSE);
11630     FeedMovesToProgram(&first, currentMove);
11631
11632     if (!first.sendTime) {
11633         /* can't tell gnuchess what its clock should read,
11634            so we bow to its notion. */
11635         ResetClocks();
11636         timeRemaining[0][currentMove] = whiteTimeRemaining;
11637         timeRemaining[1][currentMove] = blackTimeRemaining;
11638     }
11639
11640     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11641                 appData.icsEngineAnalyze) && first.analysisSupport) {
11642       SendToProgram("analyze\n", &first);
11643       first.analyzing = TRUE;
11644     }
11645     return 1;
11646 }
11647
11648 /*
11649  * Button procedures
11650  */
11651 void
11652 Reset (int redraw, int init)
11653 {
11654     int i;
11655
11656     if (appData.debugMode) {
11657         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11658                 redraw, init, gameMode);
11659     }
11660     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11661     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11662     CleanupTail(); // [HGM] vari: delete any stored variations
11663     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11664     pausing = pauseExamInvalid = FALSE;
11665     startedFromSetupPosition = blackPlaysFirst = FALSE;
11666     firstMove = TRUE;
11667     whiteFlag = blackFlag = FALSE;
11668     userOfferedDraw = FALSE;
11669     hintRequested = bookRequested = FALSE;
11670     first.maybeThinking = FALSE;
11671     second.maybeThinking = FALSE;
11672     first.bookSuspend = FALSE; // [HGM] book
11673     second.bookSuspend = FALSE;
11674     thinkOutput[0] = NULLCHAR;
11675     lastHint[0] = NULLCHAR;
11676     ClearGameInfo(&gameInfo);
11677     gameInfo.variant = StringToVariant(appData.variant);
11678     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11679     ics_user_moved = ics_clock_paused = FALSE;
11680     ics_getting_history = H_FALSE;
11681     ics_gamenum = -1;
11682     white_holding[0] = black_holding[0] = NULLCHAR;
11683     ClearProgramStats();
11684     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11685
11686     ResetFrontEnd();
11687     ClearHighlights();
11688     flipView = appData.flipView;
11689     ClearPremoveHighlights();
11690     gotPremove = FALSE;
11691     alarmSounded = FALSE;
11692     killX = killY = -1; // [HGM] lion
11693
11694     GameEnds(EndOfFile, NULL, GE_PLAYER);
11695     if(appData.serverMovesName != NULL) {
11696         /* [HGM] prepare to make moves file for broadcasting */
11697         clock_t t = clock();
11698         if(serverMoves != NULL) fclose(serverMoves);
11699         serverMoves = fopen(appData.serverMovesName, "r");
11700         if(serverMoves != NULL) {
11701             fclose(serverMoves);
11702             /* delay 15 sec before overwriting, so all clients can see end */
11703             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11704         }
11705         serverMoves = fopen(appData.serverMovesName, "w");
11706     }
11707
11708     ExitAnalyzeMode();
11709     gameMode = BeginningOfGame;
11710     ModeHighlight();
11711     if(appData.icsActive) gameInfo.variant = VariantNormal;
11712     currentMove = forwardMostMove = backwardMostMove = 0;
11713     MarkTargetSquares(1);
11714     InitPosition(redraw);
11715     for (i = 0; i < MAX_MOVES; i++) {
11716         if (commentList[i] != NULL) {
11717             free(commentList[i]);
11718             commentList[i] = NULL;
11719         }
11720     }
11721     ResetClocks();
11722     timeRemaining[0][0] = whiteTimeRemaining;
11723     timeRemaining[1][0] = blackTimeRemaining;
11724
11725     if (first.pr == NoProc) {
11726         StartChessProgram(&first);
11727     }
11728     if (init) {
11729             InitChessProgram(&first, startedFromSetupPosition);
11730     }
11731     DisplayTitle("");
11732     DisplayMessage("", "");
11733     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11734     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11735     ClearMap();        // [HGM] exclude: invalidate map
11736 }
11737
11738 void
11739 AutoPlayGameLoop ()
11740 {
11741     for (;;) {
11742         if (!AutoPlayOneMove())
11743           return;
11744         if (matchMode || appData.timeDelay == 0)
11745           continue;
11746         if (appData.timeDelay < 0)
11747           return;
11748         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11749         break;
11750     }
11751 }
11752
11753 void
11754 AnalyzeNextGame()
11755 {
11756     ReloadGame(1); // next game
11757 }
11758
11759 int
11760 AutoPlayOneMove ()
11761 {
11762     int fromX, fromY, toX, toY;
11763
11764     if (appData.debugMode) {
11765       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11766     }
11767
11768     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11769       return FALSE;
11770
11771     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11772       pvInfoList[currentMove].depth = programStats.depth;
11773       pvInfoList[currentMove].score = programStats.score;
11774       pvInfoList[currentMove].time  = 0;
11775       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11776       else { // append analysis of final position as comment
11777         char buf[MSG_SIZ];
11778         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11779         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11780       }
11781       programStats.depth = 0;
11782     }
11783
11784     if (currentMove >= forwardMostMove) {
11785       if(gameMode == AnalyzeFile) {
11786           if(appData.loadGameIndex == -1) {
11787             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11788           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11789           } else {
11790           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11791         }
11792       }
11793 //      gameMode = EndOfGame;
11794 //      ModeHighlight();
11795
11796       /* [AS] Clear current move marker at the end of a game */
11797       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11798
11799       return FALSE;
11800     }
11801
11802     toX = moveList[currentMove][2] - AAA;
11803     toY = moveList[currentMove][3] - ONE;
11804
11805     if (moveList[currentMove][1] == '@') {
11806         if (appData.highlightLastMove) {
11807             SetHighlights(-1, -1, toX, toY);
11808         }
11809     } else {
11810         int viaX = moveList[currentMove][5] - AAA;
11811         int viaY = moveList[currentMove][6] - ONE;
11812         fromX = moveList[currentMove][0] - AAA;
11813         fromY = moveList[currentMove][1] - ONE;
11814
11815         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11816
11817         if(moveList[currentMove][4] == ';') { // multi-leg
11818             ChessSquare piece = boards[currentMove][viaY][viaX];
11819             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11820             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11821             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11822             boards[currentMove][viaY][viaX] = piece;
11823         } else
11824         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11825
11826         if (appData.highlightLastMove) {
11827             SetHighlights(fromX, fromY, toX, toY);
11828         }
11829     }
11830     DisplayMove(currentMove);
11831     SendMoveToProgram(currentMove++, &first);
11832     DisplayBothClocks();
11833     DrawPosition(FALSE, boards[currentMove]);
11834     // [HGM] PV info: always display, routine tests if empty
11835     DisplayComment(currentMove - 1, commentList[currentMove]);
11836     return TRUE;
11837 }
11838
11839
11840 int
11841 LoadGameOneMove (ChessMove readAhead)
11842 {
11843     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11844     char promoChar = NULLCHAR;
11845     ChessMove moveType;
11846     char move[MSG_SIZ];
11847     char *p, *q;
11848
11849     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11850         gameMode != AnalyzeMode && gameMode != Training) {
11851         gameFileFP = NULL;
11852         return FALSE;
11853     }
11854
11855     yyboardindex = forwardMostMove;
11856     if (readAhead != EndOfFile) {
11857       moveType = readAhead;
11858     } else {
11859       if (gameFileFP == NULL)
11860           return FALSE;
11861       moveType = (ChessMove) Myylex();
11862     }
11863
11864     done = FALSE;
11865     switch (moveType) {
11866       case Comment:
11867         if (appData.debugMode)
11868           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11869         p = yy_text;
11870
11871         /* append the comment but don't display it */
11872         AppendComment(currentMove, p, FALSE);
11873         return TRUE;
11874
11875       case WhiteCapturesEnPassant:
11876       case BlackCapturesEnPassant:
11877       case WhitePromotion:
11878       case BlackPromotion:
11879       case WhiteNonPromotion:
11880       case BlackNonPromotion:
11881       case NormalMove:
11882       case FirstLeg:
11883       case WhiteKingSideCastle:
11884       case WhiteQueenSideCastle:
11885       case BlackKingSideCastle:
11886       case BlackQueenSideCastle:
11887       case WhiteKingSideCastleWild:
11888       case WhiteQueenSideCastleWild:
11889       case BlackKingSideCastleWild:
11890       case BlackQueenSideCastleWild:
11891       /* PUSH Fabien */
11892       case WhiteHSideCastleFR:
11893       case WhiteASideCastleFR:
11894       case BlackHSideCastleFR:
11895       case BlackASideCastleFR:
11896       /* POP Fabien */
11897         if (appData.debugMode)
11898           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11899         fromX = currentMoveString[0] - AAA;
11900         fromY = currentMoveString[1] - ONE;
11901         toX = currentMoveString[2] - AAA;
11902         toY = currentMoveString[3] - ONE;
11903         promoChar = currentMoveString[4];
11904         if(promoChar == ';') promoChar = NULLCHAR;
11905         break;
11906
11907       case WhiteDrop:
11908       case BlackDrop:
11909         if (appData.debugMode)
11910           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11911         fromX = moveType == WhiteDrop ?
11912           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11913         (int) CharToPiece(ToLower(currentMoveString[0]));
11914         fromY = DROP_RANK;
11915         toX = currentMoveString[2] - AAA;
11916         toY = currentMoveString[3] - ONE;
11917         break;
11918
11919       case WhiteWins:
11920       case BlackWins:
11921       case GameIsDrawn:
11922       case GameUnfinished:
11923         if (appData.debugMode)
11924           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11925         p = strchr(yy_text, '{');
11926         if (p == NULL) p = strchr(yy_text, '(');
11927         if (p == NULL) {
11928             p = yy_text;
11929             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11930         } else {
11931             q = strchr(p, *p == '{' ? '}' : ')');
11932             if (q != NULL) *q = NULLCHAR;
11933             p++;
11934         }
11935         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11936         GameEnds(moveType, p, GE_FILE);
11937         done = TRUE;
11938         if (cmailMsgLoaded) {
11939             ClearHighlights();
11940             flipView = WhiteOnMove(currentMove);
11941             if (moveType == GameUnfinished) flipView = !flipView;
11942             if (appData.debugMode)
11943               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11944         }
11945         break;
11946
11947       case EndOfFile:
11948         if (appData.debugMode)
11949           fprintf(debugFP, "Parser hit end of file\n");
11950         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11951           case MT_NONE:
11952           case MT_CHECK:
11953             break;
11954           case MT_CHECKMATE:
11955           case MT_STAINMATE:
11956             if (WhiteOnMove(currentMove)) {
11957                 GameEnds(BlackWins, "Black mates", GE_FILE);
11958             } else {
11959                 GameEnds(WhiteWins, "White mates", GE_FILE);
11960             }
11961             break;
11962           case MT_STALEMATE:
11963             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11964             break;
11965         }
11966         done = TRUE;
11967         break;
11968
11969       case MoveNumberOne:
11970         if (lastLoadGameStart == GNUChessGame) {
11971             /* GNUChessGames have numbers, but they aren't move numbers */
11972             if (appData.debugMode)
11973               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11974                       yy_text, (int) moveType);
11975             return LoadGameOneMove(EndOfFile); /* tail recursion */
11976         }
11977         /* else fall thru */
11978
11979       case XBoardGame:
11980       case GNUChessGame:
11981       case PGNTag:
11982         /* Reached start of next game in file */
11983         if (appData.debugMode)
11984           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11985         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11986           case MT_NONE:
11987           case MT_CHECK:
11988             break;
11989           case MT_CHECKMATE:
11990           case MT_STAINMATE:
11991             if (WhiteOnMove(currentMove)) {
11992                 GameEnds(BlackWins, "Black mates", GE_FILE);
11993             } else {
11994                 GameEnds(WhiteWins, "White mates", GE_FILE);
11995             }
11996             break;
11997           case MT_STALEMATE:
11998             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11999             break;
12000         }
12001         done = TRUE;
12002         break;
12003
12004       case PositionDiagram:     /* should not happen; ignore */
12005       case ElapsedTime:         /* ignore */
12006       case NAG:                 /* ignore */
12007         if (appData.debugMode)
12008           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12009                   yy_text, (int) moveType);
12010         return LoadGameOneMove(EndOfFile); /* tail recursion */
12011
12012       case IllegalMove:
12013         if (appData.testLegality) {
12014             if (appData.debugMode)
12015               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12016             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12017                     (forwardMostMove / 2) + 1,
12018                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12019             DisplayError(move, 0);
12020             done = TRUE;
12021         } else {
12022             if (appData.debugMode)
12023               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12024                       yy_text, currentMoveString);
12025             fromX = currentMoveString[0] - AAA;
12026             fromY = currentMoveString[1] - ONE;
12027             toX = currentMoveString[2] - AAA;
12028             toY = currentMoveString[3] - ONE;
12029             promoChar = currentMoveString[4];
12030         }
12031         break;
12032
12033       case AmbiguousMove:
12034         if (appData.debugMode)
12035           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12036         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12037                 (forwardMostMove / 2) + 1,
12038                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12039         DisplayError(move, 0);
12040         done = TRUE;
12041         break;
12042
12043       default:
12044       case ImpossibleMove:
12045         if (appData.debugMode)
12046           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12047         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12048                 (forwardMostMove / 2) + 1,
12049                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12050         DisplayError(move, 0);
12051         done = TRUE;
12052         break;
12053     }
12054
12055     if (done) {
12056         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12057             DrawPosition(FALSE, boards[currentMove]);
12058             DisplayBothClocks();
12059             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12060               DisplayComment(currentMove - 1, commentList[currentMove]);
12061         }
12062         (void) StopLoadGameTimer();
12063         gameFileFP = NULL;
12064         cmailOldMove = forwardMostMove;
12065         return FALSE;
12066     } else {
12067         /* currentMoveString is set as a side-effect of yylex */
12068
12069         thinkOutput[0] = NULLCHAR;
12070         MakeMove(fromX, fromY, toX, toY, promoChar);
12071         killX = killY = -1; // [HGM] lion: used up
12072         currentMove = forwardMostMove;
12073         return TRUE;
12074     }
12075 }
12076
12077 /* Load the nth game from the given file */
12078 int
12079 LoadGameFromFile (char *filename, int n, char *title, int useList)
12080 {
12081     FILE *f;
12082     char buf[MSG_SIZ];
12083
12084     if (strcmp(filename, "-") == 0) {
12085         f = stdin;
12086         title = "stdin";
12087     } else {
12088         f = fopen(filename, "rb");
12089         if (f == NULL) {
12090           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12091             DisplayError(buf, errno);
12092             return FALSE;
12093         }
12094     }
12095     if (fseek(f, 0, 0) == -1) {
12096         /* f is not seekable; probably a pipe */
12097         useList = FALSE;
12098     }
12099     if (useList && n == 0) {
12100         int error = GameListBuild(f);
12101         if (error) {
12102             DisplayError(_("Cannot build game list"), error);
12103         } else if (!ListEmpty(&gameList) &&
12104                    ((ListGame *) gameList.tailPred)->number > 1) {
12105             GameListPopUp(f, title);
12106             return TRUE;
12107         }
12108         GameListDestroy();
12109         n = 1;
12110     }
12111     if (n == 0) n = 1;
12112     return LoadGame(f, n, title, FALSE);
12113 }
12114
12115
12116 void
12117 MakeRegisteredMove ()
12118 {
12119     int fromX, fromY, toX, toY;
12120     char promoChar;
12121     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12122         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12123           case CMAIL_MOVE:
12124           case CMAIL_DRAW:
12125             if (appData.debugMode)
12126               fprintf(debugFP, "Restoring %s for game %d\n",
12127                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12128
12129             thinkOutput[0] = NULLCHAR;
12130             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12131             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12132             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12133             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12134             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12135             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12136             MakeMove(fromX, fromY, toX, toY, promoChar);
12137             ShowMove(fromX, fromY, toX, toY);
12138
12139             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12140               case MT_NONE:
12141               case MT_CHECK:
12142                 break;
12143
12144               case MT_CHECKMATE:
12145               case MT_STAINMATE:
12146                 if (WhiteOnMove(currentMove)) {
12147                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12148                 } else {
12149                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12150                 }
12151                 break;
12152
12153               case MT_STALEMATE:
12154                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12155                 break;
12156             }
12157
12158             break;
12159
12160           case CMAIL_RESIGN:
12161             if (WhiteOnMove(currentMove)) {
12162                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12163             } else {
12164                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12165             }
12166             break;
12167
12168           case CMAIL_ACCEPT:
12169             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12170             break;
12171
12172           default:
12173             break;
12174         }
12175     }
12176
12177     return;
12178 }
12179
12180 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12181 int
12182 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12183 {
12184     int retVal;
12185
12186     if (gameNumber > nCmailGames) {
12187         DisplayError(_("No more games in this message"), 0);
12188         return FALSE;
12189     }
12190     if (f == lastLoadGameFP) {
12191         int offset = gameNumber - lastLoadGameNumber;
12192         if (offset == 0) {
12193             cmailMsg[0] = NULLCHAR;
12194             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12195                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12196                 nCmailMovesRegistered--;
12197             }
12198             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12199             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12200                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12201             }
12202         } else {
12203             if (! RegisterMove()) return FALSE;
12204         }
12205     }
12206
12207     retVal = LoadGame(f, gameNumber, title, useList);
12208
12209     /* Make move registered during previous look at this game, if any */
12210     MakeRegisteredMove();
12211
12212     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12213         commentList[currentMove]
12214           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12215         DisplayComment(currentMove - 1, commentList[currentMove]);
12216     }
12217
12218     return retVal;
12219 }
12220
12221 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12222 int
12223 ReloadGame (int offset)
12224 {
12225     int gameNumber = lastLoadGameNumber + offset;
12226     if (lastLoadGameFP == NULL) {
12227         DisplayError(_("No game has been loaded yet"), 0);
12228         return FALSE;
12229     }
12230     if (gameNumber <= 0) {
12231         DisplayError(_("Can't back up any further"), 0);
12232         return FALSE;
12233     }
12234     if (cmailMsgLoaded) {
12235         return CmailLoadGame(lastLoadGameFP, gameNumber,
12236                              lastLoadGameTitle, lastLoadGameUseList);
12237     } else {
12238         return LoadGame(lastLoadGameFP, gameNumber,
12239                         lastLoadGameTitle, lastLoadGameUseList);
12240     }
12241 }
12242
12243 int keys[EmptySquare+1];
12244
12245 int
12246 PositionMatches (Board b1, Board b2)
12247 {
12248     int r, f, sum=0;
12249     switch(appData.searchMode) {
12250         case 1: return CompareWithRights(b1, b2);
12251         case 2:
12252             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12253                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12254             }
12255             return TRUE;
12256         case 3:
12257             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12258               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12259                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12260             }
12261             return sum==0;
12262         case 4:
12263             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12264                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12265             }
12266             return sum==0;
12267     }
12268     return TRUE;
12269 }
12270
12271 #define Q_PROMO  4
12272 #define Q_EP     3
12273 #define Q_BCASTL 2
12274 #define Q_WCASTL 1
12275
12276 int pieceList[256], quickBoard[256];
12277 ChessSquare pieceType[256] = { EmptySquare };
12278 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12279 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12280 int soughtTotal, turn;
12281 Boolean epOK, flipSearch;
12282
12283 typedef struct {
12284     unsigned char piece, to;
12285 } Move;
12286
12287 #define DSIZE (250000)
12288
12289 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12290 Move *moveDatabase = initialSpace;
12291 unsigned int movePtr, dataSize = DSIZE;
12292
12293 int
12294 MakePieceList (Board board, int *counts)
12295 {
12296     int r, f, n=Q_PROMO, total=0;
12297     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12298     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12299         int sq = f + (r<<4);
12300         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12301             quickBoard[sq] = ++n;
12302             pieceList[n] = sq;
12303             pieceType[n] = board[r][f];
12304             counts[board[r][f]]++;
12305             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12306             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12307             total++;
12308         }
12309     }
12310     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12311     return total;
12312 }
12313
12314 void
12315 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12316 {
12317     int sq = fromX + (fromY<<4);
12318     int piece = quickBoard[sq], rook;
12319     quickBoard[sq] = 0;
12320     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12321     if(piece == pieceList[1] && fromY == toY) {
12322       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12323         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12324         moveDatabase[movePtr++].piece = Q_WCASTL;
12325         quickBoard[sq] = piece;
12326         piece = quickBoard[from]; quickBoard[from] = 0;
12327         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12328       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12329         quickBoard[sq] = 0; // remove Rook
12330         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12331         moveDatabase[movePtr++].piece = Q_WCASTL;
12332         quickBoard[sq] = pieceList[1]; // put King
12333         piece = rook;
12334         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12335       }
12336     } else
12337     if(piece == pieceList[2] && fromY == toY) {
12338       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12339         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12340         moveDatabase[movePtr++].piece = Q_BCASTL;
12341         quickBoard[sq] = piece;
12342         piece = quickBoard[from]; quickBoard[from] = 0;
12343         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12344       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12345         quickBoard[sq] = 0; // remove Rook
12346         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12347         moveDatabase[movePtr++].piece = Q_BCASTL;
12348         quickBoard[sq] = pieceList[2]; // put King
12349         piece = rook;
12350         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12351       }
12352     } else
12353     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12354         quickBoard[(fromY<<4)+toX] = 0;
12355         moveDatabase[movePtr].piece = Q_EP;
12356         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12357         moveDatabase[movePtr].to = sq;
12358     } else
12359     if(promoPiece != pieceType[piece]) {
12360         moveDatabase[movePtr++].piece = Q_PROMO;
12361         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12362     }
12363     moveDatabase[movePtr].piece = piece;
12364     quickBoard[sq] = piece;
12365     movePtr++;
12366 }
12367
12368 int
12369 PackGame (Board board)
12370 {
12371     Move *newSpace = NULL;
12372     moveDatabase[movePtr].piece = 0; // terminate previous game
12373     if(movePtr > dataSize) {
12374         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12375         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12376         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12377         if(newSpace) {
12378             int i;
12379             Move *p = moveDatabase, *q = newSpace;
12380             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12381             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12382             moveDatabase = newSpace;
12383         } else { // calloc failed, we must be out of memory. Too bad...
12384             dataSize = 0; // prevent calloc events for all subsequent games
12385             return 0;     // and signal this one isn't cached
12386         }
12387     }
12388     movePtr++;
12389     MakePieceList(board, counts);
12390     return movePtr;
12391 }
12392
12393 int
12394 QuickCompare (Board board, int *minCounts, int *maxCounts)
12395 {   // compare according to search mode
12396     int r, f;
12397     switch(appData.searchMode)
12398     {
12399       case 1: // exact position match
12400         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12401         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12402             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12403         }
12404         break;
12405       case 2: // can have extra material on empty squares
12406         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12407             if(board[r][f] == EmptySquare) continue;
12408             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12409         }
12410         break;
12411       case 3: // material with exact Pawn structure
12412         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12413             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12414             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12415         } // fall through to material comparison
12416       case 4: // exact material
12417         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12418         break;
12419       case 6: // material range with given imbalance
12420         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12421         // fall through to range comparison
12422       case 5: // material range
12423         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12424     }
12425     return TRUE;
12426 }
12427
12428 int
12429 QuickScan (Board board, Move *move)
12430 {   // reconstruct game,and compare all positions in it
12431     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12432     do {
12433         int piece = move->piece;
12434         int to = move->to, from = pieceList[piece];
12435         if(found < 0) { // if already found just scan to game end for final piece count
12436           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12437            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12438            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12439                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12440             ) {
12441             static int lastCounts[EmptySquare+1];
12442             int i;
12443             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12444             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12445           } else stretch = 0;
12446           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12447           if(found >= 0 && !appData.minPieces) return found;
12448         }
12449         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12450           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12451           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12452             piece = (++move)->piece;
12453             from = pieceList[piece];
12454             counts[pieceType[piece]]--;
12455             pieceType[piece] = (ChessSquare) move->to;
12456             counts[move->to]++;
12457           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12458             counts[pieceType[quickBoard[to]]]--;
12459             quickBoard[to] = 0; total--;
12460             move++;
12461             continue;
12462           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12463             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12464             from  = pieceList[piece]; // so this must be King
12465             quickBoard[from] = 0;
12466             pieceList[piece] = to;
12467             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12468             quickBoard[from] = 0; // rook
12469             quickBoard[to] = piece;
12470             to = move->to; piece = move->piece;
12471             goto aftercastle;
12472           }
12473         }
12474         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12475         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12476         quickBoard[from] = 0;
12477       aftercastle:
12478         quickBoard[to] = piece;
12479         pieceList[piece] = to;
12480         cnt++; turn ^= 3;
12481         move++;
12482     } while(1);
12483 }
12484
12485 void
12486 InitSearch ()
12487 {
12488     int r, f;
12489     flipSearch = FALSE;
12490     CopyBoard(soughtBoard, boards[currentMove]);
12491     soughtTotal = MakePieceList(soughtBoard, maxSought);
12492     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12493     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12494     CopyBoard(reverseBoard, boards[currentMove]);
12495     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12496         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12497         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12498         reverseBoard[r][f] = piece;
12499     }
12500     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12501     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12502     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12503                  || (boards[currentMove][CASTLING][2] == NoRights ||
12504                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12505                  && (boards[currentMove][CASTLING][5] == NoRights ||
12506                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12507       ) {
12508         flipSearch = TRUE;
12509         CopyBoard(flipBoard, soughtBoard);
12510         CopyBoard(rotateBoard, reverseBoard);
12511         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12512             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12513             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12514         }
12515     }
12516     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12517     if(appData.searchMode >= 5) {
12518         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12519         MakePieceList(soughtBoard, minSought);
12520         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12521     }
12522     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12523         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12524 }
12525
12526 GameInfo dummyInfo;
12527 static int creatingBook;
12528
12529 int
12530 GameContainsPosition (FILE *f, ListGame *lg)
12531 {
12532     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12533     int fromX, fromY, toX, toY;
12534     char promoChar;
12535     static int initDone=FALSE;
12536
12537     // weed out games based on numerical tag comparison
12538     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12539     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12540     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12541     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12542     if(!initDone) {
12543         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12544         initDone = TRUE;
12545     }
12546     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12547     else CopyBoard(boards[scratch], initialPosition); // default start position
12548     if(lg->moves) {
12549         turn = btm + 1;
12550         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12551         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12552     }
12553     if(btm) plyNr++;
12554     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12555     fseek(f, lg->offset, 0);
12556     yynewfile(f);
12557     while(1) {
12558         yyboardindex = scratch;
12559         quickFlag = plyNr+1;
12560         next = Myylex();
12561         quickFlag = 0;
12562         switch(next) {
12563             case PGNTag:
12564                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12565             default:
12566                 continue;
12567
12568             case XBoardGame:
12569             case GNUChessGame:
12570                 if(plyNr) return -1; // after we have seen moves, this is for new game
12571               continue;
12572
12573             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12574             case ImpossibleMove:
12575             case WhiteWins: // game ends here with these four
12576             case BlackWins:
12577             case GameIsDrawn:
12578             case GameUnfinished:
12579                 return -1;
12580
12581             case IllegalMove:
12582                 if(appData.testLegality) return -1;
12583             case WhiteCapturesEnPassant:
12584             case BlackCapturesEnPassant:
12585             case WhitePromotion:
12586             case BlackPromotion:
12587             case WhiteNonPromotion:
12588             case BlackNonPromotion:
12589             case NormalMove:
12590             case FirstLeg:
12591             case WhiteKingSideCastle:
12592             case WhiteQueenSideCastle:
12593             case BlackKingSideCastle:
12594             case BlackQueenSideCastle:
12595             case WhiteKingSideCastleWild:
12596             case WhiteQueenSideCastleWild:
12597             case BlackKingSideCastleWild:
12598             case BlackQueenSideCastleWild:
12599             case WhiteHSideCastleFR:
12600             case WhiteASideCastleFR:
12601             case BlackHSideCastleFR:
12602             case BlackASideCastleFR:
12603                 fromX = currentMoveString[0] - AAA;
12604                 fromY = currentMoveString[1] - ONE;
12605                 toX = currentMoveString[2] - AAA;
12606                 toY = currentMoveString[3] - ONE;
12607                 promoChar = currentMoveString[4];
12608                 break;
12609             case WhiteDrop:
12610             case BlackDrop:
12611                 fromX = next == WhiteDrop ?
12612                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12613                   (int) CharToPiece(ToLower(currentMoveString[0]));
12614                 fromY = DROP_RANK;
12615                 toX = currentMoveString[2] - AAA;
12616                 toY = currentMoveString[3] - ONE;
12617                 promoChar = 0;
12618                 break;
12619         }
12620         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12621         plyNr++;
12622         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12623         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12624         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12625         if(appData.findMirror) {
12626             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12627             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12628         }
12629     }
12630 }
12631
12632 /* Load the nth game from open file f */
12633 int
12634 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12635 {
12636     ChessMove cm;
12637     char buf[MSG_SIZ];
12638     int gn = gameNumber;
12639     ListGame *lg = NULL;
12640     int numPGNTags = 0;
12641     int err, pos = -1;
12642     GameMode oldGameMode;
12643     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12644
12645     if (appData.debugMode)
12646         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12647
12648     if (gameMode == Training )
12649         SetTrainingModeOff();
12650
12651     oldGameMode = gameMode;
12652     if (gameMode != BeginningOfGame) {
12653       Reset(FALSE, TRUE);
12654     }
12655     killX = killY = -1; // [HGM] lion: in case we did not Reset
12656
12657     gameFileFP = f;
12658     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12659         fclose(lastLoadGameFP);
12660     }
12661
12662     if (useList) {
12663         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12664
12665         if (lg) {
12666             fseek(f, lg->offset, 0);
12667             GameListHighlight(gameNumber);
12668             pos = lg->position;
12669             gn = 1;
12670         }
12671         else {
12672             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12673               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12674             else
12675             DisplayError(_("Game number out of range"), 0);
12676             return FALSE;
12677         }
12678     } else {
12679         GameListDestroy();
12680         if (fseek(f, 0, 0) == -1) {
12681             if (f == lastLoadGameFP ?
12682                 gameNumber == lastLoadGameNumber + 1 :
12683                 gameNumber == 1) {
12684                 gn = 1;
12685             } else {
12686                 DisplayError(_("Can't seek on game file"), 0);
12687                 return FALSE;
12688             }
12689         }
12690     }
12691     lastLoadGameFP = f;
12692     lastLoadGameNumber = gameNumber;
12693     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12694     lastLoadGameUseList = useList;
12695
12696     yynewfile(f);
12697
12698     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12699       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12700                 lg->gameInfo.black);
12701             DisplayTitle(buf);
12702     } else if (*title != NULLCHAR) {
12703         if (gameNumber > 1) {
12704           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12705             DisplayTitle(buf);
12706         } else {
12707             DisplayTitle(title);
12708         }
12709     }
12710
12711     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12712         gameMode = PlayFromGameFile;
12713         ModeHighlight();
12714     }
12715
12716     currentMove = forwardMostMove = backwardMostMove = 0;
12717     CopyBoard(boards[0], initialPosition);
12718     StopClocks();
12719
12720     /*
12721      * Skip the first gn-1 games in the file.
12722      * Also skip over anything that precedes an identifiable
12723      * start of game marker, to avoid being confused by
12724      * garbage at the start of the file.  Currently
12725      * recognized start of game markers are the move number "1",
12726      * the pattern "gnuchess .* game", the pattern
12727      * "^[#;%] [^ ]* game file", and a PGN tag block.
12728      * A game that starts with one of the latter two patterns
12729      * will also have a move number 1, possibly
12730      * following a position diagram.
12731      * 5-4-02: Let's try being more lenient and allowing a game to
12732      * start with an unnumbered move.  Does that break anything?
12733      */
12734     cm = lastLoadGameStart = EndOfFile;
12735     while (gn > 0) {
12736         yyboardindex = forwardMostMove;
12737         cm = (ChessMove) Myylex();
12738         switch (cm) {
12739           case EndOfFile:
12740             if (cmailMsgLoaded) {
12741                 nCmailGames = CMAIL_MAX_GAMES - gn;
12742             } else {
12743                 Reset(TRUE, TRUE);
12744                 DisplayError(_("Game not found in file"), 0);
12745             }
12746             return FALSE;
12747
12748           case GNUChessGame:
12749           case XBoardGame:
12750             gn--;
12751             lastLoadGameStart = cm;
12752             break;
12753
12754           case MoveNumberOne:
12755             switch (lastLoadGameStart) {
12756               case GNUChessGame:
12757               case XBoardGame:
12758               case PGNTag:
12759                 break;
12760               case MoveNumberOne:
12761               case EndOfFile:
12762                 gn--;           /* count this game */
12763                 lastLoadGameStart = cm;
12764                 break;
12765               default:
12766                 /* impossible */
12767                 break;
12768             }
12769             break;
12770
12771           case PGNTag:
12772             switch (lastLoadGameStart) {
12773               case GNUChessGame:
12774               case PGNTag:
12775               case MoveNumberOne:
12776               case EndOfFile:
12777                 gn--;           /* count this game */
12778                 lastLoadGameStart = cm;
12779                 break;
12780               case XBoardGame:
12781                 lastLoadGameStart = cm; /* game counted already */
12782                 break;
12783               default:
12784                 /* impossible */
12785                 break;
12786             }
12787             if (gn > 0) {
12788                 do {
12789                     yyboardindex = forwardMostMove;
12790                     cm = (ChessMove) Myylex();
12791                 } while (cm == PGNTag || cm == Comment);
12792             }
12793             break;
12794
12795           case WhiteWins:
12796           case BlackWins:
12797           case GameIsDrawn:
12798             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12799                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12800                     != CMAIL_OLD_RESULT) {
12801                     nCmailResults ++ ;
12802                     cmailResult[  CMAIL_MAX_GAMES
12803                                 - gn - 1] = CMAIL_OLD_RESULT;
12804                 }
12805             }
12806             break;
12807
12808           case NormalMove:
12809           case FirstLeg:
12810             /* Only a NormalMove can be at the start of a game
12811              * without a position diagram. */
12812             if (lastLoadGameStart == EndOfFile ) {
12813               gn--;
12814               lastLoadGameStart = MoveNumberOne;
12815             }
12816             break;
12817
12818           default:
12819             break;
12820         }
12821     }
12822
12823     if (appData.debugMode)
12824       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12825
12826     if (cm == XBoardGame) {
12827         /* Skip any header junk before position diagram and/or move 1 */
12828         for (;;) {
12829             yyboardindex = forwardMostMove;
12830             cm = (ChessMove) Myylex();
12831
12832             if (cm == EndOfFile ||
12833                 cm == GNUChessGame || cm == XBoardGame) {
12834                 /* Empty game; pretend end-of-file and handle later */
12835                 cm = EndOfFile;
12836                 break;
12837             }
12838
12839             if (cm == MoveNumberOne || cm == PositionDiagram ||
12840                 cm == PGNTag || cm == Comment)
12841               break;
12842         }
12843     } else if (cm == GNUChessGame) {
12844         if (gameInfo.event != NULL) {
12845             free(gameInfo.event);
12846         }
12847         gameInfo.event = StrSave(yy_text);
12848     }
12849
12850     startedFromSetupPosition = FALSE;
12851     while (cm == PGNTag) {
12852         if (appData.debugMode)
12853           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12854         err = ParsePGNTag(yy_text, &gameInfo);
12855         if (!err) numPGNTags++;
12856
12857         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12858         if(gameInfo.variant != oldVariant) {
12859             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12860             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12861             InitPosition(TRUE);
12862             oldVariant = gameInfo.variant;
12863             if (appData.debugMode)
12864               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12865         }
12866
12867
12868         if (gameInfo.fen != NULL) {
12869           Board initial_position;
12870           startedFromSetupPosition = TRUE;
12871           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12872             Reset(TRUE, TRUE);
12873             DisplayError(_("Bad FEN position in file"), 0);
12874             return FALSE;
12875           }
12876           CopyBoard(boards[0], initial_position);
12877           if (blackPlaysFirst) {
12878             currentMove = forwardMostMove = backwardMostMove = 1;
12879             CopyBoard(boards[1], initial_position);
12880             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12881             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12882             timeRemaining[0][1] = whiteTimeRemaining;
12883             timeRemaining[1][1] = blackTimeRemaining;
12884             if (commentList[0] != NULL) {
12885               commentList[1] = commentList[0];
12886               commentList[0] = NULL;
12887             }
12888           } else {
12889             currentMove = forwardMostMove = backwardMostMove = 0;
12890           }
12891           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12892           {   int i;
12893               initialRulePlies = FENrulePlies;
12894               for( i=0; i< nrCastlingRights; i++ )
12895                   initialRights[i] = initial_position[CASTLING][i];
12896           }
12897           yyboardindex = forwardMostMove;
12898           free(gameInfo.fen);
12899           gameInfo.fen = NULL;
12900         }
12901
12902         yyboardindex = forwardMostMove;
12903         cm = (ChessMove) Myylex();
12904
12905         /* Handle comments interspersed among the tags */
12906         while (cm == Comment) {
12907             char *p;
12908             if (appData.debugMode)
12909               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12910             p = yy_text;
12911             AppendComment(currentMove, p, FALSE);
12912             yyboardindex = forwardMostMove;
12913             cm = (ChessMove) Myylex();
12914         }
12915     }
12916
12917     /* don't rely on existence of Event tag since if game was
12918      * pasted from clipboard the Event tag may not exist
12919      */
12920     if (numPGNTags > 0){
12921         char *tags;
12922         if (gameInfo.variant == VariantNormal) {
12923           VariantClass v = StringToVariant(gameInfo.event);
12924           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12925           if(v < VariantShogi) gameInfo.variant = v;
12926         }
12927         if (!matchMode) {
12928           if( appData.autoDisplayTags ) {
12929             tags = PGNTags(&gameInfo);
12930             TagsPopUp(tags, CmailMsg());
12931             free(tags);
12932           }
12933         }
12934     } else {
12935         /* Make something up, but don't display it now */
12936         SetGameInfo();
12937         TagsPopDown();
12938     }
12939
12940     if (cm == PositionDiagram) {
12941         int i, j;
12942         char *p;
12943         Board initial_position;
12944
12945         if (appData.debugMode)
12946           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12947
12948         if (!startedFromSetupPosition) {
12949             p = yy_text;
12950             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12951               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12952                 switch (*p) {
12953                   case '{':
12954                   case '[':
12955                   case '-':
12956                   case ' ':
12957                   case '\t':
12958                   case '\n':
12959                   case '\r':
12960                     break;
12961                   default:
12962                     initial_position[i][j++] = CharToPiece(*p);
12963                     break;
12964                 }
12965             while (*p == ' ' || *p == '\t' ||
12966                    *p == '\n' || *p == '\r') p++;
12967
12968             if (strncmp(p, "black", strlen("black"))==0)
12969               blackPlaysFirst = TRUE;
12970             else
12971               blackPlaysFirst = FALSE;
12972             startedFromSetupPosition = TRUE;
12973
12974             CopyBoard(boards[0], initial_position);
12975             if (blackPlaysFirst) {
12976                 currentMove = forwardMostMove = backwardMostMove = 1;
12977                 CopyBoard(boards[1], initial_position);
12978                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12979                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12980                 timeRemaining[0][1] = whiteTimeRemaining;
12981                 timeRemaining[1][1] = blackTimeRemaining;
12982                 if (commentList[0] != NULL) {
12983                     commentList[1] = commentList[0];
12984                     commentList[0] = NULL;
12985                 }
12986             } else {
12987                 currentMove = forwardMostMove = backwardMostMove = 0;
12988             }
12989         }
12990         yyboardindex = forwardMostMove;
12991         cm = (ChessMove) Myylex();
12992     }
12993
12994   if(!creatingBook) {
12995     if (first.pr == NoProc) {
12996         StartChessProgram(&first);
12997     }
12998     InitChessProgram(&first, FALSE);
12999     SendToProgram("force\n", &first);
13000     if (startedFromSetupPosition) {
13001         SendBoard(&first, forwardMostMove);
13002     if (appData.debugMode) {
13003         fprintf(debugFP, "Load Game\n");
13004     }
13005         DisplayBothClocks();
13006     }
13007   }
13008
13009     /* [HGM] server: flag to write setup moves in broadcast file as one */
13010     loadFlag = appData.suppressLoadMoves;
13011
13012     while (cm == Comment) {
13013         char *p;
13014         if (appData.debugMode)
13015           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13016         p = yy_text;
13017         AppendComment(currentMove, p, FALSE);
13018         yyboardindex = forwardMostMove;
13019         cm = (ChessMove) Myylex();
13020     }
13021
13022     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13023         cm == WhiteWins || cm == BlackWins ||
13024         cm == GameIsDrawn || cm == GameUnfinished) {
13025         DisplayMessage("", _("No moves in game"));
13026         if (cmailMsgLoaded) {
13027             if (appData.debugMode)
13028               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13029             ClearHighlights();
13030             flipView = FALSE;
13031         }
13032         DrawPosition(FALSE, boards[currentMove]);
13033         DisplayBothClocks();
13034         gameMode = EditGame;
13035         ModeHighlight();
13036         gameFileFP = NULL;
13037         cmailOldMove = 0;
13038         return TRUE;
13039     }
13040
13041     // [HGM] PV info: routine tests if comment empty
13042     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13043         DisplayComment(currentMove - 1, commentList[currentMove]);
13044     }
13045     if (!matchMode && appData.timeDelay != 0)
13046       DrawPosition(FALSE, boards[currentMove]);
13047
13048     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13049       programStats.ok_to_send = 1;
13050     }
13051
13052     /* if the first token after the PGN tags is a move
13053      * and not move number 1, retrieve it from the parser
13054      */
13055     if (cm != MoveNumberOne)
13056         LoadGameOneMove(cm);
13057
13058     /* load the remaining moves from the file */
13059     while (LoadGameOneMove(EndOfFile)) {
13060       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13061       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13062     }
13063
13064     /* rewind to the start of the game */
13065     currentMove = backwardMostMove;
13066
13067     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13068
13069     if (oldGameMode == AnalyzeFile) {
13070       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13071       AnalyzeFileEvent();
13072     } else
13073     if (oldGameMode == AnalyzeMode) {
13074       AnalyzeFileEvent();
13075     }
13076
13077     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13078         long int w, b; // [HGM] adjourn: restore saved clock times
13079         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13080         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13081             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13082             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13083         }
13084     }
13085
13086     if(creatingBook) return TRUE;
13087     if (!matchMode && pos > 0) {
13088         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13089     } else
13090     if (matchMode || appData.timeDelay == 0) {
13091       ToEndEvent();
13092     } else if (appData.timeDelay > 0) {
13093       AutoPlayGameLoop();
13094     }
13095
13096     if (appData.debugMode)
13097         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13098
13099     loadFlag = 0; /* [HGM] true game starts */
13100     return TRUE;
13101 }
13102
13103 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13104 int
13105 ReloadPosition (int offset)
13106 {
13107     int positionNumber = lastLoadPositionNumber + offset;
13108     if (lastLoadPositionFP == NULL) {
13109         DisplayError(_("No position has been loaded yet"), 0);
13110         return FALSE;
13111     }
13112     if (positionNumber <= 0) {
13113         DisplayError(_("Can't back up any further"), 0);
13114         return FALSE;
13115     }
13116     return LoadPosition(lastLoadPositionFP, positionNumber,
13117                         lastLoadPositionTitle);
13118 }
13119
13120 /* Load the nth position from the given file */
13121 int
13122 LoadPositionFromFile (char *filename, int n, char *title)
13123 {
13124     FILE *f;
13125     char buf[MSG_SIZ];
13126
13127     if (strcmp(filename, "-") == 0) {
13128         return LoadPosition(stdin, n, "stdin");
13129     } else {
13130         f = fopen(filename, "rb");
13131         if (f == NULL) {
13132             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13133             DisplayError(buf, errno);
13134             return FALSE;
13135         } else {
13136             return LoadPosition(f, n, title);
13137         }
13138     }
13139 }
13140
13141 /* Load the nth position from the given open file, and close it */
13142 int
13143 LoadPosition (FILE *f, int positionNumber, char *title)
13144 {
13145     char *p, line[MSG_SIZ];
13146     Board initial_position;
13147     int i, j, fenMode, pn;
13148
13149     if (gameMode == Training )
13150         SetTrainingModeOff();
13151
13152     if (gameMode != BeginningOfGame) {
13153         Reset(FALSE, TRUE);
13154     }
13155     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13156         fclose(lastLoadPositionFP);
13157     }
13158     if (positionNumber == 0) positionNumber = 1;
13159     lastLoadPositionFP = f;
13160     lastLoadPositionNumber = positionNumber;
13161     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13162     if (first.pr == NoProc && !appData.noChessProgram) {
13163       StartChessProgram(&first);
13164       InitChessProgram(&first, FALSE);
13165     }
13166     pn = positionNumber;
13167     if (positionNumber < 0) {
13168         /* Negative position number means to seek to that byte offset */
13169         if (fseek(f, -positionNumber, 0) == -1) {
13170             DisplayError(_("Can't seek on position file"), 0);
13171             return FALSE;
13172         };
13173         pn = 1;
13174     } else {
13175         if (fseek(f, 0, 0) == -1) {
13176             if (f == lastLoadPositionFP ?
13177                 positionNumber == lastLoadPositionNumber + 1 :
13178                 positionNumber == 1) {
13179                 pn = 1;
13180             } else {
13181                 DisplayError(_("Can't seek on position file"), 0);
13182                 return FALSE;
13183             }
13184         }
13185     }
13186     /* See if this file is FEN or old-style xboard */
13187     if (fgets(line, MSG_SIZ, f) == NULL) {
13188         DisplayError(_("Position not found in file"), 0);
13189         return FALSE;
13190     }
13191     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13192     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13193
13194     if (pn >= 2) {
13195         if (fenMode || line[0] == '#') pn--;
13196         while (pn > 0) {
13197             /* skip positions before number pn */
13198             if (fgets(line, MSG_SIZ, f) == NULL) {
13199                 Reset(TRUE, TRUE);
13200                 DisplayError(_("Position not found in file"), 0);
13201                 return FALSE;
13202             }
13203             if (fenMode || line[0] == '#') pn--;
13204         }
13205     }
13206
13207     if (fenMode) {
13208         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13209             DisplayError(_("Bad FEN position in file"), 0);
13210             return FALSE;
13211         }
13212     } else {
13213         (void) fgets(line, MSG_SIZ, f);
13214         (void) fgets(line, MSG_SIZ, f);
13215
13216         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13217             (void) fgets(line, MSG_SIZ, f);
13218             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13219                 if (*p == ' ')
13220                   continue;
13221                 initial_position[i][j++] = CharToPiece(*p);
13222             }
13223         }
13224
13225         blackPlaysFirst = FALSE;
13226         if (!feof(f)) {
13227             (void) fgets(line, MSG_SIZ, f);
13228             if (strncmp(line, "black", strlen("black"))==0)
13229               blackPlaysFirst = TRUE;
13230         }
13231     }
13232     startedFromSetupPosition = TRUE;
13233
13234     CopyBoard(boards[0], initial_position);
13235     if (blackPlaysFirst) {
13236         currentMove = forwardMostMove = backwardMostMove = 1;
13237         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13238         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13239         CopyBoard(boards[1], initial_position);
13240         DisplayMessage("", _("Black to play"));
13241     } else {
13242         currentMove = forwardMostMove = backwardMostMove = 0;
13243         DisplayMessage("", _("White to play"));
13244     }
13245     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13246     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13247         SendToProgram("force\n", &first);
13248         SendBoard(&first, forwardMostMove);
13249     }
13250     if (appData.debugMode) {
13251 int i, j;
13252   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13253   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13254         fprintf(debugFP, "Load Position\n");
13255     }
13256
13257     if (positionNumber > 1) {
13258       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13259         DisplayTitle(line);
13260     } else {
13261         DisplayTitle(title);
13262     }
13263     gameMode = EditGame;
13264     ModeHighlight();
13265     ResetClocks();
13266     timeRemaining[0][1] = whiteTimeRemaining;
13267     timeRemaining[1][1] = blackTimeRemaining;
13268     DrawPosition(FALSE, boards[currentMove]);
13269
13270     return TRUE;
13271 }
13272
13273
13274 void
13275 CopyPlayerNameIntoFileName (char **dest, char *src)
13276 {
13277     while (*src != NULLCHAR && *src != ',') {
13278         if (*src == ' ') {
13279             *(*dest)++ = '_';
13280             src++;
13281         } else {
13282             *(*dest)++ = *src++;
13283         }
13284     }
13285 }
13286
13287 char *
13288 DefaultFileName (char *ext)
13289 {
13290     static char def[MSG_SIZ];
13291     char *p;
13292
13293     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13294         p = def;
13295         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13296         *p++ = '-';
13297         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13298         *p++ = '.';
13299         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13300     } else {
13301         def[0] = NULLCHAR;
13302     }
13303     return def;
13304 }
13305
13306 /* Save the current game to the given file */
13307 int
13308 SaveGameToFile (char *filename, int append)
13309 {
13310     FILE *f;
13311     char buf[MSG_SIZ];
13312     int result, i, t,tot=0;
13313
13314     if (strcmp(filename, "-") == 0) {
13315         return SaveGame(stdout, 0, NULL);
13316     } else {
13317         for(i=0; i<10; i++) { // upto 10 tries
13318              f = fopen(filename, append ? "a" : "w");
13319              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13320              if(f || errno != 13) break;
13321              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13322              tot += t;
13323         }
13324         if (f == NULL) {
13325             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13326             DisplayError(buf, errno);
13327             return FALSE;
13328         } else {
13329             safeStrCpy(buf, lastMsg, MSG_SIZ);
13330             DisplayMessage(_("Waiting for access to save file"), "");
13331             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13332             DisplayMessage(_("Saving game"), "");
13333             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13334             result = SaveGame(f, 0, NULL);
13335             DisplayMessage(buf, "");
13336             return result;
13337         }
13338     }
13339 }
13340
13341 char *
13342 SavePart (char *str)
13343 {
13344     static char buf[MSG_SIZ];
13345     char *p;
13346
13347     p = strchr(str, ' ');
13348     if (p == NULL) return str;
13349     strncpy(buf, str, p - str);
13350     buf[p - str] = NULLCHAR;
13351     return buf;
13352 }
13353
13354 #define PGN_MAX_LINE 75
13355
13356 #define PGN_SIDE_WHITE  0
13357 #define PGN_SIDE_BLACK  1
13358
13359 static int
13360 FindFirstMoveOutOfBook (int side)
13361 {
13362     int result = -1;
13363
13364     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13365         int index = backwardMostMove;
13366         int has_book_hit = 0;
13367
13368         if( (index % 2) != side ) {
13369             index++;
13370         }
13371
13372         while( index < forwardMostMove ) {
13373             /* Check to see if engine is in book */
13374             int depth = pvInfoList[index].depth;
13375             int score = pvInfoList[index].score;
13376             int in_book = 0;
13377
13378             if( depth <= 2 ) {
13379                 in_book = 1;
13380             }
13381             else if( score == 0 && depth == 63 ) {
13382                 in_book = 1; /* Zappa */
13383             }
13384             else if( score == 2 && depth == 99 ) {
13385                 in_book = 1; /* Abrok */
13386             }
13387
13388             has_book_hit += in_book;
13389
13390             if( ! in_book ) {
13391                 result = index;
13392
13393                 break;
13394             }
13395
13396             index += 2;
13397         }
13398     }
13399
13400     return result;
13401 }
13402
13403 void
13404 GetOutOfBookInfo (char * buf)
13405 {
13406     int oob[2];
13407     int i;
13408     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13409
13410     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13411     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13412
13413     *buf = '\0';
13414
13415     if( oob[0] >= 0 || oob[1] >= 0 ) {
13416         for( i=0; i<2; i++ ) {
13417             int idx = oob[i];
13418
13419             if( idx >= 0 ) {
13420                 if( i > 0 && oob[0] >= 0 ) {
13421                     strcat( buf, "   " );
13422                 }
13423
13424                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13425                 sprintf( buf+strlen(buf), "%s%.2f",
13426                     pvInfoList[idx].score >= 0 ? "+" : "",
13427                     pvInfoList[idx].score / 100.0 );
13428             }
13429         }
13430     }
13431 }
13432
13433 /* Save game in PGN style */
13434 static void
13435 SaveGamePGN2 (FILE *f)
13436 {
13437     int i, offset, linelen, newblock;
13438 //    char *movetext;
13439     char numtext[32];
13440     int movelen, numlen, blank;
13441     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13442
13443     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13444
13445     PrintPGNTags(f, &gameInfo);
13446
13447     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13448
13449     if (backwardMostMove > 0 || startedFromSetupPosition) {
13450         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13451         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13452         fprintf(f, "\n{--------------\n");
13453         PrintPosition(f, backwardMostMove);
13454         fprintf(f, "--------------}\n");
13455         free(fen);
13456     }
13457     else {
13458         /* [AS] Out of book annotation */
13459         if( appData.saveOutOfBookInfo ) {
13460             char buf[64];
13461
13462             GetOutOfBookInfo( buf );
13463
13464             if( buf[0] != '\0' ) {
13465                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13466             }
13467         }
13468
13469         fprintf(f, "\n");
13470     }
13471
13472     i = backwardMostMove;
13473     linelen = 0;
13474     newblock = TRUE;
13475
13476     while (i < forwardMostMove) {
13477         /* Print comments preceding this move */
13478         if (commentList[i] != NULL) {
13479             if (linelen > 0) fprintf(f, "\n");
13480             fprintf(f, "%s", commentList[i]);
13481             linelen = 0;
13482             newblock = TRUE;
13483         }
13484
13485         /* Format move number */
13486         if ((i % 2) == 0)
13487           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13488         else
13489           if (newblock)
13490             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13491           else
13492             numtext[0] = NULLCHAR;
13493
13494         numlen = strlen(numtext);
13495         newblock = FALSE;
13496
13497         /* Print move number */
13498         blank = linelen > 0 && numlen > 0;
13499         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13500             fprintf(f, "\n");
13501             linelen = 0;
13502             blank = 0;
13503         }
13504         if (blank) {
13505             fprintf(f, " ");
13506             linelen++;
13507         }
13508         fprintf(f, "%s", numtext);
13509         linelen += numlen;
13510
13511         /* Get move */
13512         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13513         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13514
13515         /* Print move */
13516         blank = linelen > 0 && movelen > 0;
13517         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13518             fprintf(f, "\n");
13519             linelen = 0;
13520             blank = 0;
13521         }
13522         if (blank) {
13523             fprintf(f, " ");
13524             linelen++;
13525         }
13526         fprintf(f, "%s", move_buffer);
13527         linelen += movelen;
13528
13529         /* [AS] Add PV info if present */
13530         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13531             /* [HGM] add time */
13532             char buf[MSG_SIZ]; int seconds;
13533
13534             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13535
13536             if( seconds <= 0)
13537               buf[0] = 0;
13538             else
13539               if( seconds < 30 )
13540                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13541               else
13542                 {
13543                   seconds = (seconds + 4)/10; // round to full seconds
13544                   if( seconds < 60 )
13545                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13546                   else
13547                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13548                 }
13549
13550             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13551                       pvInfoList[i].score >= 0 ? "+" : "",
13552                       pvInfoList[i].score / 100.0,
13553                       pvInfoList[i].depth,
13554                       buf );
13555
13556             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13557
13558             /* Print score/depth */
13559             blank = linelen > 0 && movelen > 0;
13560             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13561                 fprintf(f, "\n");
13562                 linelen = 0;
13563                 blank = 0;
13564             }
13565             if (blank) {
13566                 fprintf(f, " ");
13567                 linelen++;
13568             }
13569             fprintf(f, "%s", move_buffer);
13570             linelen += movelen;
13571         }
13572
13573         i++;
13574     }
13575
13576     /* Start a new line */
13577     if (linelen > 0) fprintf(f, "\n");
13578
13579     /* Print comments after last move */
13580     if (commentList[i] != NULL) {
13581         fprintf(f, "%s\n", commentList[i]);
13582     }
13583
13584     /* Print result */
13585     if (gameInfo.resultDetails != NULL &&
13586         gameInfo.resultDetails[0] != NULLCHAR) {
13587         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13588         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13589            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13590             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13591         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13592     } else {
13593         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13594     }
13595 }
13596
13597 /* Save game in PGN style and close the file */
13598 int
13599 SaveGamePGN (FILE *f)
13600 {
13601     SaveGamePGN2(f);
13602     fclose(f);
13603     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13604     return TRUE;
13605 }
13606
13607 /* Save game in old style and close the file */
13608 int
13609 SaveGameOldStyle (FILE *f)
13610 {
13611     int i, offset;
13612     time_t tm;
13613
13614     tm = time((time_t *) NULL);
13615
13616     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13617     PrintOpponents(f);
13618
13619     if (backwardMostMove > 0 || startedFromSetupPosition) {
13620         fprintf(f, "\n[--------------\n");
13621         PrintPosition(f, backwardMostMove);
13622         fprintf(f, "--------------]\n");
13623     } else {
13624         fprintf(f, "\n");
13625     }
13626
13627     i = backwardMostMove;
13628     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13629
13630     while (i < forwardMostMove) {
13631         if (commentList[i] != NULL) {
13632             fprintf(f, "[%s]\n", commentList[i]);
13633         }
13634
13635         if ((i % 2) == 1) {
13636             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13637             i++;
13638         } else {
13639             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13640             i++;
13641             if (commentList[i] != NULL) {
13642                 fprintf(f, "\n");
13643                 continue;
13644             }
13645             if (i >= forwardMostMove) {
13646                 fprintf(f, "\n");
13647                 break;
13648             }
13649             fprintf(f, "%s\n", parseList[i]);
13650             i++;
13651         }
13652     }
13653
13654     if (commentList[i] != NULL) {
13655         fprintf(f, "[%s]\n", commentList[i]);
13656     }
13657
13658     /* This isn't really the old style, but it's close enough */
13659     if (gameInfo.resultDetails != NULL &&
13660         gameInfo.resultDetails[0] != NULLCHAR) {
13661         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13662                 gameInfo.resultDetails);
13663     } else {
13664         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13665     }
13666
13667     fclose(f);
13668     return TRUE;
13669 }
13670
13671 /* Save the current game to open file f and close the file */
13672 int
13673 SaveGame (FILE *f, int dummy, char *dummy2)
13674 {
13675     if (gameMode == EditPosition) EditPositionDone(TRUE);
13676     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13677     if (appData.oldSaveStyle)
13678       return SaveGameOldStyle(f);
13679     else
13680       return SaveGamePGN(f);
13681 }
13682
13683 /* Save the current position to the given file */
13684 int
13685 SavePositionToFile (char *filename)
13686 {
13687     FILE *f;
13688     char buf[MSG_SIZ];
13689
13690     if (strcmp(filename, "-") == 0) {
13691         return SavePosition(stdout, 0, NULL);
13692     } else {
13693         f = fopen(filename, "a");
13694         if (f == NULL) {
13695             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13696             DisplayError(buf, errno);
13697             return FALSE;
13698         } else {
13699             safeStrCpy(buf, lastMsg, MSG_SIZ);
13700             DisplayMessage(_("Waiting for access to save file"), "");
13701             flock(fileno(f), LOCK_EX); // [HGM] lock
13702             DisplayMessage(_("Saving position"), "");
13703             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13704             SavePosition(f, 0, NULL);
13705             DisplayMessage(buf, "");
13706             return TRUE;
13707         }
13708     }
13709 }
13710
13711 /* Save the current position to the given open file and close the file */
13712 int
13713 SavePosition (FILE *f, int dummy, char *dummy2)
13714 {
13715     time_t tm;
13716     char *fen;
13717
13718     if (gameMode == EditPosition) EditPositionDone(TRUE);
13719     if (appData.oldSaveStyle) {
13720         tm = time((time_t *) NULL);
13721
13722         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13723         PrintOpponents(f);
13724         fprintf(f, "[--------------\n");
13725         PrintPosition(f, currentMove);
13726         fprintf(f, "--------------]\n");
13727     } else {
13728         fen = PositionToFEN(currentMove, NULL, 1);
13729         fprintf(f, "%s\n", fen);
13730         free(fen);
13731     }
13732     fclose(f);
13733     return TRUE;
13734 }
13735
13736 void
13737 ReloadCmailMsgEvent (int unregister)
13738 {
13739 #if !WIN32
13740     static char *inFilename = NULL;
13741     static char *outFilename;
13742     int i;
13743     struct stat inbuf, outbuf;
13744     int status;
13745
13746     /* Any registered moves are unregistered if unregister is set, */
13747     /* i.e. invoked by the signal handler */
13748     if (unregister) {
13749         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13750             cmailMoveRegistered[i] = FALSE;
13751             if (cmailCommentList[i] != NULL) {
13752                 free(cmailCommentList[i]);
13753                 cmailCommentList[i] = NULL;
13754             }
13755         }
13756         nCmailMovesRegistered = 0;
13757     }
13758
13759     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13760         cmailResult[i] = CMAIL_NOT_RESULT;
13761     }
13762     nCmailResults = 0;
13763
13764     if (inFilename == NULL) {
13765         /* Because the filenames are static they only get malloced once  */
13766         /* and they never get freed                                      */
13767         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13768         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13769
13770         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13771         sprintf(outFilename, "%s.out", appData.cmailGameName);
13772     }
13773
13774     status = stat(outFilename, &outbuf);
13775     if (status < 0) {
13776         cmailMailedMove = FALSE;
13777     } else {
13778         status = stat(inFilename, &inbuf);
13779         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13780     }
13781
13782     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13783        counts the games, notes how each one terminated, etc.
13784
13785        It would be nice to remove this kludge and instead gather all
13786        the information while building the game list.  (And to keep it
13787        in the game list nodes instead of having a bunch of fixed-size
13788        parallel arrays.)  Note this will require getting each game's
13789        termination from the PGN tags, as the game list builder does
13790        not process the game moves.  --mann
13791        */
13792     cmailMsgLoaded = TRUE;
13793     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13794
13795     /* Load first game in the file or popup game menu */
13796     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13797
13798 #endif /* !WIN32 */
13799     return;
13800 }
13801
13802 int
13803 RegisterMove ()
13804 {
13805     FILE *f;
13806     char string[MSG_SIZ];
13807
13808     if (   cmailMailedMove
13809         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13810         return TRUE;            /* Allow free viewing  */
13811     }
13812
13813     /* Unregister move to ensure that we don't leave RegisterMove        */
13814     /* with the move registered when the conditions for registering no   */
13815     /* longer hold                                                       */
13816     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13817         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13818         nCmailMovesRegistered --;
13819
13820         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13821           {
13822               free(cmailCommentList[lastLoadGameNumber - 1]);
13823               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13824           }
13825     }
13826
13827     if (cmailOldMove == -1) {
13828         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13829         return FALSE;
13830     }
13831
13832     if (currentMove > cmailOldMove + 1) {
13833         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13834         return FALSE;
13835     }
13836
13837     if (currentMove < cmailOldMove) {
13838         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13839         return FALSE;
13840     }
13841
13842     if (forwardMostMove > currentMove) {
13843         /* Silently truncate extra moves */
13844         TruncateGame();
13845     }
13846
13847     if (   (currentMove == cmailOldMove + 1)
13848         || (   (currentMove == cmailOldMove)
13849             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13850                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13851         if (gameInfo.result != GameUnfinished) {
13852             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13853         }
13854
13855         if (commentList[currentMove] != NULL) {
13856             cmailCommentList[lastLoadGameNumber - 1]
13857               = StrSave(commentList[currentMove]);
13858         }
13859         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13860
13861         if (appData.debugMode)
13862           fprintf(debugFP, "Saving %s for game %d\n",
13863                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13864
13865         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13866
13867         f = fopen(string, "w");
13868         if (appData.oldSaveStyle) {
13869             SaveGameOldStyle(f); /* also closes the file */
13870
13871             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13872             f = fopen(string, "w");
13873             SavePosition(f, 0, NULL); /* also closes the file */
13874         } else {
13875             fprintf(f, "{--------------\n");
13876             PrintPosition(f, currentMove);
13877             fprintf(f, "--------------}\n\n");
13878
13879             SaveGame(f, 0, NULL); /* also closes the file*/
13880         }
13881
13882         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13883         nCmailMovesRegistered ++;
13884     } else if (nCmailGames == 1) {
13885         DisplayError(_("You have not made a move yet"), 0);
13886         return FALSE;
13887     }
13888
13889     return TRUE;
13890 }
13891
13892 void
13893 MailMoveEvent ()
13894 {
13895 #if !WIN32
13896     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13897     FILE *commandOutput;
13898     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13899     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13900     int nBuffers;
13901     int i;
13902     int archived;
13903     char *arcDir;
13904
13905     if (! cmailMsgLoaded) {
13906         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13907         return;
13908     }
13909
13910     if (nCmailGames == nCmailResults) {
13911         DisplayError(_("No unfinished games"), 0);
13912         return;
13913     }
13914
13915 #if CMAIL_PROHIBIT_REMAIL
13916     if (cmailMailedMove) {
13917       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);
13918         DisplayError(msg, 0);
13919         return;
13920     }
13921 #endif
13922
13923     if (! (cmailMailedMove || RegisterMove())) return;
13924
13925     if (   cmailMailedMove
13926         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13927       snprintf(string, MSG_SIZ, partCommandString,
13928                appData.debugMode ? " -v" : "", appData.cmailGameName);
13929         commandOutput = popen(string, "r");
13930
13931         if (commandOutput == NULL) {
13932             DisplayError(_("Failed to invoke cmail"), 0);
13933         } else {
13934             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13935                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13936             }
13937             if (nBuffers > 1) {
13938                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13939                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13940                 nBytes = MSG_SIZ - 1;
13941             } else {
13942                 (void) memcpy(msg, buffer, nBytes);
13943             }
13944             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13945
13946             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13947                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13948
13949                 archived = TRUE;
13950                 for (i = 0; i < nCmailGames; i ++) {
13951                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13952                         archived = FALSE;
13953                     }
13954                 }
13955                 if (   archived
13956                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13957                         != NULL)) {
13958                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13959                            arcDir,
13960                            appData.cmailGameName,
13961                            gameInfo.date);
13962                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13963                     cmailMsgLoaded = FALSE;
13964                 }
13965             }
13966
13967             DisplayInformation(msg);
13968             pclose(commandOutput);
13969         }
13970     } else {
13971         if ((*cmailMsg) != '\0') {
13972             DisplayInformation(cmailMsg);
13973         }
13974     }
13975
13976     return;
13977 #endif /* !WIN32 */
13978 }
13979
13980 char *
13981 CmailMsg ()
13982 {
13983 #if WIN32
13984     return NULL;
13985 #else
13986     int  prependComma = 0;
13987     char number[5];
13988     char string[MSG_SIZ];       /* Space for game-list */
13989     int  i;
13990
13991     if (!cmailMsgLoaded) return "";
13992
13993     if (cmailMailedMove) {
13994       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13995     } else {
13996         /* Create a list of games left */
13997       snprintf(string, MSG_SIZ, "[");
13998         for (i = 0; i < nCmailGames; i ++) {
13999             if (! (   cmailMoveRegistered[i]
14000                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14001                 if (prependComma) {
14002                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14003                 } else {
14004                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14005                     prependComma = 1;
14006                 }
14007
14008                 strcat(string, number);
14009             }
14010         }
14011         strcat(string, "]");
14012
14013         if (nCmailMovesRegistered + nCmailResults == 0) {
14014             switch (nCmailGames) {
14015               case 1:
14016                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14017                 break;
14018
14019               case 2:
14020                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14021                 break;
14022
14023               default:
14024                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14025                          nCmailGames);
14026                 break;
14027             }
14028         } else {
14029             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14030               case 1:
14031                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14032                          string);
14033                 break;
14034
14035               case 0:
14036                 if (nCmailResults == nCmailGames) {
14037                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14038                 } else {
14039                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14040                 }
14041                 break;
14042
14043               default:
14044                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14045                          string);
14046             }
14047         }
14048     }
14049     return cmailMsg;
14050 #endif /* WIN32 */
14051 }
14052
14053 void
14054 ResetGameEvent ()
14055 {
14056     if (gameMode == Training)
14057       SetTrainingModeOff();
14058
14059     Reset(TRUE, TRUE);
14060     cmailMsgLoaded = FALSE;
14061     if (appData.icsActive) {
14062       SendToICS(ics_prefix);
14063       SendToICS("refresh\n");
14064     }
14065 }
14066
14067 void
14068 ExitEvent (int status)
14069 {
14070     exiting++;
14071     if (exiting > 2) {
14072       /* Give up on clean exit */
14073       exit(status);
14074     }
14075     if (exiting > 1) {
14076       /* Keep trying for clean exit */
14077       return;
14078     }
14079
14080     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14081     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14082
14083     if (telnetISR != NULL) {
14084       RemoveInputSource(telnetISR);
14085     }
14086     if (icsPR != NoProc) {
14087       DestroyChildProcess(icsPR, TRUE);
14088     }
14089
14090     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14091     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14092
14093     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14094     /* make sure this other one finishes before killing it!                  */
14095     if(endingGame) { int count = 0;
14096         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14097         while(endingGame && count++ < 10) DoSleep(1);
14098         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14099     }
14100
14101     /* Kill off chess programs */
14102     if (first.pr != NoProc) {
14103         ExitAnalyzeMode();
14104
14105         DoSleep( appData.delayBeforeQuit );
14106         SendToProgram("quit\n", &first);
14107         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14108     }
14109     if (second.pr != NoProc) {
14110         DoSleep( appData.delayBeforeQuit );
14111         SendToProgram("quit\n", &second);
14112         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14113     }
14114     if (first.isr != NULL) {
14115         RemoveInputSource(first.isr);
14116     }
14117     if (second.isr != NULL) {
14118         RemoveInputSource(second.isr);
14119     }
14120
14121     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14122     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14123
14124     ShutDownFrontEnd();
14125     exit(status);
14126 }
14127
14128 void
14129 PauseEngine (ChessProgramState *cps)
14130 {
14131     SendToProgram("pause\n", cps);
14132     cps->pause = 2;
14133 }
14134
14135 void
14136 UnPauseEngine (ChessProgramState *cps)
14137 {
14138     SendToProgram("resume\n", cps);
14139     cps->pause = 1;
14140 }
14141
14142 void
14143 PauseEvent ()
14144 {
14145     if (appData.debugMode)
14146         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14147     if (pausing) {
14148         pausing = FALSE;
14149         ModeHighlight();
14150         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14151             StartClocks();
14152             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14153                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14154                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14155             }
14156             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14157             HandleMachineMove(stashedInputMove, stalledEngine);
14158             stalledEngine = NULL;
14159             return;
14160         }
14161         if (gameMode == MachinePlaysWhite ||
14162             gameMode == TwoMachinesPlay   ||
14163             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14164             if(first.pause)  UnPauseEngine(&first);
14165             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14166             if(second.pause) UnPauseEngine(&second);
14167             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14168             StartClocks();
14169         } else {
14170             DisplayBothClocks();
14171         }
14172         if (gameMode == PlayFromGameFile) {
14173             if (appData.timeDelay >= 0)
14174                 AutoPlayGameLoop();
14175         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14176             Reset(FALSE, TRUE);
14177             SendToICS(ics_prefix);
14178             SendToICS("refresh\n");
14179         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14180             ForwardInner(forwardMostMove);
14181         }
14182         pauseExamInvalid = FALSE;
14183     } else {
14184         switch (gameMode) {
14185           default:
14186             return;
14187           case IcsExamining:
14188             pauseExamForwardMostMove = forwardMostMove;
14189             pauseExamInvalid = FALSE;
14190             /* fall through */
14191           case IcsObserving:
14192           case IcsPlayingWhite:
14193           case IcsPlayingBlack:
14194             pausing = TRUE;
14195             ModeHighlight();
14196             return;
14197           case PlayFromGameFile:
14198             (void) StopLoadGameTimer();
14199             pausing = TRUE;
14200             ModeHighlight();
14201             break;
14202           case BeginningOfGame:
14203             if (appData.icsActive) return;
14204             /* else fall through */
14205           case MachinePlaysWhite:
14206           case MachinePlaysBlack:
14207           case TwoMachinesPlay:
14208             if (forwardMostMove == 0)
14209               return;           /* don't pause if no one has moved */
14210             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14211                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14212                 if(onMove->pause) {           // thinking engine can be paused
14213                     PauseEngine(onMove);      // do it
14214                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14215                         PauseEngine(onMove->other);
14216                     else
14217                         SendToProgram("easy\n", onMove->other);
14218                     StopClocks();
14219                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14220             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14221                 if(first.pause) {
14222                     PauseEngine(&first);
14223                     StopClocks();
14224                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14225             } else { // human on move, pause pondering by either method
14226                 if(first.pause)
14227                     PauseEngine(&first);
14228                 else if(appData.ponderNextMove)
14229                     SendToProgram("easy\n", &first);
14230                 StopClocks();
14231             }
14232             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14233           case AnalyzeMode:
14234             pausing = TRUE;
14235             ModeHighlight();
14236             break;
14237         }
14238     }
14239 }
14240
14241 void
14242 EditCommentEvent ()
14243 {
14244     char title[MSG_SIZ];
14245
14246     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14247       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14248     } else {
14249       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14250                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14251                parseList[currentMove - 1]);
14252     }
14253
14254     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14255 }
14256
14257
14258 void
14259 EditTagsEvent ()
14260 {
14261     char *tags = PGNTags(&gameInfo);
14262     bookUp = FALSE;
14263     EditTagsPopUp(tags, NULL);
14264     free(tags);
14265 }
14266
14267 void
14268 ToggleSecond ()
14269 {
14270   if(second.analyzing) {
14271     SendToProgram("exit\n", &second);
14272     second.analyzing = FALSE;
14273   } else {
14274     if (second.pr == NoProc) StartChessProgram(&second);
14275     InitChessProgram(&second, FALSE);
14276     FeedMovesToProgram(&second, currentMove);
14277
14278     SendToProgram("analyze\n", &second);
14279     second.analyzing = TRUE;
14280   }
14281 }
14282
14283 /* Toggle ShowThinking */
14284 void
14285 ToggleShowThinking()
14286 {
14287   appData.showThinking = !appData.showThinking;
14288   ShowThinkingEvent();
14289 }
14290
14291 int
14292 AnalyzeModeEvent ()
14293 {
14294     char buf[MSG_SIZ];
14295
14296     if (!first.analysisSupport) {
14297       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14298       DisplayError(buf, 0);
14299       return 0;
14300     }
14301     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14302     if (appData.icsActive) {
14303         if (gameMode != IcsObserving) {
14304           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14305             DisplayError(buf, 0);
14306             /* secure check */
14307             if (appData.icsEngineAnalyze) {
14308                 if (appData.debugMode)
14309                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14310                 ExitAnalyzeMode();
14311                 ModeHighlight();
14312             }
14313             return 0;
14314         }
14315         /* if enable, user wants to disable icsEngineAnalyze */
14316         if (appData.icsEngineAnalyze) {
14317                 ExitAnalyzeMode();
14318                 ModeHighlight();
14319                 return 0;
14320         }
14321         appData.icsEngineAnalyze = TRUE;
14322         if (appData.debugMode)
14323             fprintf(debugFP, "ICS engine analyze starting... \n");
14324     }
14325
14326     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14327     if (appData.noChessProgram || gameMode == AnalyzeMode)
14328       return 0;
14329
14330     if (gameMode != AnalyzeFile) {
14331         if (!appData.icsEngineAnalyze) {
14332                EditGameEvent();
14333                if (gameMode != EditGame) return 0;
14334         }
14335         if (!appData.showThinking) ToggleShowThinking();
14336         ResurrectChessProgram();
14337         SendToProgram("analyze\n", &first);
14338         first.analyzing = TRUE;
14339         /*first.maybeThinking = TRUE;*/
14340         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14341         EngineOutputPopUp();
14342     }
14343     if (!appData.icsEngineAnalyze) {
14344         gameMode = AnalyzeMode;
14345         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14346     }
14347     pausing = FALSE;
14348     ModeHighlight();
14349     SetGameInfo();
14350
14351     StartAnalysisClock();
14352     GetTimeMark(&lastNodeCountTime);
14353     lastNodeCount = 0;
14354     return 1;
14355 }
14356
14357 void
14358 AnalyzeFileEvent ()
14359 {
14360     if (appData.noChessProgram || gameMode == AnalyzeFile)
14361       return;
14362
14363     if (!first.analysisSupport) {
14364       char buf[MSG_SIZ];
14365       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14366       DisplayError(buf, 0);
14367       return;
14368     }
14369
14370     if (gameMode != AnalyzeMode) {
14371         keepInfo = 1; // mere annotating should not alter PGN tags
14372         EditGameEvent();
14373         keepInfo = 0;
14374         if (gameMode != EditGame) return;
14375         if (!appData.showThinking) ToggleShowThinking();
14376         ResurrectChessProgram();
14377         SendToProgram("analyze\n", &first);
14378         first.analyzing = TRUE;
14379         /*first.maybeThinking = TRUE;*/
14380         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14381         EngineOutputPopUp();
14382     }
14383     gameMode = AnalyzeFile;
14384     pausing = FALSE;
14385     ModeHighlight();
14386
14387     StartAnalysisClock();
14388     GetTimeMark(&lastNodeCountTime);
14389     lastNodeCount = 0;
14390     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14391     AnalysisPeriodicEvent(1);
14392 }
14393
14394 void
14395 MachineWhiteEvent ()
14396 {
14397     char buf[MSG_SIZ];
14398     char *bookHit = NULL;
14399
14400     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14401       return;
14402
14403
14404     if (gameMode == PlayFromGameFile ||
14405         gameMode == TwoMachinesPlay  ||
14406         gameMode == Training         ||
14407         gameMode == AnalyzeMode      ||
14408         gameMode == EndOfGame)
14409         EditGameEvent();
14410
14411     if (gameMode == EditPosition)
14412         EditPositionDone(TRUE);
14413
14414     if (!WhiteOnMove(currentMove)) {
14415         DisplayError(_("It is not White's turn"), 0);
14416         return;
14417     }
14418
14419     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14420       ExitAnalyzeMode();
14421
14422     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14423         gameMode == AnalyzeFile)
14424         TruncateGame();
14425
14426     ResurrectChessProgram();    /* in case it isn't running */
14427     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14428         gameMode = MachinePlaysWhite;
14429         ResetClocks();
14430     } else
14431     gameMode = MachinePlaysWhite;
14432     pausing = FALSE;
14433     ModeHighlight();
14434     SetGameInfo();
14435     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14436     DisplayTitle(buf);
14437     if (first.sendName) {
14438       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14439       SendToProgram(buf, &first);
14440     }
14441     if (first.sendTime) {
14442       if (first.useColors) {
14443         SendToProgram("black\n", &first); /*gnu kludge*/
14444       }
14445       SendTimeRemaining(&first, TRUE);
14446     }
14447     if (first.useColors) {
14448       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14449     }
14450     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14451     SetMachineThinkingEnables();
14452     first.maybeThinking = TRUE;
14453     StartClocks();
14454     firstMove = FALSE;
14455
14456     if (appData.autoFlipView && !flipView) {
14457       flipView = !flipView;
14458       DrawPosition(FALSE, NULL);
14459       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14460     }
14461
14462     if(bookHit) { // [HGM] book: simulate book reply
14463         static char bookMove[MSG_SIZ]; // a bit generous?
14464
14465         programStats.nodes = programStats.depth = programStats.time =
14466         programStats.score = programStats.got_only_move = 0;
14467         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14468
14469         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14470         strcat(bookMove, bookHit);
14471         HandleMachineMove(bookMove, &first);
14472     }
14473 }
14474
14475 void
14476 MachineBlackEvent ()
14477 {
14478   char buf[MSG_SIZ];
14479   char *bookHit = NULL;
14480
14481     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14482         return;
14483
14484
14485     if (gameMode == PlayFromGameFile ||
14486         gameMode == TwoMachinesPlay  ||
14487         gameMode == Training         ||
14488         gameMode == AnalyzeMode      ||
14489         gameMode == EndOfGame)
14490         EditGameEvent();
14491
14492     if (gameMode == EditPosition)
14493         EditPositionDone(TRUE);
14494
14495     if (WhiteOnMove(currentMove)) {
14496         DisplayError(_("It is not Black's turn"), 0);
14497         return;
14498     }
14499
14500     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14501       ExitAnalyzeMode();
14502
14503     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14504         gameMode == AnalyzeFile)
14505         TruncateGame();
14506
14507     ResurrectChessProgram();    /* in case it isn't running */
14508     gameMode = MachinePlaysBlack;
14509     pausing = FALSE;
14510     ModeHighlight();
14511     SetGameInfo();
14512     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14513     DisplayTitle(buf);
14514     if (first.sendName) {
14515       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14516       SendToProgram(buf, &first);
14517     }
14518     if (first.sendTime) {
14519       if (first.useColors) {
14520         SendToProgram("white\n", &first); /*gnu kludge*/
14521       }
14522       SendTimeRemaining(&first, FALSE);
14523     }
14524     if (first.useColors) {
14525       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14526     }
14527     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14528     SetMachineThinkingEnables();
14529     first.maybeThinking = TRUE;
14530     StartClocks();
14531
14532     if (appData.autoFlipView && flipView) {
14533       flipView = !flipView;
14534       DrawPosition(FALSE, NULL);
14535       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14536     }
14537     if(bookHit) { // [HGM] book: simulate book reply
14538         static char bookMove[MSG_SIZ]; // a bit generous?
14539
14540         programStats.nodes = programStats.depth = programStats.time =
14541         programStats.score = programStats.got_only_move = 0;
14542         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14543
14544         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14545         strcat(bookMove, bookHit);
14546         HandleMachineMove(bookMove, &first);
14547     }
14548 }
14549
14550
14551 void
14552 DisplayTwoMachinesTitle ()
14553 {
14554     char buf[MSG_SIZ];
14555     if (appData.matchGames > 0) {
14556         if(appData.tourneyFile[0]) {
14557           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14558                    gameInfo.white, _("vs."), gameInfo.black,
14559                    nextGame+1, appData.matchGames+1,
14560                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14561         } else
14562         if (first.twoMachinesColor[0] == 'w') {
14563           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14564                    gameInfo.white, _("vs."),  gameInfo.black,
14565                    first.matchWins, second.matchWins,
14566                    matchGame - 1 - (first.matchWins + second.matchWins));
14567         } else {
14568           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14569                    gameInfo.white, _("vs."), gameInfo.black,
14570                    second.matchWins, first.matchWins,
14571                    matchGame - 1 - (first.matchWins + second.matchWins));
14572         }
14573     } else {
14574       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14575     }
14576     DisplayTitle(buf);
14577 }
14578
14579 void
14580 SettingsMenuIfReady ()
14581 {
14582   if (second.lastPing != second.lastPong) {
14583     DisplayMessage("", _("Waiting for second chess program"));
14584     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14585     return;
14586   }
14587   ThawUI();
14588   DisplayMessage("", "");
14589   SettingsPopUp(&second);
14590 }
14591
14592 int
14593 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14594 {
14595     char buf[MSG_SIZ];
14596     if (cps->pr == NoProc) {
14597         StartChessProgram(cps);
14598         if (cps->protocolVersion == 1) {
14599           retry();
14600           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14601         } else {
14602           /* kludge: allow timeout for initial "feature" command */
14603           if(retry != TwoMachinesEventIfReady) FreezeUI();
14604           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14605           DisplayMessage("", buf);
14606           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14607         }
14608         return 1;
14609     }
14610     return 0;
14611 }
14612
14613 void
14614 TwoMachinesEvent P((void))
14615 {
14616     int i;
14617     char buf[MSG_SIZ];
14618     ChessProgramState *onmove;
14619     char *bookHit = NULL;
14620     static int stalling = 0;
14621     TimeMark now;
14622     long wait;
14623
14624     if (appData.noChessProgram) return;
14625
14626     switch (gameMode) {
14627       case TwoMachinesPlay:
14628         return;
14629       case MachinePlaysWhite:
14630       case MachinePlaysBlack:
14631         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14632             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14633             return;
14634         }
14635         /* fall through */
14636       case BeginningOfGame:
14637       case PlayFromGameFile:
14638       case EndOfGame:
14639         EditGameEvent();
14640         if (gameMode != EditGame) return;
14641         break;
14642       case EditPosition:
14643         EditPositionDone(TRUE);
14644         break;
14645       case AnalyzeMode:
14646       case AnalyzeFile:
14647         ExitAnalyzeMode();
14648         break;
14649       case EditGame:
14650       default:
14651         break;
14652     }
14653
14654 //    forwardMostMove = currentMove;
14655     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14656     startingEngine = TRUE;
14657
14658     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14659
14660     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14661     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14662       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14663       return;
14664     }
14665     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14666
14667     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14668                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14669         startingEngine = matchMode = FALSE;
14670         DisplayError("second engine does not play this", 0);
14671         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14672         EditGameEvent(); // switch back to EditGame mode
14673         return;
14674     }
14675
14676     if(!stalling) {
14677       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14678       SendToProgram("force\n", &second);
14679       stalling = 1;
14680       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14681       return;
14682     }
14683     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14684     if(appData.matchPause>10000 || appData.matchPause<10)
14685                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14686     wait = SubtractTimeMarks(&now, &pauseStart);
14687     if(wait < appData.matchPause) {
14688         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14689         return;
14690     }
14691     // we are now committed to starting the game
14692     stalling = 0;
14693     DisplayMessage("", "");
14694     if (startedFromSetupPosition) {
14695         SendBoard(&second, backwardMostMove);
14696     if (appData.debugMode) {
14697         fprintf(debugFP, "Two Machines\n");
14698     }
14699     }
14700     for (i = backwardMostMove; i < forwardMostMove; i++) {
14701         SendMoveToProgram(i, &second);
14702     }
14703
14704     gameMode = TwoMachinesPlay;
14705     pausing = startingEngine = FALSE;
14706     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14707     SetGameInfo();
14708     DisplayTwoMachinesTitle();
14709     firstMove = TRUE;
14710     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14711         onmove = &first;
14712     } else {
14713         onmove = &second;
14714     }
14715     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14716     SendToProgram(first.computerString, &first);
14717     if (first.sendName) {
14718       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14719       SendToProgram(buf, &first);
14720     }
14721     SendToProgram(second.computerString, &second);
14722     if (second.sendName) {
14723       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14724       SendToProgram(buf, &second);
14725     }
14726
14727     ResetClocks();
14728     if (!first.sendTime || !second.sendTime) {
14729         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14730         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14731     }
14732     if (onmove->sendTime) {
14733       if (onmove->useColors) {
14734         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14735       }
14736       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14737     }
14738     if (onmove->useColors) {
14739       SendToProgram(onmove->twoMachinesColor, onmove);
14740     }
14741     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14742 //    SendToProgram("go\n", onmove);
14743     onmove->maybeThinking = TRUE;
14744     SetMachineThinkingEnables();
14745
14746     StartClocks();
14747
14748     if(bookHit) { // [HGM] book: simulate book reply
14749         static char bookMove[MSG_SIZ]; // a bit generous?
14750
14751         programStats.nodes = programStats.depth = programStats.time =
14752         programStats.score = programStats.got_only_move = 0;
14753         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14754
14755         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14756         strcat(bookMove, bookHit);
14757         savedMessage = bookMove; // args for deferred call
14758         savedState = onmove;
14759         ScheduleDelayedEvent(DeferredBookMove, 1);
14760     }
14761 }
14762
14763 void
14764 TrainingEvent ()
14765 {
14766     if (gameMode == Training) {
14767       SetTrainingModeOff();
14768       gameMode = PlayFromGameFile;
14769       DisplayMessage("", _("Training mode off"));
14770     } else {
14771       gameMode = Training;
14772       animateTraining = appData.animate;
14773
14774       /* make sure we are not already at the end of the game */
14775       if (currentMove < forwardMostMove) {
14776         SetTrainingModeOn();
14777         DisplayMessage("", _("Training mode on"));
14778       } else {
14779         gameMode = PlayFromGameFile;
14780         DisplayError(_("Already at end of game"), 0);
14781       }
14782     }
14783     ModeHighlight();
14784 }
14785
14786 void
14787 IcsClientEvent ()
14788 {
14789     if (!appData.icsActive) return;
14790     switch (gameMode) {
14791       case IcsPlayingWhite:
14792       case IcsPlayingBlack:
14793       case IcsObserving:
14794       case IcsIdle:
14795       case BeginningOfGame:
14796       case IcsExamining:
14797         return;
14798
14799       case EditGame:
14800         break;
14801
14802       case EditPosition:
14803         EditPositionDone(TRUE);
14804         break;
14805
14806       case AnalyzeMode:
14807       case AnalyzeFile:
14808         ExitAnalyzeMode();
14809         break;
14810
14811       default:
14812         EditGameEvent();
14813         break;
14814     }
14815
14816     gameMode = IcsIdle;
14817     ModeHighlight();
14818     return;
14819 }
14820
14821 void
14822 EditGameEvent ()
14823 {
14824     int i;
14825
14826     switch (gameMode) {
14827       case Training:
14828         SetTrainingModeOff();
14829         break;
14830       case MachinePlaysWhite:
14831       case MachinePlaysBlack:
14832       case BeginningOfGame:
14833         SendToProgram("force\n", &first);
14834         SetUserThinkingEnables();
14835         break;
14836       case PlayFromGameFile:
14837         (void) StopLoadGameTimer();
14838         if (gameFileFP != NULL) {
14839             gameFileFP = NULL;
14840         }
14841         break;
14842       case EditPosition:
14843         EditPositionDone(TRUE);
14844         break;
14845       case AnalyzeMode:
14846       case AnalyzeFile:
14847         ExitAnalyzeMode();
14848         SendToProgram("force\n", &first);
14849         break;
14850       case TwoMachinesPlay:
14851         GameEnds(EndOfFile, NULL, GE_PLAYER);
14852         ResurrectChessProgram();
14853         SetUserThinkingEnables();
14854         break;
14855       case EndOfGame:
14856         ResurrectChessProgram();
14857         break;
14858       case IcsPlayingBlack:
14859       case IcsPlayingWhite:
14860         DisplayError(_("Warning: You are still playing a game"), 0);
14861         break;
14862       case IcsObserving:
14863         DisplayError(_("Warning: You are still observing a game"), 0);
14864         break;
14865       case IcsExamining:
14866         DisplayError(_("Warning: You are still examining a game"), 0);
14867         break;
14868       case IcsIdle:
14869         break;
14870       case EditGame:
14871       default:
14872         return;
14873     }
14874
14875     pausing = FALSE;
14876     StopClocks();
14877     first.offeredDraw = second.offeredDraw = 0;
14878
14879     if (gameMode == PlayFromGameFile) {
14880         whiteTimeRemaining = timeRemaining[0][currentMove];
14881         blackTimeRemaining = timeRemaining[1][currentMove];
14882         DisplayTitle("");
14883     }
14884
14885     if (gameMode == MachinePlaysWhite ||
14886         gameMode == MachinePlaysBlack ||
14887         gameMode == TwoMachinesPlay ||
14888         gameMode == EndOfGame) {
14889         i = forwardMostMove;
14890         while (i > currentMove) {
14891             SendToProgram("undo\n", &first);
14892             i--;
14893         }
14894         if(!adjustedClock) {
14895         whiteTimeRemaining = timeRemaining[0][currentMove];
14896         blackTimeRemaining = timeRemaining[1][currentMove];
14897         DisplayBothClocks();
14898         }
14899         if (whiteFlag || blackFlag) {
14900             whiteFlag = blackFlag = 0;
14901         }
14902         DisplayTitle("");
14903     }
14904
14905     gameMode = EditGame;
14906     ModeHighlight();
14907     SetGameInfo();
14908 }
14909
14910
14911 void
14912 EditPositionEvent ()
14913 {
14914     if (gameMode == EditPosition) {
14915         EditGameEvent();
14916         return;
14917     }
14918
14919     EditGameEvent();
14920     if (gameMode != EditGame) return;
14921
14922     gameMode = EditPosition;
14923     ModeHighlight();
14924     SetGameInfo();
14925     if (currentMove > 0)
14926       CopyBoard(boards[0], boards[currentMove]);
14927
14928     blackPlaysFirst = !WhiteOnMove(currentMove);
14929     ResetClocks();
14930     currentMove = forwardMostMove = backwardMostMove = 0;
14931     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14932     DisplayMove(-1);
14933     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14934 }
14935
14936 void
14937 ExitAnalyzeMode ()
14938 {
14939     /* [DM] icsEngineAnalyze - possible call from other functions */
14940     if (appData.icsEngineAnalyze) {
14941         appData.icsEngineAnalyze = FALSE;
14942
14943         DisplayMessage("",_("Close ICS engine analyze..."));
14944     }
14945     if (first.analysisSupport && first.analyzing) {
14946       SendToBoth("exit\n");
14947       first.analyzing = second.analyzing = FALSE;
14948     }
14949     thinkOutput[0] = NULLCHAR;
14950 }
14951
14952 void
14953 EditPositionDone (Boolean fakeRights)
14954 {
14955     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14956
14957     startedFromSetupPosition = TRUE;
14958     InitChessProgram(&first, FALSE);
14959     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14960       boards[0][EP_STATUS] = EP_NONE;
14961       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14962       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14963         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14964         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14965       } else boards[0][CASTLING][2] = NoRights;
14966       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14967         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14968         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14969       } else boards[0][CASTLING][5] = NoRights;
14970       if(gameInfo.variant == VariantSChess) {
14971         int i;
14972         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14973           boards[0][VIRGIN][i] = 0;
14974           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14975           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14976         }
14977       }
14978     }
14979     SendToProgram("force\n", &first);
14980     if (blackPlaysFirst) {
14981         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14982         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14983         currentMove = forwardMostMove = backwardMostMove = 1;
14984         CopyBoard(boards[1], boards[0]);
14985     } else {
14986         currentMove = forwardMostMove = backwardMostMove = 0;
14987     }
14988     SendBoard(&first, forwardMostMove);
14989     if (appData.debugMode) {
14990         fprintf(debugFP, "EditPosDone\n");
14991     }
14992     DisplayTitle("");
14993     DisplayMessage("", "");
14994     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14995     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14996     gameMode = EditGame;
14997     ModeHighlight();
14998     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14999     ClearHighlights(); /* [AS] */
15000 }
15001
15002 /* Pause for `ms' milliseconds */
15003 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15004 void
15005 TimeDelay (long ms)
15006 {
15007     TimeMark m1, m2;
15008
15009     GetTimeMark(&m1);
15010     do {
15011         GetTimeMark(&m2);
15012     } while (SubtractTimeMarks(&m2, &m1) < ms);
15013 }
15014
15015 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15016 void
15017 SendMultiLineToICS (char *buf)
15018 {
15019     char temp[MSG_SIZ+1], *p;
15020     int len;
15021
15022     len = strlen(buf);
15023     if (len > MSG_SIZ)
15024       len = MSG_SIZ;
15025
15026     strncpy(temp, buf, len);
15027     temp[len] = 0;
15028
15029     p = temp;
15030     while (*p) {
15031         if (*p == '\n' || *p == '\r')
15032           *p = ' ';
15033         ++p;
15034     }
15035
15036     strcat(temp, "\n");
15037     SendToICS(temp);
15038     SendToPlayer(temp, strlen(temp));
15039 }
15040
15041 void
15042 SetWhiteToPlayEvent ()
15043 {
15044     if (gameMode == EditPosition) {
15045         blackPlaysFirst = FALSE;
15046         DisplayBothClocks();    /* works because currentMove is 0 */
15047     } else if (gameMode == IcsExamining) {
15048         SendToICS(ics_prefix);
15049         SendToICS("tomove white\n");
15050     }
15051 }
15052
15053 void
15054 SetBlackToPlayEvent ()
15055 {
15056     if (gameMode == EditPosition) {
15057         blackPlaysFirst = TRUE;
15058         currentMove = 1;        /* kludge */
15059         DisplayBothClocks();
15060         currentMove = 0;
15061     } else if (gameMode == IcsExamining) {
15062         SendToICS(ics_prefix);
15063         SendToICS("tomove black\n");
15064     }
15065 }
15066
15067 void
15068 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15069 {
15070     char buf[MSG_SIZ];
15071     ChessSquare piece = boards[0][y][x];
15072     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15073     static int lastVariant;
15074
15075     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15076
15077     switch (selection) {
15078       case ClearBoard:
15079         CopyBoard(currentBoard, boards[0]);
15080         CopyBoard(menuBoard, initialPosition);
15081         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15082             SendToICS(ics_prefix);
15083             SendToICS("bsetup clear\n");
15084         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15085             SendToICS(ics_prefix);
15086             SendToICS("clearboard\n");
15087         } else {
15088             int nonEmpty = 0;
15089             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15090                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15091                 for (y = 0; y < BOARD_HEIGHT; y++) {
15092                     if (gameMode == IcsExamining) {
15093                         if (boards[currentMove][y][x] != EmptySquare) {
15094                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15095                                     AAA + x, ONE + y);
15096                             SendToICS(buf);
15097                         }
15098                     } else {
15099                         if(boards[0][y][x] != p) nonEmpty++;
15100                         boards[0][y][x] = p;
15101                     }
15102                 }
15103             }
15104             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15105                 int r;
15106                 for(r = 0; r < BOARD_HEIGHT; r++) {
15107                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15108                     ChessSquare p = menuBoard[r][x];
15109                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15110                   }
15111                 }
15112                 DisplayMessage("Clicking clock again restores position", "");
15113                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15114                 if(!nonEmpty) { // asked to clear an empty board
15115                     CopyBoard(boards[0], menuBoard);
15116                 } else
15117                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15118                     CopyBoard(boards[0], initialPosition);
15119                 } else
15120                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15121                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15122                     CopyBoard(boards[0], erasedBoard);
15123                 } else
15124                     CopyBoard(erasedBoard, currentBoard);
15125
15126             }
15127         }
15128         if (gameMode == EditPosition) {
15129             DrawPosition(FALSE, boards[0]);
15130         }
15131         break;
15132
15133       case WhitePlay:
15134         SetWhiteToPlayEvent();
15135         break;
15136
15137       case BlackPlay:
15138         SetBlackToPlayEvent();
15139         break;
15140
15141       case EmptySquare:
15142         if (gameMode == IcsExamining) {
15143             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15144             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15145             SendToICS(buf);
15146         } else {
15147             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15148                 if(x == BOARD_LEFT-2) {
15149                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15150                     boards[0][y][1] = 0;
15151                 } else
15152                 if(x == BOARD_RGHT+1) {
15153                     if(y >= gameInfo.holdingsSize) break;
15154                     boards[0][y][BOARD_WIDTH-2] = 0;
15155                 } else break;
15156             }
15157             boards[0][y][x] = EmptySquare;
15158             DrawPosition(FALSE, boards[0]);
15159         }
15160         break;
15161
15162       case PromotePiece:
15163         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15164            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15165             selection = (ChessSquare) (PROMOTED piece);
15166         } else if(piece == EmptySquare) selection = WhiteSilver;
15167         else selection = (ChessSquare)((int)piece - 1);
15168         goto defaultlabel;
15169
15170       case DemotePiece:
15171         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15172            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15173             selection = (ChessSquare) (DEMOTED piece);
15174         } else if(piece == EmptySquare) selection = BlackSilver;
15175         else selection = (ChessSquare)((int)piece + 1);
15176         goto defaultlabel;
15177
15178       case WhiteQueen:
15179       case BlackQueen:
15180         if(gameInfo.variant == VariantShatranj ||
15181            gameInfo.variant == VariantXiangqi  ||
15182            gameInfo.variant == VariantCourier  ||
15183            gameInfo.variant == VariantASEAN    ||
15184            gameInfo.variant == VariantMakruk     )
15185             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15186         goto defaultlabel;
15187
15188       case WhiteKing:
15189       case BlackKing:
15190         if(gameInfo.variant == VariantXiangqi)
15191             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15192         if(gameInfo.variant == VariantKnightmate)
15193             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15194       default:
15195         defaultlabel:
15196         if (gameMode == IcsExamining) {
15197             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15198             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15199                      PieceToChar(selection), AAA + x, ONE + y);
15200             SendToICS(buf);
15201         } else {
15202             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15203                 int n;
15204                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15205                     n = PieceToNumber(selection - BlackPawn);
15206                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15207                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15208                     boards[0][BOARD_HEIGHT-1-n][1]++;
15209                 } else
15210                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15211                     n = PieceToNumber(selection);
15212                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15213                     boards[0][n][BOARD_WIDTH-1] = selection;
15214                     boards[0][n][BOARD_WIDTH-2]++;
15215                 }
15216             } else
15217             boards[0][y][x] = selection;
15218             DrawPosition(TRUE, boards[0]);
15219             ClearHighlights();
15220             fromX = fromY = -1;
15221         }
15222         break;
15223     }
15224 }
15225
15226
15227 void
15228 DropMenuEvent (ChessSquare selection, int x, int y)
15229 {
15230     ChessMove moveType;
15231
15232     switch (gameMode) {
15233       case IcsPlayingWhite:
15234       case MachinePlaysBlack:
15235         if (!WhiteOnMove(currentMove)) {
15236             DisplayMoveError(_("It is Black's turn"));
15237             return;
15238         }
15239         moveType = WhiteDrop;
15240         break;
15241       case IcsPlayingBlack:
15242       case MachinePlaysWhite:
15243         if (WhiteOnMove(currentMove)) {
15244             DisplayMoveError(_("It is White's turn"));
15245             return;
15246         }
15247         moveType = BlackDrop;
15248         break;
15249       case EditGame:
15250         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15251         break;
15252       default:
15253         return;
15254     }
15255
15256     if (moveType == BlackDrop && selection < BlackPawn) {
15257       selection = (ChessSquare) ((int) selection
15258                                  + (int) BlackPawn - (int) WhitePawn);
15259     }
15260     if (boards[currentMove][y][x] != EmptySquare) {
15261         DisplayMoveError(_("That square is occupied"));
15262         return;
15263     }
15264
15265     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15266 }
15267
15268 void
15269 AcceptEvent ()
15270 {
15271     /* Accept a pending offer of any kind from opponent */
15272
15273     if (appData.icsActive) {
15274         SendToICS(ics_prefix);
15275         SendToICS("accept\n");
15276     } else if (cmailMsgLoaded) {
15277         if (currentMove == cmailOldMove &&
15278             commentList[cmailOldMove] != NULL &&
15279             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15280                    "Black offers a draw" : "White offers a draw")) {
15281             TruncateGame();
15282             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15283             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15284         } else {
15285             DisplayError(_("There is no pending offer on this move"), 0);
15286             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15287         }
15288     } else {
15289         /* Not used for offers from chess program */
15290     }
15291 }
15292
15293 void
15294 DeclineEvent ()
15295 {
15296     /* Decline a pending offer of any kind from opponent */
15297
15298     if (appData.icsActive) {
15299         SendToICS(ics_prefix);
15300         SendToICS("decline\n");
15301     } else if (cmailMsgLoaded) {
15302         if (currentMove == cmailOldMove &&
15303             commentList[cmailOldMove] != NULL &&
15304             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15305                    "Black offers a draw" : "White offers a draw")) {
15306 #ifdef NOTDEF
15307             AppendComment(cmailOldMove, "Draw declined", TRUE);
15308             DisplayComment(cmailOldMove - 1, "Draw declined");
15309 #endif /*NOTDEF*/
15310         } else {
15311             DisplayError(_("There is no pending offer on this move"), 0);
15312         }
15313     } else {
15314         /* Not used for offers from chess program */
15315     }
15316 }
15317
15318 void
15319 RematchEvent ()
15320 {
15321     /* Issue ICS rematch command */
15322     if (appData.icsActive) {
15323         SendToICS(ics_prefix);
15324         SendToICS("rematch\n");
15325     }
15326 }
15327
15328 void
15329 CallFlagEvent ()
15330 {
15331     /* Call your opponent's flag (claim a win on time) */
15332     if (appData.icsActive) {
15333         SendToICS(ics_prefix);
15334         SendToICS("flag\n");
15335     } else {
15336         switch (gameMode) {
15337           default:
15338             return;
15339           case MachinePlaysWhite:
15340             if (whiteFlag) {
15341                 if (blackFlag)
15342                   GameEnds(GameIsDrawn, "Both players ran out of time",
15343                            GE_PLAYER);
15344                 else
15345                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15346             } else {
15347                 DisplayError(_("Your opponent is not out of time"), 0);
15348             }
15349             break;
15350           case MachinePlaysBlack:
15351             if (blackFlag) {
15352                 if (whiteFlag)
15353                   GameEnds(GameIsDrawn, "Both players ran out of time",
15354                            GE_PLAYER);
15355                 else
15356                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15357             } else {
15358                 DisplayError(_("Your opponent is not out of time"), 0);
15359             }
15360             break;
15361         }
15362     }
15363 }
15364
15365 void
15366 ClockClick (int which)
15367 {       // [HGM] code moved to back-end from winboard.c
15368         if(which) { // black clock
15369           if (gameMode == EditPosition || gameMode == IcsExamining) {
15370             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15371             SetBlackToPlayEvent();
15372           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15373                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15374           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15375           } else if (shiftKey) {
15376             AdjustClock(which, -1);
15377           } else if (gameMode == IcsPlayingWhite ||
15378                      gameMode == MachinePlaysBlack) {
15379             CallFlagEvent();
15380           }
15381         } else { // white clock
15382           if (gameMode == EditPosition || gameMode == IcsExamining) {
15383             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15384             SetWhiteToPlayEvent();
15385           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15386                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15387           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15388           } else if (shiftKey) {
15389             AdjustClock(which, -1);
15390           } else if (gameMode == IcsPlayingBlack ||
15391                    gameMode == MachinePlaysWhite) {
15392             CallFlagEvent();
15393           }
15394         }
15395 }
15396
15397 void
15398 DrawEvent ()
15399 {
15400     /* Offer draw or accept pending draw offer from opponent */
15401
15402     if (appData.icsActive) {
15403         /* Note: tournament rules require draw offers to be
15404            made after you make your move but before you punch
15405            your clock.  Currently ICS doesn't let you do that;
15406            instead, you immediately punch your clock after making
15407            a move, but you can offer a draw at any time. */
15408
15409         SendToICS(ics_prefix);
15410         SendToICS("draw\n");
15411         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15412     } else if (cmailMsgLoaded) {
15413         if (currentMove == cmailOldMove &&
15414             commentList[cmailOldMove] != NULL &&
15415             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15416                    "Black offers a draw" : "White offers a draw")) {
15417             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15418             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15419         } else if (currentMove == cmailOldMove + 1) {
15420             char *offer = WhiteOnMove(cmailOldMove) ?
15421               "White offers a draw" : "Black offers a draw";
15422             AppendComment(currentMove, offer, TRUE);
15423             DisplayComment(currentMove - 1, offer);
15424             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15425         } else {
15426             DisplayError(_("You must make your move before offering a draw"), 0);
15427             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15428         }
15429     } else if (first.offeredDraw) {
15430         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15431     } else {
15432         if (first.sendDrawOffers) {
15433             SendToProgram("draw\n", &first);
15434             userOfferedDraw = TRUE;
15435         }
15436     }
15437 }
15438
15439 void
15440 AdjournEvent ()
15441 {
15442     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15443
15444     if (appData.icsActive) {
15445         SendToICS(ics_prefix);
15446         SendToICS("adjourn\n");
15447     } else {
15448         /* Currently GNU Chess doesn't offer or accept Adjourns */
15449     }
15450 }
15451
15452
15453 void
15454 AbortEvent ()
15455 {
15456     /* Offer Abort or accept pending Abort offer from opponent */
15457
15458     if (appData.icsActive) {
15459         SendToICS(ics_prefix);
15460         SendToICS("abort\n");
15461     } else {
15462         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15463     }
15464 }
15465
15466 void
15467 ResignEvent ()
15468 {
15469     /* Resign.  You can do this even if it's not your turn. */
15470
15471     if (appData.icsActive) {
15472         SendToICS(ics_prefix);
15473         SendToICS("resign\n");
15474     } else {
15475         switch (gameMode) {
15476           case MachinePlaysWhite:
15477             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15478             break;
15479           case MachinePlaysBlack:
15480             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15481             break;
15482           case EditGame:
15483             if (cmailMsgLoaded) {
15484                 TruncateGame();
15485                 if (WhiteOnMove(cmailOldMove)) {
15486                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15487                 } else {
15488                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15489                 }
15490                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15491             }
15492             break;
15493           default:
15494             break;
15495         }
15496     }
15497 }
15498
15499
15500 void
15501 StopObservingEvent ()
15502 {
15503     /* Stop observing current games */
15504     SendToICS(ics_prefix);
15505     SendToICS("unobserve\n");
15506 }
15507
15508 void
15509 StopExaminingEvent ()
15510 {
15511     /* Stop observing current game */
15512     SendToICS(ics_prefix);
15513     SendToICS("unexamine\n");
15514 }
15515
15516 void
15517 ForwardInner (int target)
15518 {
15519     int limit; int oldSeekGraphUp = seekGraphUp;
15520
15521     if (appData.debugMode)
15522         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15523                 target, currentMove, forwardMostMove);
15524
15525     if (gameMode == EditPosition)
15526       return;
15527
15528     seekGraphUp = FALSE;
15529     MarkTargetSquares(1);
15530     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15531
15532     if (gameMode == PlayFromGameFile && !pausing)
15533       PauseEvent();
15534
15535     if (gameMode == IcsExamining && pausing)
15536       limit = pauseExamForwardMostMove;
15537     else
15538       limit = forwardMostMove;
15539
15540     if (target > limit) target = limit;
15541
15542     if (target > 0 && moveList[target - 1][0]) {
15543         int fromX, fromY, toX, toY;
15544         toX = moveList[target - 1][2] - AAA;
15545         toY = moveList[target - 1][3] - ONE;
15546         if (moveList[target - 1][1] == '@') {
15547             if (appData.highlightLastMove) {
15548                 SetHighlights(-1, -1, toX, toY);
15549             }
15550         } else {
15551             int viaX = moveList[target - 1][5] - AAA;
15552             int viaY = moveList[target - 1][6] - ONE;
15553             fromX = moveList[target - 1][0] - AAA;
15554             fromY = moveList[target - 1][1] - ONE;
15555             if (target == currentMove + 1) {
15556                 if(moveList[target - 1][4] == ';') { // multi-leg
15557                     ChessSquare piece = boards[currentMove][viaY][viaX];
15558                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15559                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15560                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15561                     boards[currentMove][viaY][viaX] = piece;
15562                 } else
15563                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15564             }
15565             if (appData.highlightLastMove) {
15566                 SetHighlights(fromX, fromY, toX, toY);
15567             }
15568         }
15569     }
15570     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15571         gameMode == Training || gameMode == PlayFromGameFile ||
15572         gameMode == AnalyzeFile) {
15573         while (currentMove < target) {
15574             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15575             SendMoveToProgram(currentMove++, &first);
15576         }
15577     } else {
15578         currentMove = target;
15579     }
15580
15581     if (gameMode == EditGame || gameMode == EndOfGame) {
15582         whiteTimeRemaining = timeRemaining[0][currentMove];
15583         blackTimeRemaining = timeRemaining[1][currentMove];
15584     }
15585     DisplayBothClocks();
15586     DisplayMove(currentMove - 1);
15587     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15588     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15589     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15590         DisplayComment(currentMove - 1, commentList[currentMove]);
15591     }
15592     ClearMap(); // [HGM] exclude: invalidate map
15593 }
15594
15595
15596 void
15597 ForwardEvent ()
15598 {
15599     if (gameMode == IcsExamining && !pausing) {
15600         SendToICS(ics_prefix);
15601         SendToICS("forward\n");
15602     } else {
15603         ForwardInner(currentMove + 1);
15604     }
15605 }
15606
15607 void
15608 ToEndEvent ()
15609 {
15610     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15611         /* to optimze, we temporarily turn off analysis mode while we feed
15612          * the remaining moves to the engine. Otherwise we get analysis output
15613          * after each move.
15614          */
15615         if (first.analysisSupport) {
15616           SendToProgram("exit\nforce\n", &first);
15617           first.analyzing = FALSE;
15618         }
15619     }
15620
15621     if (gameMode == IcsExamining && !pausing) {
15622         SendToICS(ics_prefix);
15623         SendToICS("forward 999999\n");
15624     } else {
15625         ForwardInner(forwardMostMove);
15626     }
15627
15628     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15629         /* we have fed all the moves, so reactivate analysis mode */
15630         SendToProgram("analyze\n", &first);
15631         first.analyzing = TRUE;
15632         /*first.maybeThinking = TRUE;*/
15633         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15634     }
15635 }
15636
15637 void
15638 BackwardInner (int target)
15639 {
15640     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15641
15642     if (appData.debugMode)
15643         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15644                 target, currentMove, forwardMostMove);
15645
15646     if (gameMode == EditPosition) return;
15647     seekGraphUp = FALSE;
15648     MarkTargetSquares(1);
15649     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15650     if (currentMove <= backwardMostMove) {
15651         ClearHighlights();
15652         DrawPosition(full_redraw, boards[currentMove]);
15653         return;
15654     }
15655     if (gameMode == PlayFromGameFile && !pausing)
15656       PauseEvent();
15657
15658     if (moveList[target][0]) {
15659         int fromX, fromY, toX, toY;
15660         toX = moveList[target][2] - AAA;
15661         toY = moveList[target][3] - ONE;
15662         if (moveList[target][1] == '@') {
15663             if (appData.highlightLastMove) {
15664                 SetHighlights(-1, -1, toX, toY);
15665             }
15666         } else {
15667             fromX = moveList[target][0] - AAA;
15668             fromY = moveList[target][1] - ONE;
15669             if (target == currentMove - 1) {
15670                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15671             }
15672             if (appData.highlightLastMove) {
15673                 SetHighlights(fromX, fromY, toX, toY);
15674             }
15675         }
15676     }
15677     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15678         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15679         while (currentMove > target) {
15680             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15681                 // null move cannot be undone. Reload program with move history before it.
15682                 int i;
15683                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15684                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15685                 }
15686                 SendBoard(&first, i);
15687               if(second.analyzing) SendBoard(&second, i);
15688                 for(currentMove=i; currentMove<target; currentMove++) {
15689                     SendMoveToProgram(currentMove, &first);
15690                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15691                 }
15692                 break;
15693             }
15694             SendToBoth("undo\n");
15695             currentMove--;
15696         }
15697     } else {
15698         currentMove = target;
15699     }
15700
15701     if (gameMode == EditGame || gameMode == EndOfGame) {
15702         whiteTimeRemaining = timeRemaining[0][currentMove];
15703         blackTimeRemaining = timeRemaining[1][currentMove];
15704     }
15705     DisplayBothClocks();
15706     DisplayMove(currentMove - 1);
15707     DrawPosition(full_redraw, boards[currentMove]);
15708     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15709     // [HGM] PV info: routine tests if comment empty
15710     DisplayComment(currentMove - 1, commentList[currentMove]);
15711     ClearMap(); // [HGM] exclude: invalidate map
15712 }
15713
15714 void
15715 BackwardEvent ()
15716 {
15717     if (gameMode == IcsExamining && !pausing) {
15718         SendToICS(ics_prefix);
15719         SendToICS("backward\n");
15720     } else {
15721         BackwardInner(currentMove - 1);
15722     }
15723 }
15724
15725 void
15726 ToStartEvent ()
15727 {
15728     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15729         /* to optimize, we temporarily turn off analysis mode while we undo
15730          * all the moves. Otherwise we get analysis output after each undo.
15731          */
15732         if (first.analysisSupport) {
15733           SendToProgram("exit\nforce\n", &first);
15734           first.analyzing = FALSE;
15735         }
15736     }
15737
15738     if (gameMode == IcsExamining && !pausing) {
15739         SendToICS(ics_prefix);
15740         SendToICS("backward 999999\n");
15741     } else {
15742         BackwardInner(backwardMostMove);
15743     }
15744
15745     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15746         /* we have fed all the moves, so reactivate analysis mode */
15747         SendToProgram("analyze\n", &first);
15748         first.analyzing = TRUE;
15749         /*first.maybeThinking = TRUE;*/
15750         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15751     }
15752 }
15753
15754 void
15755 ToNrEvent (int to)
15756 {
15757   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15758   if (to >= forwardMostMove) to = forwardMostMove;
15759   if (to <= backwardMostMove) to = backwardMostMove;
15760   if (to < currentMove) {
15761     BackwardInner(to);
15762   } else {
15763     ForwardInner(to);
15764   }
15765 }
15766
15767 void
15768 RevertEvent (Boolean annotate)
15769 {
15770     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15771         return;
15772     }
15773     if (gameMode != IcsExamining) {
15774         DisplayError(_("You are not examining a game"), 0);
15775         return;
15776     }
15777     if (pausing) {
15778         DisplayError(_("You can't revert while pausing"), 0);
15779         return;
15780     }
15781     SendToICS(ics_prefix);
15782     SendToICS("revert\n");
15783 }
15784
15785 void
15786 RetractMoveEvent ()
15787 {
15788     switch (gameMode) {
15789       case MachinePlaysWhite:
15790       case MachinePlaysBlack:
15791         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15792             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15793             return;
15794         }
15795         if (forwardMostMove < 2) return;
15796         currentMove = forwardMostMove = forwardMostMove - 2;
15797         whiteTimeRemaining = timeRemaining[0][currentMove];
15798         blackTimeRemaining = timeRemaining[1][currentMove];
15799         DisplayBothClocks();
15800         DisplayMove(currentMove - 1);
15801         ClearHighlights();/*!! could figure this out*/
15802         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15803         SendToProgram("remove\n", &first);
15804         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15805         break;
15806
15807       case BeginningOfGame:
15808       default:
15809         break;
15810
15811       case IcsPlayingWhite:
15812       case IcsPlayingBlack:
15813         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15814             SendToICS(ics_prefix);
15815             SendToICS("takeback 2\n");
15816         } else {
15817             SendToICS(ics_prefix);
15818             SendToICS("takeback 1\n");
15819         }
15820         break;
15821     }
15822 }
15823
15824 void
15825 MoveNowEvent ()
15826 {
15827     ChessProgramState *cps;
15828
15829     switch (gameMode) {
15830       case MachinePlaysWhite:
15831         if (!WhiteOnMove(forwardMostMove)) {
15832             DisplayError(_("It is your turn"), 0);
15833             return;
15834         }
15835         cps = &first;
15836         break;
15837       case MachinePlaysBlack:
15838         if (WhiteOnMove(forwardMostMove)) {
15839             DisplayError(_("It is your turn"), 0);
15840             return;
15841         }
15842         cps = &first;
15843         break;
15844       case TwoMachinesPlay:
15845         if (WhiteOnMove(forwardMostMove) ==
15846             (first.twoMachinesColor[0] == 'w')) {
15847             cps = &first;
15848         } else {
15849             cps = &second;
15850         }
15851         break;
15852       case BeginningOfGame:
15853       default:
15854         return;
15855     }
15856     SendToProgram("?\n", cps);
15857 }
15858
15859 void
15860 TruncateGameEvent ()
15861 {
15862     EditGameEvent();
15863     if (gameMode != EditGame) return;
15864     TruncateGame();
15865 }
15866
15867 void
15868 TruncateGame ()
15869 {
15870     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15871     if (forwardMostMove > currentMove) {
15872         if (gameInfo.resultDetails != NULL) {
15873             free(gameInfo.resultDetails);
15874             gameInfo.resultDetails = NULL;
15875             gameInfo.result = GameUnfinished;
15876         }
15877         forwardMostMove = currentMove;
15878         HistorySet(parseList, backwardMostMove, forwardMostMove,
15879                    currentMove-1);
15880     }
15881 }
15882
15883 void
15884 HintEvent ()
15885 {
15886     if (appData.noChessProgram) return;
15887     switch (gameMode) {
15888       case MachinePlaysWhite:
15889         if (WhiteOnMove(forwardMostMove)) {
15890             DisplayError(_("Wait until your turn."), 0);
15891             return;
15892         }
15893         break;
15894       case BeginningOfGame:
15895       case MachinePlaysBlack:
15896         if (!WhiteOnMove(forwardMostMove)) {
15897             DisplayError(_("Wait until your turn."), 0);
15898             return;
15899         }
15900         break;
15901       default:
15902         DisplayError(_("No hint available"), 0);
15903         return;
15904     }
15905     SendToProgram("hint\n", &first);
15906     hintRequested = TRUE;
15907 }
15908
15909 int
15910 SaveSelected (FILE *g, int dummy, char *dummy2)
15911 {
15912     ListGame * lg = (ListGame *) gameList.head;
15913     int nItem, cnt=0;
15914     FILE *f;
15915
15916     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15917         DisplayError(_("Game list not loaded or empty"), 0);
15918         return 0;
15919     }
15920
15921     creatingBook = TRUE; // suppresses stuff during load game
15922
15923     /* Get list size */
15924     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15925         if(lg->position >= 0) { // selected?
15926             LoadGame(f, nItem, "", TRUE);
15927             SaveGamePGN2(g); // leaves g open
15928             cnt++; DoEvents();
15929         }
15930         lg = (ListGame *) lg->node.succ;
15931     }
15932
15933     fclose(g);
15934     creatingBook = FALSE;
15935
15936     return cnt;
15937 }
15938
15939 void
15940 CreateBookEvent ()
15941 {
15942     ListGame * lg = (ListGame *) gameList.head;
15943     FILE *f, *g;
15944     int nItem;
15945     static int secondTime = FALSE;
15946
15947     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15948         DisplayError(_("Game list not loaded or empty"), 0);
15949         return;
15950     }
15951
15952     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15953         fclose(g);
15954         secondTime++;
15955         DisplayNote(_("Book file exists! Try again for overwrite."));
15956         return;
15957     }
15958
15959     creatingBook = TRUE;
15960     secondTime = FALSE;
15961
15962     /* Get list size */
15963     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15964         if(lg->position >= 0) {
15965             LoadGame(f, nItem, "", TRUE);
15966             AddGameToBook(TRUE);
15967             DoEvents();
15968         }
15969         lg = (ListGame *) lg->node.succ;
15970     }
15971
15972     creatingBook = FALSE;
15973     FlushBook();
15974 }
15975
15976 void
15977 BookEvent ()
15978 {
15979     if (appData.noChessProgram) return;
15980     switch (gameMode) {
15981       case MachinePlaysWhite:
15982         if (WhiteOnMove(forwardMostMove)) {
15983             DisplayError(_("Wait until your turn."), 0);
15984             return;
15985         }
15986         break;
15987       case BeginningOfGame:
15988       case MachinePlaysBlack:
15989         if (!WhiteOnMove(forwardMostMove)) {
15990             DisplayError(_("Wait until your turn."), 0);
15991             return;
15992         }
15993         break;
15994       case EditPosition:
15995         EditPositionDone(TRUE);
15996         break;
15997       case TwoMachinesPlay:
15998         return;
15999       default:
16000         break;
16001     }
16002     SendToProgram("bk\n", &first);
16003     bookOutput[0] = NULLCHAR;
16004     bookRequested = TRUE;
16005 }
16006
16007 void
16008 AboutGameEvent ()
16009 {
16010     char *tags = PGNTags(&gameInfo);
16011     TagsPopUp(tags, CmailMsg());
16012     free(tags);
16013 }
16014
16015 /* end button procedures */
16016
16017 void
16018 PrintPosition (FILE *fp, int move)
16019 {
16020     int i, j;
16021
16022     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16023         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16024             char c = PieceToChar(boards[move][i][j]);
16025             fputc(c == 'x' ? '.' : c, fp);
16026             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16027         }
16028     }
16029     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16030       fprintf(fp, "white to play\n");
16031     else
16032       fprintf(fp, "black to play\n");
16033 }
16034
16035 void
16036 PrintOpponents (FILE *fp)
16037 {
16038     if (gameInfo.white != NULL) {
16039         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16040     } else {
16041         fprintf(fp, "\n");
16042     }
16043 }
16044
16045 /* Find last component of program's own name, using some heuristics */
16046 void
16047 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16048 {
16049     char *p, *q, c;
16050     int local = (strcmp(host, "localhost") == 0);
16051     while (!local && (p = strchr(prog, ';')) != NULL) {
16052         p++;
16053         while (*p == ' ') p++;
16054         prog = p;
16055     }
16056     if (*prog == '"' || *prog == '\'') {
16057         q = strchr(prog + 1, *prog);
16058     } else {
16059         q = strchr(prog, ' ');
16060     }
16061     if (q == NULL) q = prog + strlen(prog);
16062     p = q;
16063     while (p >= prog && *p != '/' && *p != '\\') p--;
16064     p++;
16065     if(p == prog && *p == '"') p++;
16066     c = *q; *q = 0;
16067     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16068     memcpy(buf, p, q - p);
16069     buf[q - p] = NULLCHAR;
16070     if (!local) {
16071         strcat(buf, "@");
16072         strcat(buf, host);
16073     }
16074 }
16075
16076 char *
16077 TimeControlTagValue ()
16078 {
16079     char buf[MSG_SIZ];
16080     if (!appData.clockMode) {
16081       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16082     } else if (movesPerSession > 0) {
16083       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16084     } else if (timeIncrement == 0) {
16085       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16086     } else {
16087       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16088     }
16089     return StrSave(buf);
16090 }
16091
16092 void
16093 SetGameInfo ()
16094 {
16095     /* This routine is used only for certain modes */
16096     VariantClass v = gameInfo.variant;
16097     ChessMove r = GameUnfinished;
16098     char *p = NULL;
16099
16100     if(keepInfo) return;
16101
16102     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16103         r = gameInfo.result;
16104         p = gameInfo.resultDetails;
16105         gameInfo.resultDetails = NULL;
16106     }
16107     ClearGameInfo(&gameInfo);
16108     gameInfo.variant = v;
16109
16110     switch (gameMode) {
16111       case MachinePlaysWhite:
16112         gameInfo.event = StrSave( appData.pgnEventHeader );
16113         gameInfo.site = StrSave(HostName());
16114         gameInfo.date = PGNDate();
16115         gameInfo.round = StrSave("-");
16116         gameInfo.white = StrSave(first.tidy);
16117         gameInfo.black = StrSave(UserName());
16118         gameInfo.timeControl = TimeControlTagValue();
16119         break;
16120
16121       case MachinePlaysBlack:
16122         gameInfo.event = StrSave( appData.pgnEventHeader );
16123         gameInfo.site = StrSave(HostName());
16124         gameInfo.date = PGNDate();
16125         gameInfo.round = StrSave("-");
16126         gameInfo.white = StrSave(UserName());
16127         gameInfo.black = StrSave(first.tidy);
16128         gameInfo.timeControl = TimeControlTagValue();
16129         break;
16130
16131       case TwoMachinesPlay:
16132         gameInfo.event = StrSave( appData.pgnEventHeader );
16133         gameInfo.site = StrSave(HostName());
16134         gameInfo.date = PGNDate();
16135         if (roundNr > 0) {
16136             char buf[MSG_SIZ];
16137             snprintf(buf, MSG_SIZ, "%d", roundNr);
16138             gameInfo.round = StrSave(buf);
16139         } else {
16140             gameInfo.round = StrSave("-");
16141         }
16142         if (first.twoMachinesColor[0] == 'w') {
16143             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16144             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16145         } else {
16146             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16147             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16148         }
16149         gameInfo.timeControl = TimeControlTagValue();
16150         break;
16151
16152       case EditGame:
16153         gameInfo.event = StrSave("Edited game");
16154         gameInfo.site = StrSave(HostName());
16155         gameInfo.date = PGNDate();
16156         gameInfo.round = StrSave("-");
16157         gameInfo.white = StrSave("-");
16158         gameInfo.black = StrSave("-");
16159         gameInfo.result = r;
16160         gameInfo.resultDetails = p;
16161         break;
16162
16163       case EditPosition:
16164         gameInfo.event = StrSave("Edited position");
16165         gameInfo.site = StrSave(HostName());
16166         gameInfo.date = PGNDate();
16167         gameInfo.round = StrSave("-");
16168         gameInfo.white = StrSave("-");
16169         gameInfo.black = StrSave("-");
16170         break;
16171
16172       case IcsPlayingWhite:
16173       case IcsPlayingBlack:
16174       case IcsObserving:
16175       case IcsExamining:
16176         break;
16177
16178       case PlayFromGameFile:
16179         gameInfo.event = StrSave("Game from non-PGN file");
16180         gameInfo.site = StrSave(HostName());
16181         gameInfo.date = PGNDate();
16182         gameInfo.round = StrSave("-");
16183         gameInfo.white = StrSave("?");
16184         gameInfo.black = StrSave("?");
16185         break;
16186
16187       default:
16188         break;
16189     }
16190 }
16191
16192 void
16193 ReplaceComment (int index, char *text)
16194 {
16195     int len;
16196     char *p;
16197     float score;
16198
16199     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16200        pvInfoList[index-1].depth == len &&
16201        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16202        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16203     while (*text == '\n') text++;
16204     len = strlen(text);
16205     while (len > 0 && text[len - 1] == '\n') len--;
16206
16207     if (commentList[index] != NULL)
16208       free(commentList[index]);
16209
16210     if (len == 0) {
16211         commentList[index] = NULL;
16212         return;
16213     }
16214   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16215       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16216       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16217     commentList[index] = (char *) malloc(len + 2);
16218     strncpy(commentList[index], text, len);
16219     commentList[index][len] = '\n';
16220     commentList[index][len + 1] = NULLCHAR;
16221   } else {
16222     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16223     char *p;
16224     commentList[index] = (char *) malloc(len + 7);
16225     safeStrCpy(commentList[index], "{\n", 3);
16226     safeStrCpy(commentList[index]+2, text, len+1);
16227     commentList[index][len+2] = NULLCHAR;
16228     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16229     strcat(commentList[index], "\n}\n");
16230   }
16231 }
16232
16233 void
16234 CrushCRs (char *text)
16235 {
16236   char *p = text;
16237   char *q = text;
16238   char ch;
16239
16240   do {
16241     ch = *p++;
16242     if (ch == '\r') continue;
16243     *q++ = ch;
16244   } while (ch != '\0');
16245 }
16246
16247 void
16248 AppendComment (int index, char *text, Boolean addBraces)
16249 /* addBraces  tells if we should add {} */
16250 {
16251     int oldlen, len;
16252     char *old;
16253
16254 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16255     if(addBraces == 3) addBraces = 0; else // force appending literally
16256     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16257
16258     CrushCRs(text);
16259     while (*text == '\n') text++;
16260     len = strlen(text);
16261     while (len > 0 && text[len - 1] == '\n') len--;
16262     text[len] = NULLCHAR;
16263
16264     if (len == 0) return;
16265
16266     if (commentList[index] != NULL) {
16267       Boolean addClosingBrace = addBraces;
16268         old = commentList[index];
16269         oldlen = strlen(old);
16270         while(commentList[index][oldlen-1] ==  '\n')
16271           commentList[index][--oldlen] = NULLCHAR;
16272         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16273         safeStrCpy(commentList[index], old, oldlen + len + 6);
16274         free(old);
16275         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16276         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16277           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16278           while (*text == '\n') { text++; len--; }
16279           commentList[index][--oldlen] = NULLCHAR;
16280       }
16281         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16282         else          strcat(commentList[index], "\n");
16283         strcat(commentList[index], text);
16284         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16285         else          strcat(commentList[index], "\n");
16286     } else {
16287         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16288         if(addBraces)
16289           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16290         else commentList[index][0] = NULLCHAR;
16291         strcat(commentList[index], text);
16292         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16293         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16294     }
16295 }
16296
16297 static char *
16298 FindStr (char * text, char * sub_text)
16299 {
16300     char * result = strstr( text, sub_text );
16301
16302     if( result != NULL ) {
16303         result += strlen( sub_text );
16304     }
16305
16306     return result;
16307 }
16308
16309 /* [AS] Try to extract PV info from PGN comment */
16310 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16311 char *
16312 GetInfoFromComment (int index, char * text)
16313 {
16314     char * sep = text, *p;
16315
16316     if( text != NULL && index > 0 ) {
16317         int score = 0;
16318         int depth = 0;
16319         int time = -1, sec = 0, deci;
16320         char * s_eval = FindStr( text, "[%eval " );
16321         char * s_emt = FindStr( text, "[%emt " );
16322 #if 0
16323         if( s_eval != NULL || s_emt != NULL ) {
16324 #else
16325         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16326 #endif
16327             /* New style */
16328             char delim;
16329
16330             if( s_eval != NULL ) {
16331                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16332                     return text;
16333                 }
16334
16335                 if( delim != ']' ) {
16336                     return text;
16337                 }
16338             }
16339
16340             if( s_emt != NULL ) {
16341             }
16342                 return text;
16343         }
16344         else {
16345             /* We expect something like: [+|-]nnn.nn/dd */
16346             int score_lo = 0;
16347
16348             if(*text != '{') return text; // [HGM] braces: must be normal comment
16349
16350             sep = strchr( text, '/' );
16351             if( sep == NULL || sep < (text+4) ) {
16352                 return text;
16353             }
16354
16355             p = text;
16356             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16357             if(p[1] == '(') { // comment starts with PV
16358                p = strchr(p, ')'); // locate end of PV
16359                if(p == NULL || sep < p+5) return text;
16360                // at this point we have something like "{(.*) +0.23/6 ..."
16361                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16362                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16363                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16364             }
16365             time = -1; sec = -1; deci = -1;
16366             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16367                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16368                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16369                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16370                 return text;
16371             }
16372
16373             if( score_lo < 0 || score_lo >= 100 ) {
16374                 return text;
16375             }
16376
16377             if(sec >= 0) time = 600*time + 10*sec; else
16378             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16379
16380             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16381
16382             /* [HGM] PV time: now locate end of PV info */
16383             while( *++sep >= '0' && *sep <= '9'); // strip depth
16384             if(time >= 0)
16385             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16386             if(sec >= 0)
16387             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16388             if(deci >= 0)
16389             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16390             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16391         }
16392
16393         if( depth <= 0 ) {
16394             return text;
16395         }
16396
16397         if( time < 0 ) {
16398             time = -1;
16399         }
16400
16401         pvInfoList[index-1].depth = depth;
16402         pvInfoList[index-1].score = score;
16403         pvInfoList[index-1].time  = 10*time; // centi-sec
16404         if(*sep == '}') *sep = 0; else *--sep = '{';
16405         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16406     }
16407     return sep;
16408 }
16409
16410 void
16411 SendToProgram (char *message, ChessProgramState *cps)
16412 {
16413     int count, outCount, error;
16414     char buf[MSG_SIZ];
16415
16416     if (cps->pr == NoProc) return;
16417     Attention(cps);
16418
16419     if (appData.debugMode) {
16420         TimeMark now;
16421         GetTimeMark(&now);
16422         fprintf(debugFP, "%ld >%-6s: %s",
16423                 SubtractTimeMarks(&now, &programStartTime),
16424                 cps->which, message);
16425         if(serverFP)
16426             fprintf(serverFP, "%ld >%-6s: %s",
16427                 SubtractTimeMarks(&now, &programStartTime),
16428                 cps->which, message), fflush(serverFP);
16429     }
16430
16431     count = strlen(message);
16432     outCount = OutputToProcess(cps->pr, message, count, &error);
16433     if (outCount < count && !exiting
16434                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16435       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16436       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16437         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16438             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16439                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16440                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16441                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16442             } else {
16443                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16444                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16445                 gameInfo.result = res;
16446             }
16447             gameInfo.resultDetails = StrSave(buf);
16448         }
16449         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16450         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16451     }
16452 }
16453
16454 void
16455 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16456 {
16457     char *end_str;
16458     char buf[MSG_SIZ];
16459     ChessProgramState *cps = (ChessProgramState *)closure;
16460
16461     if (isr != cps->isr) return; /* Killed intentionally */
16462     if (count <= 0) {
16463         if (count == 0) {
16464             RemoveInputSource(cps->isr);
16465             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16466                     _(cps->which), cps->program);
16467             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16468             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16469                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16470                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16471                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16472                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16473                 } else {
16474                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16475                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16476                     gameInfo.result = res;
16477                 }
16478                 gameInfo.resultDetails = StrSave(buf);
16479             }
16480             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16481             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16482         } else {
16483             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16484                     _(cps->which), cps->program);
16485             RemoveInputSource(cps->isr);
16486
16487             /* [AS] Program is misbehaving badly... kill it */
16488             if( count == -2 ) {
16489                 DestroyChildProcess( cps->pr, 9 );
16490                 cps->pr = NoProc;
16491             }
16492
16493             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16494         }
16495         return;
16496     }
16497
16498     if ((end_str = strchr(message, '\r')) != NULL)
16499       *end_str = NULLCHAR;
16500     if ((end_str = strchr(message, '\n')) != NULL)
16501       *end_str = NULLCHAR;
16502
16503     if (appData.debugMode) {
16504         TimeMark now; int print = 1;
16505         char *quote = ""; char c; int i;
16506
16507         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16508                 char start = message[0];
16509                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16510                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16511                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16512                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16513                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16514                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16515                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16516                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16517                    sscanf(message, "hint: %c", &c)!=1 &&
16518                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16519                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16520                     print = (appData.engineComments >= 2);
16521                 }
16522                 message[0] = start; // restore original message
16523         }
16524         if(print) {
16525                 GetTimeMark(&now);
16526                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16527                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16528                         quote,
16529                         message);
16530                 if(serverFP)
16531                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16532                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16533                         quote,
16534                         message), fflush(serverFP);
16535         }
16536     }
16537
16538     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16539     if (appData.icsEngineAnalyze) {
16540         if (strstr(message, "whisper") != NULL ||
16541              strstr(message, "kibitz") != NULL ||
16542             strstr(message, "tellics") != NULL) return;
16543     }
16544
16545     HandleMachineMove(message, cps);
16546 }
16547
16548
16549 void
16550 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16551 {
16552     char buf[MSG_SIZ];
16553     int seconds;
16554
16555     if( timeControl_2 > 0 ) {
16556         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16557             tc = timeControl_2;
16558         }
16559     }
16560     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16561     inc /= cps->timeOdds;
16562     st  /= cps->timeOdds;
16563
16564     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16565
16566     if (st > 0) {
16567       /* Set exact time per move, normally using st command */
16568       if (cps->stKludge) {
16569         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16570         seconds = st % 60;
16571         if (seconds == 0) {
16572           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16573         } else {
16574           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16575         }
16576       } else {
16577         snprintf(buf, MSG_SIZ, "st %d\n", st);
16578       }
16579     } else {
16580       /* Set conventional or incremental time control, using level command */
16581       if (seconds == 0) {
16582         /* Note old gnuchess bug -- minutes:seconds used to not work.
16583            Fixed in later versions, but still avoid :seconds
16584            when seconds is 0. */
16585         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16586       } else {
16587         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16588                  seconds, inc/1000.);
16589       }
16590     }
16591     SendToProgram(buf, cps);
16592
16593     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16594     /* Orthogonally, limit search to given depth */
16595     if (sd > 0) {
16596       if (cps->sdKludge) {
16597         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16598       } else {
16599         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16600       }
16601       SendToProgram(buf, cps);
16602     }
16603
16604     if(cps->nps >= 0) { /* [HGM] nps */
16605         if(cps->supportsNPS == FALSE)
16606           cps->nps = -1; // don't use if engine explicitly says not supported!
16607         else {
16608           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16609           SendToProgram(buf, cps);
16610         }
16611     }
16612 }
16613
16614 ChessProgramState *
16615 WhitePlayer ()
16616 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16617 {
16618     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16619        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16620         return &second;
16621     return &first;
16622 }
16623
16624 void
16625 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16626 {
16627     char message[MSG_SIZ];
16628     long time, otime;
16629
16630     /* Note: this routine must be called when the clocks are stopped
16631        or when they have *just* been set or switched; otherwise
16632        it will be off by the time since the current tick started.
16633     */
16634     if (machineWhite) {
16635         time = whiteTimeRemaining / 10;
16636         otime = blackTimeRemaining / 10;
16637     } else {
16638         time = blackTimeRemaining / 10;
16639         otime = whiteTimeRemaining / 10;
16640     }
16641     /* [HGM] translate opponent's time by time-odds factor */
16642     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16643
16644     if (time <= 0) time = 1;
16645     if (otime <= 0) otime = 1;
16646
16647     snprintf(message, MSG_SIZ, "time %ld\n", time);
16648     SendToProgram(message, cps);
16649
16650     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16651     SendToProgram(message, cps);
16652 }
16653
16654 char *
16655 EngineDefinedVariant (ChessProgramState *cps, int n)
16656 {   // return name of n-th unknown variant that engine supports
16657     static char buf[MSG_SIZ];
16658     char *p, *s = cps->variants;
16659     if(!s) return NULL;
16660     do { // parse string from variants feature
16661       VariantClass v;
16662         p = strchr(s, ',');
16663         if(p) *p = NULLCHAR;
16664       v = StringToVariant(s);
16665       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16666         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16667             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16668                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16669                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16670                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16671             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16672         }
16673         if(p) *p++ = ',';
16674         if(n < 0) return buf;
16675     } while(s = p);
16676     return NULL;
16677 }
16678
16679 int
16680 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16681 {
16682   char buf[MSG_SIZ];
16683   int len = strlen(name);
16684   int val;
16685
16686   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16687     (*p) += len + 1;
16688     sscanf(*p, "%d", &val);
16689     *loc = (val != 0);
16690     while (**p && **p != ' ')
16691       (*p)++;
16692     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16693     SendToProgram(buf, cps);
16694     return TRUE;
16695   }
16696   return FALSE;
16697 }
16698
16699 int
16700 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16701 {
16702   char buf[MSG_SIZ];
16703   int len = strlen(name);
16704   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16705     (*p) += len + 1;
16706     sscanf(*p, "%d", loc);
16707     while (**p && **p != ' ') (*p)++;
16708     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16709     SendToProgram(buf, cps);
16710     return TRUE;
16711   }
16712   return FALSE;
16713 }
16714
16715 int
16716 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16717 {
16718   char buf[MSG_SIZ];
16719   int len = strlen(name);
16720   if (strncmp((*p), name, len) == 0
16721       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16722     (*p) += len + 2;
16723     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16724     sscanf(*p, "%[^\"]", *loc);
16725     while (**p && **p != '\"') (*p)++;
16726     if (**p == '\"') (*p)++;
16727     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16728     SendToProgram(buf, cps);
16729     return TRUE;
16730   }
16731   return FALSE;
16732 }
16733
16734 int
16735 ParseOption (Option *opt, ChessProgramState *cps)
16736 // [HGM] options: process the string that defines an engine option, and determine
16737 // name, type, default value, and allowed value range
16738 {
16739         char *p, *q, buf[MSG_SIZ];
16740         int n, min = (-1)<<31, max = 1<<31, def;
16741
16742         if(p = strstr(opt->name, " -spin ")) {
16743             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16744             if(max < min) max = min; // enforce consistency
16745             if(def < min) def = min;
16746             if(def > max) def = max;
16747             opt->value = def;
16748             opt->min = min;
16749             opt->max = max;
16750             opt->type = Spin;
16751         } else if((p = strstr(opt->name, " -slider "))) {
16752             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16753             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16754             if(max < min) max = min; // enforce consistency
16755             if(def < min) def = min;
16756             if(def > max) def = max;
16757             opt->value = def;
16758             opt->min = min;
16759             opt->max = max;
16760             opt->type = Spin; // Slider;
16761         } else if((p = strstr(opt->name, " -string "))) {
16762             opt->textValue = p+9;
16763             opt->type = TextBox;
16764         } else if((p = strstr(opt->name, " -file "))) {
16765             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16766             opt->textValue = p+7;
16767             opt->type = FileName; // FileName;
16768         } else if((p = strstr(opt->name, " -path "))) {
16769             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16770             opt->textValue = p+7;
16771             opt->type = PathName; // PathName;
16772         } else if(p = strstr(opt->name, " -check ")) {
16773             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16774             opt->value = (def != 0);
16775             opt->type = CheckBox;
16776         } else if(p = strstr(opt->name, " -combo ")) {
16777             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16778             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16779             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16780             opt->value = n = 0;
16781             while(q = StrStr(q, " /// ")) {
16782                 n++; *q = 0;    // count choices, and null-terminate each of them
16783                 q += 5;
16784                 if(*q == '*') { // remember default, which is marked with * prefix
16785                     q++;
16786                     opt->value = n;
16787                 }
16788                 cps->comboList[cps->comboCnt++] = q;
16789             }
16790             cps->comboList[cps->comboCnt++] = NULL;
16791             opt->max = n + 1;
16792             opt->type = ComboBox;
16793         } else if(p = strstr(opt->name, " -button")) {
16794             opt->type = Button;
16795         } else if(p = strstr(opt->name, " -save")) {
16796             opt->type = SaveButton;
16797         } else return FALSE;
16798         *p = 0; // terminate option name
16799         // now look if the command-line options define a setting for this engine option.
16800         if(cps->optionSettings && cps->optionSettings[0])
16801             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16802         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16803           snprintf(buf, MSG_SIZ, "option %s", p);
16804                 if(p = strstr(buf, ",")) *p = 0;
16805                 if(q = strchr(buf, '=')) switch(opt->type) {
16806                     case ComboBox:
16807                         for(n=0; n<opt->max; n++)
16808                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16809                         break;
16810                     case TextBox:
16811                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16812                         break;
16813                     case Spin:
16814                     case CheckBox:
16815                         opt->value = atoi(q+1);
16816                     default:
16817                         break;
16818                 }
16819                 strcat(buf, "\n");
16820                 SendToProgram(buf, cps);
16821         }
16822         return TRUE;
16823 }
16824
16825 void
16826 FeatureDone (ChessProgramState *cps, int val)
16827 {
16828   DelayedEventCallback cb = GetDelayedEvent();
16829   if ((cb == InitBackEnd3 && cps == &first) ||
16830       (cb == SettingsMenuIfReady && cps == &second) ||
16831       (cb == LoadEngine) ||
16832       (cb == TwoMachinesEventIfReady)) {
16833     CancelDelayedEvent();
16834     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16835   }
16836   cps->initDone = val;
16837   if(val) cps->reload = FALSE;
16838 }
16839
16840 /* Parse feature command from engine */
16841 void
16842 ParseFeatures (char *args, ChessProgramState *cps)
16843 {
16844   char *p = args;
16845   char *q = NULL;
16846   int val;
16847   char buf[MSG_SIZ];
16848
16849   for (;;) {
16850     while (*p == ' ') p++;
16851     if (*p == NULLCHAR) return;
16852
16853     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16854     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16855     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16856     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16857     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16858     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16859     if (BoolFeature(&p, "reuse", &val, cps)) {
16860       /* Engine can disable reuse, but can't enable it if user said no */
16861       if (!val) cps->reuse = FALSE;
16862       continue;
16863     }
16864     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16865     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16866       if (gameMode == TwoMachinesPlay) {
16867         DisplayTwoMachinesTitle();
16868       } else {
16869         DisplayTitle("");
16870       }
16871       continue;
16872     }
16873     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16874     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16875     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16876     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16877     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16878     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16879     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16880     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16881     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16882     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16883     if (IntFeature(&p, "done", &val, cps)) {
16884       FeatureDone(cps, val);
16885       continue;
16886     }
16887     /* Added by Tord: */
16888     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16889     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16890     /* End of additions by Tord */
16891
16892     /* [HGM] added features: */
16893     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16894     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16895     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16896     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16897     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16898     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16899     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16900     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16901         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16902         FREE(cps->option[cps->nrOptions].name);
16903         cps->option[cps->nrOptions].name = q; q = NULL;
16904         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16905           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16906             SendToProgram(buf, cps);
16907             continue;
16908         }
16909         if(cps->nrOptions >= MAX_OPTIONS) {
16910             cps->nrOptions--;
16911             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16912             DisplayError(buf, 0);
16913         }
16914         continue;
16915     }
16916     /* End of additions by HGM */
16917
16918     /* unknown feature: complain and skip */
16919     q = p;
16920     while (*q && *q != '=') q++;
16921     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16922     SendToProgram(buf, cps);
16923     p = q;
16924     if (*p == '=') {
16925       p++;
16926       if (*p == '\"') {
16927         p++;
16928         while (*p && *p != '\"') p++;
16929         if (*p == '\"') p++;
16930       } else {
16931         while (*p && *p != ' ') p++;
16932       }
16933     }
16934   }
16935
16936 }
16937
16938 void
16939 PeriodicUpdatesEvent (int newState)
16940 {
16941     if (newState == appData.periodicUpdates)
16942       return;
16943
16944     appData.periodicUpdates=newState;
16945
16946     /* Display type changes, so update it now */
16947 //    DisplayAnalysis();
16948
16949     /* Get the ball rolling again... */
16950     if (newState) {
16951         AnalysisPeriodicEvent(1);
16952         StartAnalysisClock();
16953     }
16954 }
16955
16956 void
16957 PonderNextMoveEvent (int newState)
16958 {
16959     if (newState == appData.ponderNextMove) return;
16960     if (gameMode == EditPosition) EditPositionDone(TRUE);
16961     if (newState) {
16962         SendToProgram("hard\n", &first);
16963         if (gameMode == TwoMachinesPlay) {
16964             SendToProgram("hard\n", &second);
16965         }
16966     } else {
16967         SendToProgram("easy\n", &first);
16968         thinkOutput[0] = NULLCHAR;
16969         if (gameMode == TwoMachinesPlay) {
16970             SendToProgram("easy\n", &second);
16971         }
16972     }
16973     appData.ponderNextMove = newState;
16974 }
16975
16976 void
16977 NewSettingEvent (int option, int *feature, char *command, int value)
16978 {
16979     char buf[MSG_SIZ];
16980
16981     if (gameMode == EditPosition) EditPositionDone(TRUE);
16982     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16983     if(feature == NULL || *feature) SendToProgram(buf, &first);
16984     if (gameMode == TwoMachinesPlay) {
16985         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16986     }
16987 }
16988
16989 void
16990 ShowThinkingEvent ()
16991 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16992 {
16993     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16994     int newState = appData.showThinking
16995         // [HGM] thinking: other features now need thinking output as well
16996         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16997
16998     if (oldState == newState) return;
16999     oldState = newState;
17000     if (gameMode == EditPosition) EditPositionDone(TRUE);
17001     if (oldState) {
17002         SendToProgram("post\n", &first);
17003         if (gameMode == TwoMachinesPlay) {
17004             SendToProgram("post\n", &second);
17005         }
17006     } else {
17007         SendToProgram("nopost\n", &first);
17008         thinkOutput[0] = NULLCHAR;
17009         if (gameMode == TwoMachinesPlay) {
17010             SendToProgram("nopost\n", &second);
17011         }
17012     }
17013 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17014 }
17015
17016 void
17017 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17018 {
17019   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17020   if (pr == NoProc) return;
17021   AskQuestion(title, question, replyPrefix, pr);
17022 }
17023
17024 void
17025 TypeInEvent (char firstChar)
17026 {
17027     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17028         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17029         gameMode == AnalyzeMode || gameMode == EditGame ||
17030         gameMode == EditPosition || gameMode == IcsExamining ||
17031         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17032         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17033                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17034                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17035         gameMode == Training) PopUpMoveDialog(firstChar);
17036 }
17037
17038 void
17039 TypeInDoneEvent (char *move)
17040 {
17041         Board board;
17042         int n, fromX, fromY, toX, toY;
17043         char promoChar;
17044         ChessMove moveType;
17045
17046         // [HGM] FENedit
17047         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17048                 EditPositionPasteFEN(move);
17049                 return;
17050         }
17051         // [HGM] movenum: allow move number to be typed in any mode
17052         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17053           ToNrEvent(2*n-1);
17054           return;
17055         }
17056         // undocumented kludge: allow command-line option to be typed in!
17057         // (potentially fatal, and does not implement the effect of the option.)
17058         // should only be used for options that are values on which future decisions will be made,
17059         // and definitely not on options that would be used during initialization.
17060         if(strstr(move, "!!! -") == move) {
17061             ParseArgsFromString(move+4);
17062             return;
17063         }
17064
17065       if (gameMode != EditGame && currentMove != forwardMostMove &&
17066         gameMode != Training) {
17067         DisplayMoveError(_("Displayed move is not current"));
17068       } else {
17069         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17070           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17071         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17072         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17073           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17074           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17075         } else {
17076           DisplayMoveError(_("Could not parse move"));
17077         }
17078       }
17079 }
17080
17081 void
17082 DisplayMove (int moveNumber)
17083 {
17084     char message[MSG_SIZ];
17085     char res[MSG_SIZ];
17086     char cpThinkOutput[MSG_SIZ];
17087
17088     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17089
17090     if (moveNumber == forwardMostMove - 1 ||
17091         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17092
17093         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17094
17095         if (strchr(cpThinkOutput, '\n')) {
17096             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17097         }
17098     } else {
17099         *cpThinkOutput = NULLCHAR;
17100     }
17101
17102     /* [AS] Hide thinking from human user */
17103     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17104         *cpThinkOutput = NULLCHAR;
17105         if( thinkOutput[0] != NULLCHAR ) {
17106             int i;
17107
17108             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17109                 cpThinkOutput[i] = '.';
17110             }
17111             cpThinkOutput[i] = NULLCHAR;
17112             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17113         }
17114     }
17115
17116     if (moveNumber == forwardMostMove - 1 &&
17117         gameInfo.resultDetails != NULL) {
17118         if (gameInfo.resultDetails[0] == NULLCHAR) {
17119           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17120         } else {
17121           snprintf(res, MSG_SIZ, " {%s} %s",
17122                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17123         }
17124     } else {
17125         res[0] = NULLCHAR;
17126     }
17127
17128     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17129         DisplayMessage(res, cpThinkOutput);
17130     } else {
17131       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17132                 WhiteOnMove(moveNumber) ? " " : ".. ",
17133                 parseList[moveNumber], res);
17134         DisplayMessage(message, cpThinkOutput);
17135     }
17136 }
17137
17138 void
17139 DisplayComment (int moveNumber, char *text)
17140 {
17141     char title[MSG_SIZ];
17142
17143     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17144       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17145     } else {
17146       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17147               WhiteOnMove(moveNumber) ? " " : ".. ",
17148               parseList[moveNumber]);
17149     }
17150     if (text != NULL && (appData.autoDisplayComment || commentUp))
17151         CommentPopUp(title, text);
17152 }
17153
17154 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17155  * might be busy thinking or pondering.  It can be omitted if your
17156  * gnuchess is configured to stop thinking immediately on any user
17157  * input.  However, that gnuchess feature depends on the FIONREAD
17158  * ioctl, which does not work properly on some flavors of Unix.
17159  */
17160 void
17161 Attention (ChessProgramState *cps)
17162 {
17163 #if ATTENTION
17164     if (!cps->useSigint) return;
17165     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17166     switch (gameMode) {
17167       case MachinePlaysWhite:
17168       case MachinePlaysBlack:
17169       case TwoMachinesPlay:
17170       case IcsPlayingWhite:
17171       case IcsPlayingBlack:
17172       case AnalyzeMode:
17173       case AnalyzeFile:
17174         /* Skip if we know it isn't thinking */
17175         if (!cps->maybeThinking) return;
17176         if (appData.debugMode)
17177           fprintf(debugFP, "Interrupting %s\n", cps->which);
17178         InterruptChildProcess(cps->pr);
17179         cps->maybeThinking = FALSE;
17180         break;
17181       default:
17182         break;
17183     }
17184 #endif /*ATTENTION*/
17185 }
17186
17187 int
17188 CheckFlags ()
17189 {
17190     if (whiteTimeRemaining <= 0) {
17191         if (!whiteFlag) {
17192             whiteFlag = TRUE;
17193             if (appData.icsActive) {
17194                 if (appData.autoCallFlag &&
17195                     gameMode == IcsPlayingBlack && !blackFlag) {
17196                   SendToICS(ics_prefix);
17197                   SendToICS("flag\n");
17198                 }
17199             } else {
17200                 if (blackFlag) {
17201                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17202                 } else {
17203                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17204                     if (appData.autoCallFlag) {
17205                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17206                         return TRUE;
17207                     }
17208                 }
17209             }
17210         }
17211     }
17212     if (blackTimeRemaining <= 0) {
17213         if (!blackFlag) {
17214             blackFlag = TRUE;
17215             if (appData.icsActive) {
17216                 if (appData.autoCallFlag &&
17217                     gameMode == IcsPlayingWhite && !whiteFlag) {
17218                   SendToICS(ics_prefix);
17219                   SendToICS("flag\n");
17220                 }
17221             } else {
17222                 if (whiteFlag) {
17223                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17224                 } else {
17225                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17226                     if (appData.autoCallFlag) {
17227                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17228                         return TRUE;
17229                     }
17230                 }
17231             }
17232         }
17233     }
17234     return FALSE;
17235 }
17236
17237 void
17238 CheckTimeControl ()
17239 {
17240     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17241         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17242
17243     /*
17244      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17245      */
17246     if ( !WhiteOnMove(forwardMostMove) ) {
17247         /* White made time control */
17248         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17249         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17250         /* [HGM] time odds: correct new time quota for time odds! */
17251                                             / WhitePlayer()->timeOdds;
17252         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17253     } else {
17254         lastBlack -= blackTimeRemaining;
17255         /* Black made time control */
17256         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17257                                             / WhitePlayer()->other->timeOdds;
17258         lastWhite = whiteTimeRemaining;
17259     }
17260 }
17261
17262 void
17263 DisplayBothClocks ()
17264 {
17265     int wom = gameMode == EditPosition ?
17266       !blackPlaysFirst : WhiteOnMove(currentMove);
17267     DisplayWhiteClock(whiteTimeRemaining, wom);
17268     DisplayBlackClock(blackTimeRemaining, !wom);
17269 }
17270
17271
17272 /* Timekeeping seems to be a portability nightmare.  I think everyone
17273    has ftime(), but I'm really not sure, so I'm including some ifdefs
17274    to use other calls if you don't.  Clocks will be less accurate if
17275    you have neither ftime nor gettimeofday.
17276 */
17277
17278 /* VS 2008 requires the #include outside of the function */
17279 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17280 #include <sys/timeb.h>
17281 #endif
17282
17283 /* Get the current time as a TimeMark */
17284 void
17285 GetTimeMark (TimeMark *tm)
17286 {
17287 #if HAVE_GETTIMEOFDAY
17288
17289     struct timeval timeVal;
17290     struct timezone timeZone;
17291
17292     gettimeofday(&timeVal, &timeZone);
17293     tm->sec = (long) timeVal.tv_sec;
17294     tm->ms = (int) (timeVal.tv_usec / 1000L);
17295
17296 #else /*!HAVE_GETTIMEOFDAY*/
17297 #if HAVE_FTIME
17298
17299 // include <sys/timeb.h> / moved to just above start of function
17300     struct timeb timeB;
17301
17302     ftime(&timeB);
17303     tm->sec = (long) timeB.time;
17304     tm->ms = (int) timeB.millitm;
17305
17306 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17307     tm->sec = (long) time(NULL);
17308     tm->ms = 0;
17309 #endif
17310 #endif
17311 }
17312
17313 /* Return the difference in milliseconds between two
17314    time marks.  We assume the difference will fit in a long!
17315 */
17316 long
17317 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17318 {
17319     return 1000L*(tm2->sec - tm1->sec) +
17320            (long) (tm2->ms - tm1->ms);
17321 }
17322
17323
17324 /*
17325  * Code to manage the game clocks.
17326  *
17327  * In tournament play, black starts the clock and then white makes a move.
17328  * We give the human user a slight advantage if he is playing white---the
17329  * clocks don't run until he makes his first move, so it takes zero time.
17330  * Also, we don't account for network lag, so we could get out of sync
17331  * with GNU Chess's clock -- but then, referees are always right.
17332  */
17333
17334 static TimeMark tickStartTM;
17335 static long intendedTickLength;
17336
17337 long
17338 NextTickLength (long timeRemaining)
17339 {
17340     long nominalTickLength, nextTickLength;
17341
17342     if (timeRemaining > 0L && timeRemaining <= 10000L)
17343       nominalTickLength = 100L;
17344     else
17345       nominalTickLength = 1000L;
17346     nextTickLength = timeRemaining % nominalTickLength;
17347     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17348
17349     return nextTickLength;
17350 }
17351
17352 /* Adjust clock one minute up or down */
17353 void
17354 AdjustClock (Boolean which, int dir)
17355 {
17356     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17357     if(which) blackTimeRemaining += 60000*dir;
17358     else      whiteTimeRemaining += 60000*dir;
17359     DisplayBothClocks();
17360     adjustedClock = TRUE;
17361 }
17362
17363 /* Stop clocks and reset to a fresh time control */
17364 void
17365 ResetClocks ()
17366 {
17367     (void) StopClockTimer();
17368     if (appData.icsActive) {
17369         whiteTimeRemaining = blackTimeRemaining = 0;
17370     } else if (searchTime) {
17371         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17372         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17373     } else { /* [HGM] correct new time quote for time odds */
17374         whiteTC = blackTC = fullTimeControlString;
17375         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17376         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17377     }
17378     if (whiteFlag || blackFlag) {
17379         DisplayTitle("");
17380         whiteFlag = blackFlag = FALSE;
17381     }
17382     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17383     DisplayBothClocks();
17384     adjustedClock = FALSE;
17385 }
17386
17387 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17388
17389 /* Decrement running clock by amount of time that has passed */
17390 void
17391 DecrementClocks ()
17392 {
17393     long timeRemaining;
17394     long lastTickLength, fudge;
17395     TimeMark now;
17396
17397     if (!appData.clockMode) return;
17398     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17399
17400     GetTimeMark(&now);
17401
17402     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17403
17404     /* Fudge if we woke up a little too soon */
17405     fudge = intendedTickLength - lastTickLength;
17406     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17407
17408     if (WhiteOnMove(forwardMostMove)) {
17409         if(whiteNPS >= 0) lastTickLength = 0;
17410         timeRemaining = whiteTimeRemaining -= lastTickLength;
17411         if(timeRemaining < 0 && !appData.icsActive) {
17412             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17413             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17414                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17415                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17416             }
17417         }
17418         DisplayWhiteClock(whiteTimeRemaining - fudge,
17419                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17420     } else {
17421         if(blackNPS >= 0) lastTickLength = 0;
17422         timeRemaining = blackTimeRemaining -= lastTickLength;
17423         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17424             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17425             if(suddenDeath) {
17426                 blackStartMove = forwardMostMove;
17427                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17428             }
17429         }
17430         DisplayBlackClock(blackTimeRemaining - fudge,
17431                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17432     }
17433     if (CheckFlags()) return;
17434
17435     if(twoBoards) { // count down secondary board's clocks as well
17436         activePartnerTime -= lastTickLength;
17437         partnerUp = 1;
17438         if(activePartner == 'W')
17439             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17440         else
17441             DisplayBlackClock(activePartnerTime, TRUE);
17442         partnerUp = 0;
17443     }
17444
17445     tickStartTM = now;
17446     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17447     StartClockTimer(intendedTickLength);
17448
17449     /* if the time remaining has fallen below the alarm threshold, sound the
17450      * alarm. if the alarm has sounded and (due to a takeback or time control
17451      * with increment) the time remaining has increased to a level above the
17452      * threshold, reset the alarm so it can sound again.
17453      */
17454
17455     if (appData.icsActive && appData.icsAlarm) {
17456
17457         /* make sure we are dealing with the user's clock */
17458         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17459                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17460            )) return;
17461
17462         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17463             alarmSounded = FALSE;
17464         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17465             PlayAlarmSound();
17466             alarmSounded = TRUE;
17467         }
17468     }
17469 }
17470
17471
17472 /* A player has just moved, so stop the previously running
17473    clock and (if in clock mode) start the other one.
17474    We redisplay both clocks in case we're in ICS mode, because
17475    ICS gives us an update to both clocks after every move.
17476    Note that this routine is called *after* forwardMostMove
17477    is updated, so the last fractional tick must be subtracted
17478    from the color that is *not* on move now.
17479 */
17480 void
17481 SwitchClocks (int newMoveNr)
17482 {
17483     long lastTickLength;
17484     TimeMark now;
17485     int flagged = FALSE;
17486
17487     GetTimeMark(&now);
17488
17489     if (StopClockTimer() && appData.clockMode) {
17490         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17491         if (!WhiteOnMove(forwardMostMove)) {
17492             if(blackNPS >= 0) lastTickLength = 0;
17493             blackTimeRemaining -= lastTickLength;
17494            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17495 //         if(pvInfoList[forwardMostMove].time == -1)
17496                  pvInfoList[forwardMostMove].time =               // use GUI time
17497                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17498         } else {
17499            if(whiteNPS >= 0) lastTickLength = 0;
17500            whiteTimeRemaining -= lastTickLength;
17501            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17502 //         if(pvInfoList[forwardMostMove].time == -1)
17503                  pvInfoList[forwardMostMove].time =
17504                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17505         }
17506         flagged = CheckFlags();
17507     }
17508     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17509     CheckTimeControl();
17510
17511     if (flagged || !appData.clockMode) return;
17512
17513     switch (gameMode) {
17514       case MachinePlaysBlack:
17515       case MachinePlaysWhite:
17516       case BeginningOfGame:
17517         if (pausing) return;
17518         break;
17519
17520       case EditGame:
17521       case PlayFromGameFile:
17522       case IcsExamining:
17523         return;
17524
17525       default:
17526         break;
17527     }
17528
17529     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17530         if(WhiteOnMove(forwardMostMove))
17531              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17532         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17533     }
17534
17535     tickStartTM = now;
17536     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17537       whiteTimeRemaining : blackTimeRemaining);
17538     StartClockTimer(intendedTickLength);
17539 }
17540
17541
17542 /* Stop both clocks */
17543 void
17544 StopClocks ()
17545 {
17546     long lastTickLength;
17547     TimeMark now;
17548
17549     if (!StopClockTimer()) return;
17550     if (!appData.clockMode) return;
17551
17552     GetTimeMark(&now);
17553
17554     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17555     if (WhiteOnMove(forwardMostMove)) {
17556         if(whiteNPS >= 0) lastTickLength = 0;
17557         whiteTimeRemaining -= lastTickLength;
17558         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17559     } else {
17560         if(blackNPS >= 0) lastTickLength = 0;
17561         blackTimeRemaining -= lastTickLength;
17562         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17563     }
17564     CheckFlags();
17565 }
17566
17567 /* Start clock of player on move.  Time may have been reset, so
17568    if clock is already running, stop and restart it. */
17569 void
17570 StartClocks ()
17571 {
17572     (void) StopClockTimer(); /* in case it was running already */
17573     DisplayBothClocks();
17574     if (CheckFlags()) return;
17575
17576     if (!appData.clockMode) return;
17577     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17578
17579     GetTimeMark(&tickStartTM);
17580     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17581       whiteTimeRemaining : blackTimeRemaining);
17582
17583    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17584     whiteNPS = blackNPS = -1;
17585     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17586        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17587         whiteNPS = first.nps;
17588     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17589        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17590         blackNPS = first.nps;
17591     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17592         whiteNPS = second.nps;
17593     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17594         blackNPS = second.nps;
17595     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17596
17597     StartClockTimer(intendedTickLength);
17598 }
17599
17600 char *
17601 TimeString (long ms)
17602 {
17603     long second, minute, hour, day;
17604     char *sign = "";
17605     static char buf[32];
17606
17607     if (ms > 0 && ms <= 9900) {
17608       /* convert milliseconds to tenths, rounding up */
17609       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17610
17611       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17612       return buf;
17613     }
17614
17615     /* convert milliseconds to seconds, rounding up */
17616     /* use floating point to avoid strangeness of integer division
17617        with negative dividends on many machines */
17618     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17619
17620     if (second < 0) {
17621         sign = "-";
17622         second = -second;
17623     }
17624
17625     day = second / (60 * 60 * 24);
17626     second = second % (60 * 60 * 24);
17627     hour = second / (60 * 60);
17628     second = second % (60 * 60);
17629     minute = second / 60;
17630     second = second % 60;
17631
17632     if (day > 0)
17633       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17634               sign, day, hour, minute, second);
17635     else if (hour > 0)
17636       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17637     else
17638       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17639
17640     return buf;
17641 }
17642
17643
17644 /*
17645  * This is necessary because some C libraries aren't ANSI C compliant yet.
17646  */
17647 char *
17648 StrStr (char *string, char *match)
17649 {
17650     int i, length;
17651
17652     length = strlen(match);
17653
17654     for (i = strlen(string) - length; i >= 0; i--, string++)
17655       if (!strncmp(match, string, length))
17656         return string;
17657
17658     return NULL;
17659 }
17660
17661 char *
17662 StrCaseStr (char *string, char *match)
17663 {
17664     int i, j, length;
17665
17666     length = strlen(match);
17667
17668     for (i = strlen(string) - length; i >= 0; i--, string++) {
17669         for (j = 0; j < length; j++) {
17670             if (ToLower(match[j]) != ToLower(string[j]))
17671               break;
17672         }
17673         if (j == length) return string;
17674     }
17675
17676     return NULL;
17677 }
17678
17679 #ifndef _amigados
17680 int
17681 StrCaseCmp (char *s1, char *s2)
17682 {
17683     char c1, c2;
17684
17685     for (;;) {
17686         c1 = ToLower(*s1++);
17687         c2 = ToLower(*s2++);
17688         if (c1 > c2) return 1;
17689         if (c1 < c2) return -1;
17690         if (c1 == NULLCHAR) return 0;
17691     }
17692 }
17693
17694
17695 int
17696 ToLower (int c)
17697 {
17698     return isupper(c) ? tolower(c) : c;
17699 }
17700
17701
17702 int
17703 ToUpper (int c)
17704 {
17705     return islower(c) ? toupper(c) : c;
17706 }
17707 #endif /* !_amigados    */
17708
17709 char *
17710 StrSave (char *s)
17711 {
17712   char *ret;
17713
17714   if ((ret = (char *) malloc(strlen(s) + 1)))
17715     {
17716       safeStrCpy(ret, s, strlen(s)+1);
17717     }
17718   return ret;
17719 }
17720
17721 char *
17722 StrSavePtr (char *s, char **savePtr)
17723 {
17724     if (*savePtr) {
17725         free(*savePtr);
17726     }
17727     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17728       safeStrCpy(*savePtr, s, strlen(s)+1);
17729     }
17730     return(*savePtr);
17731 }
17732
17733 char *
17734 PGNDate ()
17735 {
17736     time_t clock;
17737     struct tm *tm;
17738     char buf[MSG_SIZ];
17739
17740     clock = time((time_t *)NULL);
17741     tm = localtime(&clock);
17742     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17743             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17744     return StrSave(buf);
17745 }
17746
17747
17748 char *
17749 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17750 {
17751     int i, j, fromX, fromY, toX, toY;
17752     int whiteToPlay;
17753     char buf[MSG_SIZ];
17754     char *p, *q;
17755     int emptycount;
17756     ChessSquare piece;
17757
17758     whiteToPlay = (gameMode == EditPosition) ?
17759       !blackPlaysFirst : (move % 2 == 0);
17760     p = buf;
17761
17762     /* Piece placement data */
17763     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17764         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17765         emptycount = 0;
17766         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17767             if (boards[move][i][j] == EmptySquare) {
17768                 emptycount++;
17769             } else { ChessSquare piece = boards[move][i][j];
17770                 if (emptycount > 0) {
17771                     if(emptycount<10) /* [HGM] can be >= 10 */
17772                         *p++ = '0' + emptycount;
17773                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17774                     emptycount = 0;
17775                 }
17776                 if(PieceToChar(piece) == '+') {
17777                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17778                     *p++ = '+';
17779                     piece = (ChessSquare)(CHUDEMOTED piece);
17780                 }
17781                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17782                 if(p[-1] == '~') {
17783                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17784                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17785                     *p++ = '~';
17786                 }
17787             }
17788         }
17789         if (emptycount > 0) {
17790             if(emptycount<10) /* [HGM] can be >= 10 */
17791                 *p++ = '0' + emptycount;
17792             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17793             emptycount = 0;
17794         }
17795         *p++ = '/';
17796     }
17797     *(p - 1) = ' ';
17798
17799     /* [HGM] print Crazyhouse or Shogi holdings */
17800     if( gameInfo.holdingsWidth ) {
17801         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17802         q = p;
17803         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17804             piece = boards[move][i][BOARD_WIDTH-1];
17805             if( piece != EmptySquare )
17806               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17807                   *p++ = PieceToChar(piece);
17808         }
17809         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17810             piece = boards[move][BOARD_HEIGHT-i-1][0];
17811             if( piece != EmptySquare )
17812               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17813                   *p++ = PieceToChar(piece);
17814         }
17815
17816         if( q == p ) *p++ = '-';
17817         *p++ = ']';
17818         *p++ = ' ';
17819     }
17820
17821     /* Active color */
17822     *p++ = whiteToPlay ? 'w' : 'b';
17823     *p++ = ' ';
17824
17825   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17826     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17827   } else {
17828   if(nrCastlingRights) {
17829      int handW=0, handB=0;
17830      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17831         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17832         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17833      }
17834      q = p;
17835      if(appData.fischerCastling) {
17836         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17837            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17838                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17839         } else {
17840        /* [HGM] write directly from rights */
17841            if(boards[move][CASTLING][2] != NoRights &&
17842               boards[move][CASTLING][0] != NoRights   )
17843                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17844            if(boards[move][CASTLING][2] != NoRights &&
17845               boards[move][CASTLING][1] != NoRights   )
17846                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17847         }
17848         if(handB) {
17849            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17850                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17851         } else {
17852            if(boards[move][CASTLING][5] != NoRights &&
17853               boards[move][CASTLING][3] != NoRights   )
17854                 *p++ = boards[move][CASTLING][3] + AAA;
17855            if(boards[move][CASTLING][5] != NoRights &&
17856               boards[move][CASTLING][4] != NoRights   )
17857                 *p++ = boards[move][CASTLING][4] + AAA;
17858         }
17859      } else {
17860
17861         /* [HGM] write true castling rights */
17862         if( nrCastlingRights == 6 ) {
17863             int q, k=0;
17864             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17865                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17866             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17867                  boards[move][CASTLING][2] != NoRights  );
17868             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17869                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17870                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17871                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17872             }
17873             if(q) *p++ = 'Q';
17874             k = 0;
17875             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17876                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17877             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17878                  boards[move][CASTLING][5] != NoRights  );
17879             if(handB) {
17880                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17881                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17882                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17883             }
17884             if(q) *p++ = 'q';
17885         }
17886      }
17887      if (q == p) *p++ = '-'; /* No castling rights */
17888      *p++ = ' ';
17889   }
17890
17891   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17892      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17893      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17894     /* En passant target square */
17895     if (move > backwardMostMove) {
17896         fromX = moveList[move - 1][0] - AAA;
17897         fromY = moveList[move - 1][1] - ONE;
17898         toX = moveList[move - 1][2] - AAA;
17899         toY = moveList[move - 1][3] - ONE;
17900         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17901             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17902             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17903             fromX == toX) {
17904             /* 2-square pawn move just happened */
17905             *p++ = toX + AAA;
17906             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17907         } else {
17908             *p++ = '-';
17909         }
17910     } else if(move == backwardMostMove) {
17911         // [HGM] perhaps we should always do it like this, and forget the above?
17912         if((signed char)boards[move][EP_STATUS] >= 0) {
17913             *p++ = boards[move][EP_STATUS] + AAA;
17914             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17915         } else {
17916             *p++ = '-';
17917         }
17918     } else {
17919         *p++ = '-';
17920     }
17921     *p++ = ' ';
17922   }
17923   }
17924
17925     if(moveCounts)
17926     {   int i = 0, j=move;
17927
17928         /* [HGM] find reversible plies */
17929         if (appData.debugMode) { int k;
17930             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17931             for(k=backwardMostMove; k<=forwardMostMove; k++)
17932                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17933
17934         }
17935
17936         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17937         if( j == backwardMostMove ) i += initialRulePlies;
17938         sprintf(p, "%d ", i);
17939         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17940
17941         /* Fullmove number */
17942         sprintf(p, "%d", (move / 2) + 1);
17943     } else *--p = NULLCHAR;
17944
17945     return StrSave(buf);
17946 }
17947
17948 Boolean
17949 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17950 {
17951     int i, j, k, w=0, subst=0, shuffle=0;
17952     char *p, c;
17953     int emptycount, virgin[BOARD_FILES];
17954     ChessSquare piece;
17955
17956     p = fen;
17957
17958     /* Piece placement data */
17959     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17960         j = 0;
17961         for (;;) {
17962             if (*p == '/' || *p == ' ' || *p == '[' ) {
17963                 if(j > w) w = j;
17964                 emptycount = gameInfo.boardWidth - j;
17965                 while (emptycount--)
17966                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17967                 if (*p == '/') p++;
17968                 else if(autoSize) { // we stumbled unexpectedly into end of board
17969                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17970                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17971                     }
17972                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17973                 }
17974                 break;
17975 #if(BOARD_FILES >= 10)*0
17976             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17977                 p++; emptycount=10;
17978                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17979                 while (emptycount--)
17980                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17981 #endif
17982             } else if (*p == '*') {
17983                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17984             } else if (isdigit(*p)) {
17985                 emptycount = *p++ - '0';
17986                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17987                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17988                 while (emptycount--)
17989                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17990             } else if (*p == '<') {
17991                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17992                 else if (i != 0 || !shuffle) return FALSE;
17993                 p++;
17994             } else if (shuffle && *p == '>') {
17995                 p++; // for now ignore closing shuffle range, and assume rank-end
17996             } else if (*p == '?') {
17997                 if (j >= gameInfo.boardWidth) return FALSE;
17998                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17999                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18000             } else if (*p == '+' || isalpha(*p)) {
18001                 if (j >= gameInfo.boardWidth) return FALSE;
18002                 if(*p=='+') {
18003                     piece = CharToPiece(*++p);
18004                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18005                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18006                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18007                 } else piece = CharToPiece(*p++);
18008
18009                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18010                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18011                     piece = (ChessSquare) (PROMOTED piece);
18012                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18013                     p++;
18014                 }
18015                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18016             } else {
18017                 return FALSE;
18018             }
18019         }
18020     }
18021     while (*p == '/' || *p == ' ') p++;
18022
18023     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18024
18025     /* [HGM] by default clear Crazyhouse holdings, if present */
18026     if(gameInfo.holdingsWidth) {
18027        for(i=0; i<BOARD_HEIGHT; i++) {
18028            board[i][0]             = EmptySquare; /* black holdings */
18029            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18030            board[i][1]             = (ChessSquare) 0; /* black counts */
18031            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18032        }
18033     }
18034
18035     /* [HGM] look for Crazyhouse holdings here */
18036     while(*p==' ') p++;
18037     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18038         int swap=0, wcnt=0, bcnt=0;
18039         if(*p == '[') p++;
18040         if(*p == '<') swap++, p++;
18041         if(*p == '-' ) p++; /* empty holdings */ else {
18042             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18043             /* if we would allow FEN reading to set board size, we would   */
18044             /* have to add holdings and shift the board read so far here   */
18045             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18046                 p++;
18047                 if((int) piece >= (int) BlackPawn ) {
18048                     i = (int)piece - (int)BlackPawn;
18049                     i = PieceToNumber((ChessSquare)i);
18050                     if( i >= gameInfo.holdingsSize ) return FALSE;
18051                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18052                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18053                     bcnt++;
18054                 } else {
18055                     i = (int)piece - (int)WhitePawn;
18056                     i = PieceToNumber((ChessSquare)i);
18057                     if( i >= gameInfo.holdingsSize ) return FALSE;
18058                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18059                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18060                     wcnt++;
18061                 }
18062             }
18063             if(subst) { // substitute back-rank question marks by holdings pieces
18064                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18065                     int k, m, n = bcnt + 1;
18066                     if(board[0][j] == ClearBoard) {
18067                         if(!wcnt) return FALSE;
18068                         n = rand() % wcnt;
18069                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18070                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18071                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18072                             break;
18073                         }
18074                     }
18075                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18076                         if(!bcnt) return FALSE;
18077                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18078                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18079                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18080                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18081                             break;
18082                         }
18083                     }
18084                 }
18085                 subst = 0;
18086             }
18087         }
18088         if(*p == ']') p++;
18089     }
18090
18091     if(subst) return FALSE; // substitution requested, but no holdings
18092
18093     while(*p == ' ') p++;
18094
18095     /* Active color */
18096     c = *p++;
18097     if(appData.colorNickNames) {
18098       if( c == appData.colorNickNames[0] ) c = 'w'; else
18099       if( c == appData.colorNickNames[1] ) c = 'b';
18100     }
18101     switch (c) {
18102       case 'w':
18103         *blackPlaysFirst = FALSE;
18104         break;
18105       case 'b':
18106         *blackPlaysFirst = TRUE;
18107         break;
18108       default:
18109         return FALSE;
18110     }
18111
18112     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18113     /* return the extra info in global variiables             */
18114
18115     /* set defaults in case FEN is incomplete */
18116     board[EP_STATUS] = EP_UNKNOWN;
18117     for(i=0; i<nrCastlingRights; i++ ) {
18118         board[CASTLING][i] =
18119             appData.fischerCastling ? NoRights : initialRights[i];
18120     }   /* assume possible unless obviously impossible */
18121     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18122     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18123     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18124                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18125     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18126     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18127     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18128                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18129     FENrulePlies = 0;
18130
18131     while(*p==' ') p++;
18132     if(nrCastlingRights) {
18133       int fischer = 0;
18134       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18135       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18136           /* castling indicator present, so default becomes no castlings */
18137           for(i=0; i<nrCastlingRights; i++ ) {
18138                  board[CASTLING][i] = NoRights;
18139           }
18140       }
18141       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18142              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18143              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18144              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18145         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18146
18147         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18148             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18149             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18150         }
18151         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18152             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18153         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18154                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18155         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18156                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18157         switch(c) {
18158           case'K':
18159               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18160               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18161               board[CASTLING][2] = whiteKingFile;
18162               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18163               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18164               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18165               break;
18166           case'Q':
18167               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18168               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18169               board[CASTLING][2] = whiteKingFile;
18170               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18171               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18172               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18173               break;
18174           case'k':
18175               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18176               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18177               board[CASTLING][5] = blackKingFile;
18178               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18179               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18180               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18181               break;
18182           case'q':
18183               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18184               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18185               board[CASTLING][5] = blackKingFile;
18186               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18187               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18188               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18189           case '-':
18190               break;
18191           default: /* FRC castlings */
18192               if(c >= 'a') { /* black rights */
18193                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18194                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18195                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18196                   if(i == BOARD_RGHT) break;
18197                   board[CASTLING][5] = i;
18198                   c -= AAA;
18199                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18200                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18201                   if(c > i)
18202                       board[CASTLING][3] = c;
18203                   else
18204                       board[CASTLING][4] = c;
18205               } else { /* white rights */
18206                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18207                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18208                     if(board[0][i] == WhiteKing) break;
18209                   if(i == BOARD_RGHT) break;
18210                   board[CASTLING][2] = i;
18211                   c -= AAA - 'a' + 'A';
18212                   if(board[0][c] >= WhiteKing) break;
18213                   if(c > i)
18214                       board[CASTLING][0] = c;
18215                   else
18216                       board[CASTLING][1] = c;
18217               }
18218         }
18219       }
18220       for(i=0; i<nrCastlingRights; i++)
18221         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18222       if(gameInfo.variant == VariantSChess)
18223         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18224       if(fischer && shuffle) appData.fischerCastling = TRUE;
18225     if (appData.debugMode) {
18226         fprintf(debugFP, "FEN castling rights:");
18227         for(i=0; i<nrCastlingRights; i++)
18228         fprintf(debugFP, " %d", board[CASTLING][i]);
18229         fprintf(debugFP, "\n");
18230     }
18231
18232       while(*p==' ') p++;
18233     }
18234
18235     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18236
18237     /* read e.p. field in games that know e.p. capture */
18238     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18239        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18240        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18241       if(*p=='-') {
18242         p++; board[EP_STATUS] = EP_NONE;
18243       } else {
18244          char c = *p++ - AAA;
18245
18246          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18247          if(*p >= '0' && *p <='9') p++;
18248          board[EP_STATUS] = c;
18249       }
18250     }
18251
18252
18253     if(sscanf(p, "%d", &i) == 1) {
18254         FENrulePlies = i; /* 50-move ply counter */
18255         /* (The move number is still ignored)    */
18256     }
18257
18258     return TRUE;
18259 }
18260
18261 void
18262 EditPositionPasteFEN (char *fen)
18263 {
18264   if (fen != NULL) {
18265     Board initial_position;
18266
18267     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18268       DisplayError(_("Bad FEN position in clipboard"), 0);
18269       return ;
18270     } else {
18271       int savedBlackPlaysFirst = blackPlaysFirst;
18272       EditPositionEvent();
18273       blackPlaysFirst = savedBlackPlaysFirst;
18274       CopyBoard(boards[0], initial_position);
18275       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18276       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18277       DisplayBothClocks();
18278       DrawPosition(FALSE, boards[currentMove]);
18279     }
18280   }
18281 }
18282
18283 static char cseq[12] = "\\   ";
18284
18285 Boolean
18286 set_cont_sequence (char *new_seq)
18287 {
18288     int len;
18289     Boolean ret;
18290
18291     // handle bad attempts to set the sequence
18292         if (!new_seq)
18293                 return 0; // acceptable error - no debug
18294
18295     len = strlen(new_seq);
18296     ret = (len > 0) && (len < sizeof(cseq));
18297     if (ret)
18298       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18299     else if (appData.debugMode)
18300       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18301     return ret;
18302 }
18303
18304 /*
18305     reformat a source message so words don't cross the width boundary.  internal
18306     newlines are not removed.  returns the wrapped size (no null character unless
18307     included in source message).  If dest is NULL, only calculate the size required
18308     for the dest buffer.  lp argument indicats line position upon entry, and it's
18309     passed back upon exit.
18310 */
18311 int
18312 wrap (char *dest, char *src, int count, int width, int *lp)
18313 {
18314     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18315
18316     cseq_len = strlen(cseq);
18317     old_line = line = *lp;
18318     ansi = len = clen = 0;
18319
18320     for (i=0; i < count; i++)
18321     {
18322         if (src[i] == '\033')
18323             ansi = 1;
18324
18325         // if we hit the width, back up
18326         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18327         {
18328             // store i & len in case the word is too long
18329             old_i = i, old_len = len;
18330
18331             // find the end of the last word
18332             while (i && src[i] != ' ' && src[i] != '\n')
18333             {
18334                 i--;
18335                 len--;
18336             }
18337
18338             // word too long?  restore i & len before splitting it
18339             if ((old_i-i+clen) >= width)
18340             {
18341                 i = old_i;
18342                 len = old_len;
18343             }
18344
18345             // extra space?
18346             if (i && src[i-1] == ' ')
18347                 len--;
18348
18349             if (src[i] != ' ' && src[i] != '\n')
18350             {
18351                 i--;
18352                 if (len)
18353                     len--;
18354             }
18355
18356             // now append the newline and continuation sequence
18357             if (dest)
18358                 dest[len] = '\n';
18359             len++;
18360             if (dest)
18361                 strncpy(dest+len, cseq, cseq_len);
18362             len += cseq_len;
18363             line = cseq_len;
18364             clen = cseq_len;
18365             continue;
18366         }
18367
18368         if (dest)
18369             dest[len] = src[i];
18370         len++;
18371         if (!ansi)
18372             line++;
18373         if (src[i] == '\n')
18374             line = 0;
18375         if (src[i] == 'm')
18376             ansi = 0;
18377     }
18378     if (dest && appData.debugMode)
18379     {
18380         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18381             count, width, line, len, *lp);
18382         show_bytes(debugFP, src, count);
18383         fprintf(debugFP, "\ndest: ");
18384         show_bytes(debugFP, dest, len);
18385         fprintf(debugFP, "\n");
18386     }
18387     *lp = dest ? line : old_line;
18388
18389     return len;
18390 }
18391
18392 // [HGM] vari: routines for shelving variations
18393 Boolean modeRestore = FALSE;
18394
18395 void
18396 PushInner (int firstMove, int lastMove)
18397 {
18398         int i, j, nrMoves = lastMove - firstMove;
18399
18400         // push current tail of game on stack
18401         savedResult[storedGames] = gameInfo.result;
18402         savedDetails[storedGames] = gameInfo.resultDetails;
18403         gameInfo.resultDetails = NULL;
18404         savedFirst[storedGames] = firstMove;
18405         savedLast [storedGames] = lastMove;
18406         savedFramePtr[storedGames] = framePtr;
18407         framePtr -= nrMoves; // reserve space for the boards
18408         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18409             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18410             for(j=0; j<MOVE_LEN; j++)
18411                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18412             for(j=0; j<2*MOVE_LEN; j++)
18413                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18414             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18415             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18416             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18417             pvInfoList[firstMove+i-1].depth = 0;
18418             commentList[framePtr+i] = commentList[firstMove+i];
18419             commentList[firstMove+i] = NULL;
18420         }
18421
18422         storedGames++;
18423         forwardMostMove = firstMove; // truncate game so we can start variation
18424 }
18425
18426 void
18427 PushTail (int firstMove, int lastMove)
18428 {
18429         if(appData.icsActive) { // only in local mode
18430                 forwardMostMove = currentMove; // mimic old ICS behavior
18431                 return;
18432         }
18433         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18434
18435         PushInner(firstMove, lastMove);
18436         if(storedGames == 1) GreyRevert(FALSE);
18437         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18438 }
18439
18440 void
18441 PopInner (Boolean annotate)
18442 {
18443         int i, j, nrMoves;
18444         char buf[8000], moveBuf[20];
18445
18446         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18447         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18448         nrMoves = savedLast[storedGames] - currentMove;
18449         if(annotate) {
18450                 int cnt = 10;
18451                 if(!WhiteOnMove(currentMove))
18452                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18453                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18454                 for(i=currentMove; i<forwardMostMove; i++) {
18455                         if(WhiteOnMove(i))
18456                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18457                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18458                         strcat(buf, moveBuf);
18459                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18460                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18461                 }
18462                 strcat(buf, ")");
18463         }
18464         for(i=1; i<=nrMoves; i++) { // copy last variation back
18465             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18466             for(j=0; j<MOVE_LEN; j++)
18467                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18468             for(j=0; j<2*MOVE_LEN; j++)
18469                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18470             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18471             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18472             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18473             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18474             commentList[currentMove+i] = commentList[framePtr+i];
18475             commentList[framePtr+i] = NULL;
18476         }
18477         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18478         framePtr = savedFramePtr[storedGames];
18479         gameInfo.result = savedResult[storedGames];
18480         if(gameInfo.resultDetails != NULL) {
18481             free(gameInfo.resultDetails);
18482       }
18483         gameInfo.resultDetails = savedDetails[storedGames];
18484         forwardMostMove = currentMove + nrMoves;
18485 }
18486
18487 Boolean
18488 PopTail (Boolean annotate)
18489 {
18490         if(appData.icsActive) return FALSE; // only in local mode
18491         if(!storedGames) return FALSE; // sanity
18492         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18493
18494         PopInner(annotate);
18495         if(currentMove < forwardMostMove) ForwardEvent(); else
18496         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18497
18498         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18499         return TRUE;
18500 }
18501
18502 void
18503 CleanupTail ()
18504 {       // remove all shelved variations
18505         int i;
18506         for(i=0; i<storedGames; i++) {
18507             if(savedDetails[i])
18508                 free(savedDetails[i]);
18509             savedDetails[i] = NULL;
18510         }
18511         for(i=framePtr; i<MAX_MOVES; i++) {
18512                 if(commentList[i]) free(commentList[i]);
18513                 commentList[i] = NULL;
18514         }
18515         framePtr = MAX_MOVES-1;
18516         storedGames = 0;
18517 }
18518
18519 void
18520 LoadVariation (int index, char *text)
18521 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18522         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18523         int level = 0, move;
18524
18525         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18526         // first find outermost bracketing variation
18527         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18528             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18529                 if(*p == '{') wait = '}'; else
18530                 if(*p == '[') wait = ']'; else
18531                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18532                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18533             }
18534             if(*p == wait) wait = NULLCHAR; // closing ]} found
18535             p++;
18536         }
18537         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18538         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18539         end[1] = NULLCHAR; // clip off comment beyond variation
18540         ToNrEvent(currentMove-1);
18541         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18542         // kludge: use ParsePV() to append variation to game
18543         move = currentMove;
18544         ParsePV(start, TRUE, TRUE);
18545         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18546         ClearPremoveHighlights();
18547         CommentPopDown();
18548         ToNrEvent(currentMove+1);
18549 }
18550
18551 void
18552 LoadTheme ()
18553 {
18554     char *p, *q, buf[MSG_SIZ];
18555     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18556         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18557         ParseArgsFromString(buf);
18558         ActivateTheme(TRUE); // also redo colors
18559         return;
18560     }
18561     p = nickName;
18562     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18563     {
18564         int len;
18565         q = appData.themeNames;
18566         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18567       if(appData.useBitmaps) {
18568         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18569                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18570                 appData.liteBackTextureMode,
18571                 appData.darkBackTextureMode );
18572       } else {
18573         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18574                 Col2Text(2),   // lightSquareColor
18575                 Col2Text(3) ); // darkSquareColor
18576       }
18577       if(appData.useBorder) {
18578         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18579                 appData.border);
18580       } else {
18581         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18582       }
18583       if(appData.useFont) {
18584         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18585                 appData.renderPiecesWithFont,
18586                 appData.fontToPieceTable,
18587                 Col2Text(9),    // appData.fontBackColorWhite
18588                 Col2Text(10) ); // appData.fontForeColorBlack
18589       } else {
18590         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18591                 appData.pieceDirectory);
18592         if(!appData.pieceDirectory[0])
18593           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18594                 Col2Text(0),   // whitePieceColor
18595                 Col2Text(1) ); // blackPieceColor
18596       }
18597       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18598                 Col2Text(4),   // highlightSquareColor
18599                 Col2Text(5) ); // premoveHighlightColor
18600         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18601         if(insert != q) insert[-1] = NULLCHAR;
18602         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18603         if(q)   free(q);
18604     }
18605     ActivateTheme(FALSE);
18606 }