b8ebf8fcad7048d5977cf23eee7090174e5dab12
[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 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 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278
279 /* States for ics_getting_history */
280 #define H_FALSE 0
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
286
287 /* whosays values for GameEnds */
288 #define GE_ICS 0
289 #define GE_ENGINE 1
290 #define GE_PLAYER 2
291 #define GE_FILE 3
292 #define GE_XBOARD 4
293 #define GE_ENGINE1 5
294 #define GE_ENGINE2 6
295
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
298
299 /* Different types of move when calling RegisterMove */
300 #define CMAIL_MOVE   0
301 #define CMAIL_RESIGN 1
302 #define CMAIL_DRAW   2
303 #define CMAIL_ACCEPT 3
304
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
309
310 /* Telnet protocol constants */
311 #define TN_WILL 0373
312 #define TN_WONT 0374
313 #define TN_DO   0375
314 #define TN_DONT 0376
315 #define TN_IAC  0377
316 #define TN_ECHO 0001
317 #define TN_SGA  0003
318 #define TN_PORT 23
319
320 char*
321 safeStrCpy (char *dst, const char *src, size_t count)
322 { // [HGM] made safe
323   int i;
324   assert( dst != NULL );
325   assert( src != NULL );
326   assert( count > 0 );
327
328   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329   if(  i == count && dst[count-1] != NULLCHAR)
330     {
331       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332       if(appData.debugMode)
333         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334     }
335
336   return dst;
337 }
338
339 /* Some compiler can't cast u64 to double
340  * This function do the job for us:
341
342  * We use the highest bit for cast, this only
343  * works if the highest bit is not
344  * in use (This should not happen)
345  *
346  * We used this for all compiler
347  */
348 double
349 u64ToDouble (u64 value)
350 {
351   double r;
352   u64 tmp = value & u64Const(0x7fffffffffffffff);
353   r = (double)(s64)tmp;
354   if (value & u64Const(0x8000000000000000))
355        r +=  9.2233720368547758080e18; /* 2^63 */
356  return r;
357 }
358
359 /* Fake up flags for now, as we aren't keeping track of castling
360    availability yet. [HGM] Change of logic: the flag now only
361    indicates the type of castlings allowed by the rule of the game.
362    The actual rights themselves are maintained in the array
363    castlingRights, as part of the game history, and are not probed
364    by this function.
365  */
366 int
367 PosFlags (index)
368 {
369   int flags = F_ALL_CASTLE_OK;
370   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371   switch (gameInfo.variant) {
372   case VariantSuicide:
373     flags &= ~F_ALL_CASTLE_OK;
374   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375     flags |= F_IGNORE_CHECK;
376   case VariantLosers:
377     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378     break;
379   case VariantAtomic:
380     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
381     break;
382   case VariantKriegspiel:
383     flags |= F_KRIEGSPIEL_CAPTURE;
384     break;
385   case VariantCapaRandom:
386   case VariantFischeRandom:
387     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388   case VariantNoCastle:
389   case VariantShatranj:
390   case VariantCourier:
391   case VariantMakruk:
392   case VariantASEAN:
393   case VariantGrand:
394     flags &= ~F_ALL_CASTLE_OK;
395     break;
396   default:
397     break;
398   }
399   return flags;
400 }
401
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second, pairing;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackMan, BlackFerz,
566         BlackKing, BlackMan, BlackKnight, BlackRook }
567 };
568
569
570 #if (BOARD_FILES>=10)
571 ChessSquare ShogiArray[2][BOARD_FILES] = {
572     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
573         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
574     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
575         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
576 };
577
578 ChessSquare XiangqiArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
580         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
582         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare CapablancaArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
587         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
589         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
590 };
591
592 ChessSquare GreatArray[2][BOARD_FILES] = {
593     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
594         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
595     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
596         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
597 };
598
599 ChessSquare JanusArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
601         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
602     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
603         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
604 };
605
606 ChessSquare GrandArray[2][BOARD_FILES] = {
607     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
608         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
609     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
610         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
611 };
612
613 #ifdef GOTHIC
614 ChessSquare GothicArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
616         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
618         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
619 };
620 #else // !GOTHIC
621 #define GothicArray CapablancaArray
622 #endif // !GOTHIC
623
624 #ifdef FALCON
625 ChessSquare FalconArray[2][BOARD_FILES] = {
626     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
627         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
628     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
629         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
630 };
631 #else // !FALCON
632 #define FalconArray CapablancaArray
633 #endif // !FALCON
634
635 #else // !(BOARD_FILES>=10)
636 #define XiangqiPosition FIDEArray
637 #define CapablancaArray FIDEArray
638 #define GothicArray FIDEArray
639 #define GreatArray FIDEArray
640 #endif // !(BOARD_FILES>=10)
641
642 #if (BOARD_FILES>=12)
643 ChessSquare CourierArray[2][BOARD_FILES] = {
644     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
645         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
646     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
647         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
648 };
649 #else // !(BOARD_FILES>=12)
650 #define CourierArray CapablancaArray
651 #endif // !(BOARD_FILES>=12)
652
653
654 Board initialPosition;
655
656
657 /* Convert str to a rating. Checks for special cases of "----",
658
659    "++++", etc. Also strips ()'s */
660 int
661 string_to_rating (char *str)
662 {
663   while(*str && !isdigit(*str)) ++str;
664   if (!*str)
665     return 0;   /* One of the special "no rating" cases */
666   else
667     return atoi(str);
668 }
669
670 void
671 ClearProgramStats ()
672 {
673     /* Init programStats */
674     programStats.movelist[0] = 0;
675     programStats.depth = 0;
676     programStats.nr_moves = 0;
677     programStats.moves_left = 0;
678     programStats.nodes = 0;
679     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
680     programStats.score = 0;
681     programStats.got_only_move = 0;
682     programStats.got_fail = 0;
683     programStats.line_is_book = 0;
684 }
685
686 void
687 CommonEngineInit ()
688 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
689     if (appData.firstPlaysBlack) {
690         first.twoMachinesColor = "black\n";
691         second.twoMachinesColor = "white\n";
692     } else {
693         first.twoMachinesColor = "white\n";
694         second.twoMachinesColor = "black\n";
695     }
696
697     first.other = &second;
698     second.other = &first;
699
700     { float norm = 1;
701         if(appData.timeOddsMode) {
702             norm = appData.timeOdds[0];
703             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
704         }
705         first.timeOdds  = appData.timeOdds[0]/norm;
706         second.timeOdds = appData.timeOdds[1]/norm;
707     }
708
709     if(programVersion) free(programVersion);
710     if (appData.noChessProgram) {
711         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
712         sprintf(programVersion, "%s", PACKAGE_STRING);
713     } else {
714       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
715       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
716       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
717     }
718 }
719
720 void
721 UnloadEngine (ChessProgramState *cps)
722 {
723         /* Kill off first chess program */
724         if (cps->isr != NULL)
725           RemoveInputSource(cps->isr);
726         cps->isr = NULL;
727
728         if (cps->pr != NoProc) {
729             ExitAnalyzeMode();
730             DoSleep( appData.delayBeforeQuit );
731             SendToProgram("quit\n", cps);
732             DoSleep( appData.delayAfterQuit );
733             DestroyChildProcess(cps->pr, cps->useSigterm);
734         }
735         cps->pr = NoProc;
736         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
737 }
738
739 void
740 ClearOptions (ChessProgramState *cps)
741 {
742     int i;
743     cps->nrOptions = cps->comboCnt = 0;
744     for(i=0; i<MAX_OPTIONS; i++) {
745         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
746         cps->option[i].textValue = 0;
747     }
748 }
749
750 char *engineNames[] = {
751   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
752      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
753 N_("first"),
754   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
755      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
756 N_("second")
757 };
758
759 void
760 InitEngine (ChessProgramState *cps, int n)
761 {   // [HGM] all engine initialiation put in a function that does one engine
762
763     ClearOptions(cps);
764
765     cps->which = engineNames[n];
766     cps->maybeThinking = FALSE;
767     cps->pr = NoProc;
768     cps->isr = NULL;
769     cps->sendTime = 2;
770     cps->sendDrawOffers = 1;
771
772     cps->program = appData.chessProgram[n];
773     cps->host = appData.host[n];
774     cps->dir = appData.directory[n];
775     cps->initString = appData.engInitString[n];
776     cps->computerString = appData.computerString[n];
777     cps->useSigint  = TRUE;
778     cps->useSigterm = TRUE;
779     cps->reuse = appData.reuse[n];
780     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
781     cps->useSetboard = FALSE;
782     cps->useSAN = FALSE;
783     cps->usePing = FALSE;
784     cps->lastPing = 0;
785     cps->lastPong = 0;
786     cps->usePlayother = FALSE;
787     cps->useColors = TRUE;
788     cps->useUsermove = FALSE;
789     cps->sendICS = FALSE;
790     cps->sendName = appData.icsActive;
791     cps->sdKludge = FALSE;
792     cps->stKludge = FALSE;
793     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
794     TidyProgramName(cps->program, cps->host, cps->tidy);
795     cps->matchWins = 0;
796     ASSIGN(cps->variants, appData.variant);
797     cps->analysisSupport = 2; /* detect */
798     cps->analyzing = FALSE;
799     cps->initDone = FALSE;
800     cps->reload = FALSE;
801
802     /* New features added by Tord: */
803     cps->useFEN960 = FALSE;
804     cps->useOOCastle = TRUE;
805     /* End of new features added by Tord. */
806     cps->fenOverride  = appData.fenOverride[n];
807
808     /* [HGM] time odds: set factor for each machine */
809     cps->timeOdds  = appData.timeOdds[n];
810
811     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812     cps->accumulateTC = appData.accumulateTC[n];
813     cps->maxNrOfSessions = 1;
814
815     /* [HGM] debug */
816     cps->debug = FALSE;
817
818     cps->supportsNPS = UNKNOWN;
819     cps->memSize = FALSE;
820     cps->maxCores = FALSE;
821     ASSIGN(cps->egtFormats, "");
822
823     /* [HGM] options */
824     cps->optionSettings  = appData.engOptions[n];
825
826     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
827     cps->isUCI = appData.isUCI[n]; /* [AS] */
828     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
829     cps->highlight = 0;
830
831     if (appData.protocolVersion[n] > PROTOVER
832         || appData.protocolVersion[n] < 1)
833       {
834         char buf[MSG_SIZ];
835         int len;
836
837         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838                        appData.protocolVersion[n]);
839         if( (len >= MSG_SIZ) && appData.debugMode )
840           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
841
842         DisplayFatalError(buf, 0, 2);
843       }
844     else
845       {
846         cps->protocolVersion = appData.protocolVersion[n];
847       }
848
849     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
850     ParseFeatures(appData.featureDefaults, cps);
851 }
852
853 ChessProgramState *savCps;
854
855 GameMode oldMode;
856
857 void
858 LoadEngine ()
859 {
860     int i;
861     if(WaitForEngine(savCps, LoadEngine)) return;
862     CommonEngineInit(); // recalculate time odds
863     if(gameInfo.variant != StringToVariant(appData.variant)) {
864         // we changed variant when loading the engine; this forces us to reset
865         Reset(TRUE, savCps != &first);
866         oldMode = BeginningOfGame; // to prevent restoring old mode
867     }
868     InitChessProgram(savCps, FALSE);
869     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
870     DisplayMessage("", "");
871     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
872     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
873     ThawUI();
874     SetGNUMode();
875     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
876 }
877
878 void
879 ReplaceEngine (ChessProgramState *cps, int n)
880 {
881     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
882     keepInfo = 1;
883     if(oldMode != BeginningOfGame) EditGameEvent();
884     keepInfo = 0;
885     UnloadEngine(cps);
886     appData.noChessProgram = FALSE;
887     appData.clockMode = TRUE;
888     InitEngine(cps, n);
889     UpdateLogos(TRUE);
890     if(n) return; // only startup first engine immediately; second can wait
891     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
892     LoadEngine();
893 }
894
895 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
896 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
897
898 static char resetOptions[] =
899         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
900         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
901         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
902         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
903
904 void
905 FloatToFront(char **list, char *engineLine)
906 {
907     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
908     int i=0;
909     if(appData.recentEngines <= 0) return;
910     TidyProgramName(engineLine, "localhost", tidy+1);
911     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
912     strncpy(buf+1, *list, MSG_SIZ-50);
913     if(p = strstr(buf, tidy)) { // tidy name appears in list
914         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
915         while(*p++ = *++q); // squeeze out
916     }
917     strcat(tidy, buf+1); // put list behind tidy name
918     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
919     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
920     ASSIGN(*list, tidy+1);
921 }
922
923 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
924
925 void
926 Load (ChessProgramState *cps, int i)
927 {
928     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
929     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
930         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
931         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
932         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
933         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
934         appData.firstProtocolVersion = PROTOVER;
935         ParseArgsFromString(buf);
936         SwapEngines(i);
937         ReplaceEngine(cps, i);
938         FloatToFront(&appData.recentEngineList, engineLine);
939         return;
940     }
941     p = engineName;
942     while(q = strchr(p, SLASH)) p = q+1;
943     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
944     if(engineDir[0] != NULLCHAR) {
945         ASSIGN(appData.directory[i], engineDir); p = engineName;
946     } else if(p != engineName) { // derive directory from engine path, when not given
947         p[-1] = 0;
948         ASSIGN(appData.directory[i], engineName);
949         p[-1] = SLASH;
950         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
951     } else { ASSIGN(appData.directory[i], "."); }
952     if(params[0]) {
953         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
954         snprintf(command, MSG_SIZ, "%s %s", p, params);
955         p = command;
956     }
957     ASSIGN(appData.chessProgram[i], p);
958     appData.isUCI[i] = isUCI;
959     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
960     appData.hasOwnBookUCI[i] = hasBook;
961     if(!nickName[0]) useNick = FALSE;
962     if(useNick) ASSIGN(appData.pgnName[i], nickName);
963     if(addToList) {
964         int len;
965         char quote;
966         q = firstChessProgramNames;
967         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
968         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
969         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
970                         quote, p, quote, appData.directory[i],
971                         useNick ? " -fn \"" : "",
972                         useNick ? nickName : "",
973                         useNick ? "\"" : "",
974                         v1 ? " -firstProtocolVersion 1" : "",
975                         hasBook ? "" : " -fNoOwnBookUCI",
976                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
977                         storeVariant ? " -variant " : "",
978                         storeVariant ? VariantName(gameInfo.variant) : "");
979         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
980         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
981         if(insert != q) insert[-1] = NULLCHAR;
982         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
983         if(q)   free(q);
984         FloatToFront(&appData.recentEngineList, buf);
985     }
986     ReplaceEngine(cps, i);
987 }
988
989 void
990 InitTimeControls ()
991 {
992     int matched, min, sec;
993     /*
994      * Parse timeControl resource
995      */
996     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
997                           appData.movesPerSession)) {
998         char buf[MSG_SIZ];
999         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1000         DisplayFatalError(buf, 0, 2);
1001     }
1002
1003     /*
1004      * Parse searchTime resource
1005      */
1006     if (*appData.searchTime != NULLCHAR) {
1007         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1008         if (matched == 1) {
1009             searchTime = min * 60;
1010         } else if (matched == 2) {
1011             searchTime = min * 60 + sec;
1012         } else {
1013             char buf[MSG_SIZ];
1014             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1015             DisplayFatalError(buf, 0, 2);
1016         }
1017     }
1018 }
1019
1020 void
1021 InitBackEnd1 ()
1022 {
1023
1024     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1025     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1026
1027     GetTimeMark(&programStartTime);
1028     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1029     appData.seedBase = random() + (random()<<15);
1030     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1031
1032     ClearProgramStats();
1033     programStats.ok_to_send = 1;
1034     programStats.seen_stat = 0;
1035
1036     /*
1037      * Initialize game list
1038      */
1039     ListNew(&gameList);
1040
1041
1042     /*
1043      * Internet chess server status
1044      */
1045     if (appData.icsActive) {
1046         appData.matchMode = FALSE;
1047         appData.matchGames = 0;
1048 #if ZIPPY
1049         appData.noChessProgram = !appData.zippyPlay;
1050 #else
1051         appData.zippyPlay = FALSE;
1052         appData.zippyTalk = FALSE;
1053         appData.noChessProgram = TRUE;
1054 #endif
1055         if (*appData.icsHelper != NULLCHAR) {
1056             appData.useTelnet = TRUE;
1057             appData.telnetProgram = appData.icsHelper;
1058         }
1059     } else {
1060         appData.zippyTalk = appData.zippyPlay = FALSE;
1061     }
1062
1063     /* [AS] Initialize pv info list [HGM] and game state */
1064     {
1065         int i, j;
1066
1067         for( i=0; i<=framePtr; i++ ) {
1068             pvInfoList[i].depth = -1;
1069             boards[i][EP_STATUS] = EP_NONE;
1070             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1071         }
1072     }
1073
1074     InitTimeControls();
1075
1076     /* [AS] Adjudication threshold */
1077     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1078
1079     InitEngine(&first, 0);
1080     InitEngine(&second, 1);
1081     CommonEngineInit();
1082
1083     pairing.which = "pairing"; // pairing engine
1084     pairing.pr = NoProc;
1085     pairing.isr = NULL;
1086     pairing.program = appData.pairingEngine;
1087     pairing.host = "localhost";
1088     pairing.dir = ".";
1089
1090     if (appData.icsActive) {
1091         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1092     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1093         appData.clockMode = FALSE;
1094         first.sendTime = second.sendTime = 0;
1095     }
1096
1097 #if ZIPPY
1098     /* Override some settings from environment variables, for backward
1099        compatibility.  Unfortunately it's not feasible to have the env
1100        vars just set defaults, at least in xboard.  Ugh.
1101     */
1102     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1103       ZippyInit();
1104     }
1105 #endif
1106
1107     if (!appData.icsActive) {
1108       char buf[MSG_SIZ];
1109       int len;
1110
1111       /* Check for variants that are supported only in ICS mode,
1112          or not at all.  Some that are accepted here nevertheless
1113          have bugs; see comments below.
1114       */
1115       VariantClass variant = StringToVariant(appData.variant);
1116       switch (variant) {
1117       case VariantBughouse:     /* need four players and two boards */
1118       case VariantKriegspiel:   /* need to hide pieces and move details */
1119         /* case VariantFischeRandom: (Fabien: moved below) */
1120         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1121         if( (len >= MSG_SIZ) && appData.debugMode )
1122           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1123
1124         DisplayFatalError(buf, 0, 2);
1125         return;
1126
1127       case VariantUnknown:
1128       case VariantLoadable:
1129       case Variant29:
1130       case Variant30:
1131       case Variant31:
1132       case Variant32:
1133       case Variant33:
1134       case Variant34:
1135       case Variant35:
1136       case Variant36:
1137       default:
1138         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1139         if( (len >= MSG_SIZ) && appData.debugMode )
1140           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1141
1142         DisplayFatalError(buf, 0, 2);
1143         return;
1144
1145       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1146       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1147       case VariantGothic:     /* [HGM] should work */
1148       case VariantCapablanca: /* [HGM] should work */
1149       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1150       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1151       case VariantKnightmate: /* [HGM] should work */
1152       case VariantCylinder:   /* [HGM] untested */
1153       case VariantFalcon:     /* [HGM] untested */
1154       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1155                                  offboard interposition not understood */
1156       case VariantNormal:     /* definitely works! */
1157       case VariantWildCastle: /* pieces not automatically shuffled */
1158       case VariantNoCastle:   /* pieces not automatically shuffled */
1159       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1160       case VariantLosers:     /* should work except for win condition,
1161                                  and doesn't know captures are mandatory */
1162       case VariantSuicide:    /* should work except for win condition,
1163                                  and doesn't know captures are mandatory */
1164       case VariantGiveaway:   /* should work except for win condition,
1165                                  and doesn't know captures are mandatory */
1166       case VariantTwoKings:   /* should work */
1167       case VariantAtomic:     /* should work except for win condition */
1168       case Variant3Check:     /* should work except for win condition */
1169       case VariantShatranj:   /* should work except for all win conditions */
1170       case VariantMakruk:     /* should work except for draw countdown */
1171       case VariantASEAN :     /* should work except for draw countdown */
1172       case VariantBerolina:   /* might work if TestLegality is off */
1173       case VariantCapaRandom: /* should work */
1174       case VariantJanus:      /* should work */
1175       case VariantSuper:      /* experimental */
1176       case VariantGreat:      /* experimental, requires legality testing to be off */
1177       case VariantSChess:     /* S-Chess, should work */
1178       case VariantGrand:      /* should work */
1179       case VariantSpartan:    /* should work */
1180         break;
1181       }
1182     }
1183
1184 }
1185
1186 int
1187 NextIntegerFromString (char ** str, long * value)
1188 {
1189     int result = -1;
1190     char * s = *str;
1191
1192     while( *s == ' ' || *s == '\t' ) {
1193         s++;
1194     }
1195
1196     *value = 0;
1197
1198     if( *s >= '0' && *s <= '9' ) {
1199         while( *s >= '0' && *s <= '9' ) {
1200             *value = *value * 10 + (*s - '0');
1201             s++;
1202         }
1203
1204         result = 0;
1205     }
1206
1207     *str = s;
1208
1209     return result;
1210 }
1211
1212 int
1213 NextTimeControlFromString (char ** str, long * value)
1214 {
1215     long temp;
1216     int result = NextIntegerFromString( str, &temp );
1217
1218     if( result == 0 ) {
1219         *value = temp * 60; /* Minutes */
1220         if( **str == ':' ) {
1221             (*str)++;
1222             result = NextIntegerFromString( str, &temp );
1223             *value += temp; /* Seconds */
1224         }
1225     }
1226
1227     return result;
1228 }
1229
1230 int
1231 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1232 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1233     int result = -1, type = 0; long temp, temp2;
1234
1235     if(**str != ':') return -1; // old params remain in force!
1236     (*str)++;
1237     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1238     if( NextIntegerFromString( str, &temp ) ) return -1;
1239     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1240
1241     if(**str != '/') {
1242         /* time only: incremental or sudden-death time control */
1243         if(**str == '+') { /* increment follows; read it */
1244             (*str)++;
1245             if(**str == '!') type = *(*str)++; // Bronstein TC
1246             if(result = NextIntegerFromString( str, &temp2)) return -1;
1247             *inc = temp2 * 1000;
1248             if(**str == '.') { // read fraction of increment
1249                 char *start = ++(*str);
1250                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1251                 temp2 *= 1000;
1252                 while(start++ < *str) temp2 /= 10;
1253                 *inc += temp2;
1254             }
1255         } else *inc = 0;
1256         *moves = 0; *tc = temp * 1000; *incType = type;
1257         return 0;
1258     }
1259
1260     (*str)++; /* classical time control */
1261     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1262
1263     if(result == 0) {
1264         *moves = temp;
1265         *tc    = temp2 * 1000;
1266         *inc   = 0;
1267         *incType = type;
1268     }
1269     return result;
1270 }
1271
1272 int
1273 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1274 {   /* [HGM] get time to add from the multi-session time-control string */
1275     int incType, moves=1; /* kludge to force reading of first session */
1276     long time, increment;
1277     char *s = tcString;
1278
1279     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1280     do {
1281         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1282         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1283         if(movenr == -1) return time;    /* last move before new session     */
1284         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1285         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1286         if(!moves) return increment;     /* current session is incremental   */
1287         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1288     } while(movenr >= -1);               /* try again for next session       */
1289
1290     return 0; // no new time quota on this move
1291 }
1292
1293 int
1294 ParseTimeControl (char *tc, float ti, int mps)
1295 {
1296   long tc1;
1297   long tc2;
1298   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1299   int min, sec=0;
1300
1301   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1302   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1303       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1304   if(ti > 0) {
1305
1306     if(mps)
1307       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1308     else
1309       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1310   } else {
1311     if(mps)
1312       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1313     else
1314       snprintf(buf, MSG_SIZ, ":%s", mytc);
1315   }
1316   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1317
1318   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1319     return FALSE;
1320   }
1321
1322   if( *tc == '/' ) {
1323     /* Parse second time control */
1324     tc++;
1325
1326     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1327       return FALSE;
1328     }
1329
1330     if( tc2 == 0 ) {
1331       return FALSE;
1332     }
1333
1334     timeControl_2 = tc2 * 1000;
1335   }
1336   else {
1337     timeControl_2 = 0;
1338   }
1339
1340   if( tc1 == 0 ) {
1341     return FALSE;
1342   }
1343
1344   timeControl = tc1 * 1000;
1345
1346   if (ti >= 0) {
1347     timeIncrement = ti * 1000;  /* convert to ms */
1348     movesPerSession = 0;
1349   } else {
1350     timeIncrement = 0;
1351     movesPerSession = mps;
1352   }
1353   return TRUE;
1354 }
1355
1356 void
1357 InitBackEnd2 ()
1358 {
1359     if (appData.debugMode) {
1360 #    ifdef __GIT_VERSION
1361       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1362 #    else
1363       fprintf(debugFP, "Version: %s\n", programVersion);
1364 #    endif
1365     }
1366     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1367
1368     set_cont_sequence(appData.wrapContSeq);
1369     if (appData.matchGames > 0) {
1370         appData.matchMode = TRUE;
1371     } else if (appData.matchMode) {
1372         appData.matchGames = 1;
1373     }
1374     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1375         appData.matchGames = appData.sameColorGames;
1376     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1377         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1378         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1379     }
1380     Reset(TRUE, FALSE);
1381     if (appData.noChessProgram || first.protocolVersion == 1) {
1382       InitBackEnd3();
1383     } else {
1384       /* kludge: allow timeout for initial "feature" commands */
1385       FreezeUI();
1386       DisplayMessage("", _("Starting chess program"));
1387       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1388     }
1389 }
1390
1391 int
1392 CalculateIndex (int index, int gameNr)
1393 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1394     int res;
1395     if(index > 0) return index; // fixed nmber
1396     if(index == 0) return 1;
1397     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1398     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1399     return res;
1400 }
1401
1402 int
1403 LoadGameOrPosition (int gameNr)
1404 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1405     if (*appData.loadGameFile != NULLCHAR) {
1406         if (!LoadGameFromFile(appData.loadGameFile,
1407                 CalculateIndex(appData.loadGameIndex, gameNr),
1408                               appData.loadGameFile, FALSE)) {
1409             DisplayFatalError(_("Bad game file"), 0, 1);
1410             return 0;
1411         }
1412     } else if (*appData.loadPositionFile != NULLCHAR) {
1413         if (!LoadPositionFromFile(appData.loadPositionFile,
1414                 CalculateIndex(appData.loadPositionIndex, gameNr),
1415                                   appData.loadPositionFile)) {
1416             DisplayFatalError(_("Bad position file"), 0, 1);
1417             return 0;
1418         }
1419     }
1420     return 1;
1421 }
1422
1423 void
1424 ReserveGame (int gameNr, char resChar)
1425 {
1426     FILE *tf = fopen(appData.tourneyFile, "r+");
1427     char *p, *q, c, buf[MSG_SIZ];
1428     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1429     safeStrCpy(buf, lastMsg, MSG_SIZ);
1430     DisplayMessage(_("Pick new game"), "");
1431     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1432     ParseArgsFromFile(tf);
1433     p = q = appData.results;
1434     if(appData.debugMode) {
1435       char *r = appData.participants;
1436       fprintf(debugFP, "results = '%s'\n", p);
1437       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1438       fprintf(debugFP, "\n");
1439     }
1440     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1441     nextGame = q - p;
1442     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1443     safeStrCpy(q, p, strlen(p) + 2);
1444     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1445     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1446     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1447         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1448         q[nextGame] = '*';
1449     }
1450     fseek(tf, -(strlen(p)+4), SEEK_END);
1451     c = fgetc(tf);
1452     if(c != '"') // depending on DOS or Unix line endings we can be one off
1453          fseek(tf, -(strlen(p)+2), SEEK_END);
1454     else fseek(tf, -(strlen(p)+3), SEEK_END);
1455     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1456     DisplayMessage(buf, "");
1457     free(p); appData.results = q;
1458     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1459        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1460       int round = appData.defaultMatchGames * appData.tourneyType;
1461       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1462          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1463         UnloadEngine(&first);  // next game belongs to other pairing;
1464         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1465     }
1466     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1467 }
1468
1469 void
1470 MatchEvent (int mode)
1471 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1472         int dummy;
1473         if(matchMode) { // already in match mode: switch it off
1474             abortMatch = TRUE;
1475             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1476             return;
1477         }
1478 //      if(gameMode != BeginningOfGame) {
1479 //          DisplayError(_("You can only start a match from the initial position."), 0);
1480 //          return;
1481 //      }
1482         abortMatch = FALSE;
1483         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1484         /* Set up machine vs. machine match */
1485         nextGame = 0;
1486         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1487         if(appData.tourneyFile[0]) {
1488             ReserveGame(-1, 0);
1489             if(nextGame > appData.matchGames) {
1490                 char buf[MSG_SIZ];
1491                 if(strchr(appData.results, '*') == NULL) {
1492                     FILE *f;
1493                     appData.tourneyCycles++;
1494                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1495                         fclose(f);
1496                         NextTourneyGame(-1, &dummy);
1497                         ReserveGame(-1, 0);
1498                         if(nextGame <= appData.matchGames) {
1499                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1500                             matchMode = mode;
1501                             ScheduleDelayedEvent(NextMatchGame, 10000);
1502                             return;
1503                         }
1504                     }
1505                 }
1506                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1507                 DisplayError(buf, 0);
1508                 appData.tourneyFile[0] = 0;
1509                 return;
1510             }
1511         } else
1512         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1513             DisplayFatalError(_("Can't have a match with no chess programs"),
1514                               0, 2);
1515             return;
1516         }
1517         matchMode = mode;
1518         matchGame = roundNr = 1;
1519         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1520         NextMatchGame();
1521 }
1522
1523 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1524
1525 void
1526 InitBackEnd3 P((void))
1527 {
1528     GameMode initialMode;
1529     char buf[MSG_SIZ];
1530     int err, len;
1531
1532     InitChessProgram(&first, startedFromSetupPosition);
1533
1534     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1535         free(programVersion);
1536         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1537         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1538         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1539     }
1540
1541     if (appData.icsActive) {
1542 #ifdef WIN32
1543         /* [DM] Make a console window if needed [HGM] merged ifs */
1544         ConsoleCreate();
1545 #endif
1546         err = establish();
1547         if (err != 0)
1548           {
1549             if (*appData.icsCommPort != NULLCHAR)
1550               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1551                              appData.icsCommPort);
1552             else
1553               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1554                         appData.icsHost, appData.icsPort);
1555
1556             if( (len >= MSG_SIZ) && appData.debugMode )
1557               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1558
1559             DisplayFatalError(buf, err, 1);
1560             return;
1561         }
1562         SetICSMode();
1563         telnetISR =
1564           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1565         fromUserISR =
1566           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1567         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1568             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1569     } else if (appData.noChessProgram) {
1570         SetNCPMode();
1571     } else {
1572         SetGNUMode();
1573     }
1574
1575     if (*appData.cmailGameName != NULLCHAR) {
1576         SetCmailMode();
1577         OpenLoopback(&cmailPR);
1578         cmailISR =
1579           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1580     }
1581
1582     ThawUI();
1583     DisplayMessage("", "");
1584     if (StrCaseCmp(appData.initialMode, "") == 0) {
1585       initialMode = BeginningOfGame;
1586       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1587         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1588         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1589         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1590         ModeHighlight();
1591       }
1592     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1593       initialMode = TwoMachinesPlay;
1594     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1595       initialMode = AnalyzeFile;
1596     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1597       initialMode = AnalyzeMode;
1598     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1599       initialMode = MachinePlaysWhite;
1600     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1601       initialMode = MachinePlaysBlack;
1602     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1603       initialMode = EditGame;
1604     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1605       initialMode = EditPosition;
1606     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1607       initialMode = Training;
1608     } else {
1609       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1610       if( (len >= MSG_SIZ) && appData.debugMode )
1611         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1612
1613       DisplayFatalError(buf, 0, 2);
1614       return;
1615     }
1616
1617     if (appData.matchMode) {
1618         if(appData.tourneyFile[0]) { // start tourney from command line
1619             FILE *f;
1620             if(f = fopen(appData.tourneyFile, "r")) {
1621                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1622                 fclose(f);
1623                 appData.clockMode = TRUE;
1624                 SetGNUMode();
1625             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1626         }
1627         MatchEvent(TRUE);
1628     } else if (*appData.cmailGameName != NULLCHAR) {
1629         /* Set up cmail mode */
1630         ReloadCmailMsgEvent(TRUE);
1631     } else {
1632         /* Set up other modes */
1633         if (initialMode == AnalyzeFile) {
1634           if (*appData.loadGameFile == NULLCHAR) {
1635             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1636             return;
1637           }
1638         }
1639         if (*appData.loadGameFile != NULLCHAR) {
1640             (void) LoadGameFromFile(appData.loadGameFile,
1641                                     appData.loadGameIndex,
1642                                     appData.loadGameFile, TRUE);
1643         } else if (*appData.loadPositionFile != NULLCHAR) {
1644             (void) LoadPositionFromFile(appData.loadPositionFile,
1645                                         appData.loadPositionIndex,
1646                                         appData.loadPositionFile);
1647             /* [HGM] try to make self-starting even after FEN load */
1648             /* to allow automatic setup of fairy variants with wtm */
1649             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1650                 gameMode = BeginningOfGame;
1651                 setboardSpoiledMachineBlack = 1;
1652             }
1653             /* [HGM] loadPos: make that every new game uses the setup */
1654             /* from file as long as we do not switch variant          */
1655             if(!blackPlaysFirst) {
1656                 startedFromPositionFile = TRUE;
1657                 CopyBoard(filePosition, boards[0]);
1658             }
1659         }
1660         if (initialMode == AnalyzeMode) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1663             return;
1664           }
1665           if (appData.icsActive) {
1666             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1667             return;
1668           }
1669           AnalyzeModeEvent();
1670         } else if (initialMode == AnalyzeFile) {
1671           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1672           ShowThinkingEvent();
1673           AnalyzeFileEvent();
1674           AnalysisPeriodicEvent(1);
1675         } else if (initialMode == MachinePlaysWhite) {
1676           if (appData.noChessProgram) {
1677             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1678                               0, 2);
1679             return;
1680           }
1681           if (appData.icsActive) {
1682             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1683                               0, 2);
1684             return;
1685           }
1686           MachineWhiteEvent();
1687         } else if (initialMode == MachinePlaysBlack) {
1688           if (appData.noChessProgram) {
1689             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1690                               0, 2);
1691             return;
1692           }
1693           if (appData.icsActive) {
1694             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1695                               0, 2);
1696             return;
1697           }
1698           MachineBlackEvent();
1699         } else if (initialMode == TwoMachinesPlay) {
1700           if (appData.noChessProgram) {
1701             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1702                               0, 2);
1703             return;
1704           }
1705           if (appData.icsActive) {
1706             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1707                               0, 2);
1708             return;
1709           }
1710           TwoMachinesEvent();
1711         } else if (initialMode == EditGame) {
1712           EditGameEvent();
1713         } else if (initialMode == EditPosition) {
1714           EditPositionEvent();
1715         } else if (initialMode == Training) {
1716           if (*appData.loadGameFile == NULLCHAR) {
1717             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1718             return;
1719           }
1720           TrainingEvent();
1721         }
1722     }
1723 }
1724
1725 void
1726 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1727 {
1728     DisplayBook(current+1);
1729
1730     MoveHistorySet( movelist, first, last, current, pvInfoList );
1731
1732     EvalGraphSet( first, last, current, pvInfoList );
1733
1734     MakeEngineOutputTitle();
1735 }
1736
1737 /*
1738  * Establish will establish a contact to a remote host.port.
1739  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1740  *  used to talk to the host.
1741  * Returns 0 if okay, error code if not.
1742  */
1743 int
1744 establish ()
1745 {
1746     char buf[MSG_SIZ];
1747
1748     if (*appData.icsCommPort != NULLCHAR) {
1749         /* Talk to the host through a serial comm port */
1750         return OpenCommPort(appData.icsCommPort, &icsPR);
1751
1752     } else if (*appData.gateway != NULLCHAR) {
1753         if (*appData.remoteShell == NULLCHAR) {
1754             /* Use the rcmd protocol to run telnet program on a gateway host */
1755             snprintf(buf, sizeof(buf), "%s %s %s",
1756                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1757             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1758
1759         } else {
1760             /* Use the rsh program to run telnet program on a gateway host */
1761             if (*appData.remoteUser == NULLCHAR) {
1762                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1763                         appData.gateway, appData.telnetProgram,
1764                         appData.icsHost, appData.icsPort);
1765             } else {
1766                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1767                         appData.remoteShell, appData.gateway,
1768                         appData.remoteUser, appData.telnetProgram,
1769                         appData.icsHost, appData.icsPort);
1770             }
1771             return StartChildProcess(buf, "", &icsPR);
1772
1773         }
1774     } else if (appData.useTelnet) {
1775         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1776
1777     } else {
1778         /* TCP socket interface differs somewhat between
1779            Unix and NT; handle details in the front end.
1780            */
1781         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1782     }
1783 }
1784
1785 void
1786 EscapeExpand (char *p, char *q)
1787 {       // [HGM] initstring: routine to shape up string arguments
1788         while(*p++ = *q++) if(p[-1] == '\\')
1789             switch(*q++) {
1790                 case 'n': p[-1] = '\n'; break;
1791                 case 'r': p[-1] = '\r'; break;
1792                 case 't': p[-1] = '\t'; break;
1793                 case '\\': p[-1] = '\\'; break;
1794                 case 0: *p = 0; return;
1795                 default: p[-1] = q[-1]; break;
1796             }
1797 }
1798
1799 void
1800 show_bytes (FILE *fp, char *buf, int count)
1801 {
1802     while (count--) {
1803         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1804             fprintf(fp, "\\%03o", *buf & 0xff);
1805         } else {
1806             putc(*buf, fp);
1807         }
1808         buf++;
1809     }
1810     fflush(fp);
1811 }
1812
1813 /* Returns an errno value */
1814 int
1815 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1816 {
1817     char buf[8192], *p, *q, *buflim;
1818     int left, newcount, outcount;
1819
1820     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1821         *appData.gateway != NULLCHAR) {
1822         if (appData.debugMode) {
1823             fprintf(debugFP, ">ICS: ");
1824             show_bytes(debugFP, message, count);
1825             fprintf(debugFP, "\n");
1826         }
1827         return OutputToProcess(pr, message, count, outError);
1828     }
1829
1830     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1831     p = message;
1832     q = buf;
1833     left = count;
1834     newcount = 0;
1835     while (left) {
1836         if (q >= buflim) {
1837             if (appData.debugMode) {
1838                 fprintf(debugFP, ">ICS: ");
1839                 show_bytes(debugFP, buf, newcount);
1840                 fprintf(debugFP, "\n");
1841             }
1842             outcount = OutputToProcess(pr, buf, newcount, outError);
1843             if (outcount < newcount) return -1; /* to be sure */
1844             q = buf;
1845             newcount = 0;
1846         }
1847         if (*p == '\n') {
1848             *q++ = '\r';
1849             newcount++;
1850         } else if (((unsigned char) *p) == TN_IAC) {
1851             *q++ = (char) TN_IAC;
1852             newcount ++;
1853         }
1854         *q++ = *p++;
1855         newcount++;
1856         left--;
1857     }
1858     if (appData.debugMode) {
1859         fprintf(debugFP, ">ICS: ");
1860         show_bytes(debugFP, buf, newcount);
1861         fprintf(debugFP, "\n");
1862     }
1863     outcount = OutputToProcess(pr, buf, newcount, outError);
1864     if (outcount < newcount) return -1; /* to be sure */
1865     return count;
1866 }
1867
1868 void
1869 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1870 {
1871     int outError, outCount;
1872     static int gotEof = 0;
1873     static FILE *ini;
1874
1875     /* Pass data read from player on to ICS */
1876     if (count > 0) {
1877         gotEof = 0;
1878         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1879         if (outCount < count) {
1880             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881         }
1882         if(have_sent_ICS_logon == 2) {
1883           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1884             fprintf(ini, "%s", message);
1885             have_sent_ICS_logon = 3;
1886           } else
1887             have_sent_ICS_logon = 1;
1888         } else if(have_sent_ICS_logon == 3) {
1889             fprintf(ini, "%s", message);
1890             fclose(ini);
1891           have_sent_ICS_logon = 1;
1892         }
1893     } else if (count < 0) {
1894         RemoveInputSource(isr);
1895         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1896     } else if (gotEof++ > 0) {
1897         RemoveInputSource(isr);
1898         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1899     }
1900 }
1901
1902 void
1903 KeepAlive ()
1904 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1905     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1906     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1907     SendToICS("date\n");
1908     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1909 }
1910
1911 /* added routine for printf style output to ics */
1912 void
1913 ics_printf (char *format, ...)
1914 {
1915     char buffer[MSG_SIZ];
1916     va_list args;
1917
1918     va_start(args, format);
1919     vsnprintf(buffer, sizeof(buffer), format, args);
1920     buffer[sizeof(buffer)-1] = '\0';
1921     SendToICS(buffer);
1922     va_end(args);
1923 }
1924
1925 void
1926 SendToICS (char *s)
1927 {
1928     int count, outCount, outError;
1929
1930     if (icsPR == NoProc) return;
1931
1932     count = strlen(s);
1933     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1934     if (outCount < count) {
1935         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1936     }
1937 }
1938
1939 /* This is used for sending logon scripts to the ICS. Sending
1940    without a delay causes problems when using timestamp on ICC
1941    (at least on my machine). */
1942 void
1943 SendToICSDelayed (char *s, long msdelay)
1944 {
1945     int count, outCount, outError;
1946
1947     if (icsPR == NoProc) return;
1948
1949     count = strlen(s);
1950     if (appData.debugMode) {
1951         fprintf(debugFP, ">ICS: ");
1952         show_bytes(debugFP, s, count);
1953         fprintf(debugFP, "\n");
1954     }
1955     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1956                                       msdelay);
1957     if (outCount < count) {
1958         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1959     }
1960 }
1961
1962
1963 /* Remove all highlighting escape sequences in s
1964    Also deletes any suffix starting with '('
1965    */
1966 char *
1967 StripHighlightAndTitle (char *s)
1968 {
1969     static char retbuf[MSG_SIZ];
1970     char *p = retbuf;
1971
1972     while (*s != NULLCHAR) {
1973         while (*s == '\033') {
1974             while (*s != NULLCHAR && !isalpha(*s)) s++;
1975             if (*s != NULLCHAR) s++;
1976         }
1977         while (*s != NULLCHAR && *s != '\033') {
1978             if (*s == '(' || *s == '[') {
1979                 *p = NULLCHAR;
1980                 return retbuf;
1981             }
1982             *p++ = *s++;
1983         }
1984     }
1985     *p = NULLCHAR;
1986     return retbuf;
1987 }
1988
1989 /* Remove all highlighting escape sequences in s */
1990 char *
1991 StripHighlight (char *s)
1992 {
1993     static char retbuf[MSG_SIZ];
1994     char *p = retbuf;
1995
1996     while (*s != NULLCHAR) {
1997         while (*s == '\033') {
1998             while (*s != NULLCHAR && !isalpha(*s)) s++;
1999             if (*s != NULLCHAR) s++;
2000         }
2001         while (*s != NULLCHAR && *s != '\033') {
2002             *p++ = *s++;
2003         }
2004     }
2005     *p = NULLCHAR;
2006     return retbuf;
2007 }
2008
2009 char *variantNames[] = VARIANT_NAMES;
2010 char *
2011 VariantName (VariantClass v)
2012 {
2013     return variantNames[v];
2014 }
2015
2016
2017 /* Identify a variant from the strings the chess servers use or the
2018    PGN Variant tag names we use. */
2019 VariantClass
2020 StringToVariant (char *e)
2021 {
2022     char *p;
2023     int wnum = -1;
2024     VariantClass v = VariantNormal;
2025     int i, found = FALSE;
2026     char buf[MSG_SIZ];
2027     int len;
2028
2029     if (!e) return v;
2030
2031     /* [HGM] skip over optional board-size prefixes */
2032     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2033         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2034         while( *e++ != '_');
2035     }
2036
2037     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2038         v = VariantNormal;
2039         found = TRUE;
2040     } else
2041     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2042       if (StrCaseStr(e, variantNames[i])) {
2043         v = (VariantClass) i;
2044         found = TRUE;
2045         break;
2046       }
2047     }
2048
2049     if (!found) {
2050       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2051           || StrCaseStr(e, "wild/fr")
2052           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2053         v = VariantFischeRandom;
2054       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2055                  (i = 1, p = StrCaseStr(e, "w"))) {
2056         p += i;
2057         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2058         if (isdigit(*p)) {
2059           wnum = atoi(p);
2060         } else {
2061           wnum = -1;
2062         }
2063         switch (wnum) {
2064         case 0: /* FICS only, actually */
2065         case 1:
2066           /* Castling legal even if K starts on d-file */
2067           v = VariantWildCastle;
2068           break;
2069         case 2:
2070         case 3:
2071         case 4:
2072           /* Castling illegal even if K & R happen to start in
2073              normal positions. */
2074           v = VariantNoCastle;
2075           break;
2076         case 5:
2077         case 7:
2078         case 8:
2079         case 10:
2080         case 11:
2081         case 12:
2082         case 13:
2083         case 14:
2084         case 15:
2085         case 18:
2086         case 19:
2087           /* Castling legal iff K & R start in normal positions */
2088           v = VariantNormal;
2089           break;
2090         case 6:
2091         case 20:
2092         case 21:
2093           /* Special wilds for position setup; unclear what to do here */
2094           v = VariantLoadable;
2095           break;
2096         case 9:
2097           /* Bizarre ICC game */
2098           v = VariantTwoKings;
2099           break;
2100         case 16:
2101           v = VariantKriegspiel;
2102           break;
2103         case 17:
2104           v = VariantLosers;
2105           break;
2106         case 22:
2107           v = VariantFischeRandom;
2108           break;
2109         case 23:
2110           v = VariantCrazyhouse;
2111           break;
2112         case 24:
2113           v = VariantBughouse;
2114           break;
2115         case 25:
2116           v = Variant3Check;
2117           break;
2118         case 26:
2119           /* Not quite the same as FICS suicide! */
2120           v = VariantGiveaway;
2121           break;
2122         case 27:
2123           v = VariantAtomic;
2124           break;
2125         case 28:
2126           v = VariantShatranj;
2127           break;
2128
2129         /* Temporary names for future ICC types.  The name *will* change in
2130            the next xboard/WinBoard release after ICC defines it. */
2131         case 29:
2132           v = Variant29;
2133           break;
2134         case 30:
2135           v = Variant30;
2136           break;
2137         case 31:
2138           v = Variant31;
2139           break;
2140         case 32:
2141           v = Variant32;
2142           break;
2143         case 33:
2144           v = Variant33;
2145           break;
2146         case 34:
2147           v = Variant34;
2148           break;
2149         case 35:
2150           v = Variant35;
2151           break;
2152         case 36:
2153           v = Variant36;
2154           break;
2155         case 37:
2156           v = VariantShogi;
2157           break;
2158         case 38:
2159           v = VariantXiangqi;
2160           break;
2161         case 39:
2162           v = VariantCourier;
2163           break;
2164         case 40:
2165           v = VariantGothic;
2166           break;
2167         case 41:
2168           v = VariantCapablanca;
2169           break;
2170         case 42:
2171           v = VariantKnightmate;
2172           break;
2173         case 43:
2174           v = VariantFairy;
2175           break;
2176         case 44:
2177           v = VariantCylinder;
2178           break;
2179         case 45:
2180           v = VariantFalcon;
2181           break;
2182         case 46:
2183           v = VariantCapaRandom;
2184           break;
2185         case 47:
2186           v = VariantBerolina;
2187           break;
2188         case 48:
2189           v = VariantJanus;
2190           break;
2191         case 49:
2192           v = VariantSuper;
2193           break;
2194         case 50:
2195           v = VariantGreat;
2196           break;
2197         case -1:
2198           /* Found "wild" or "w" in the string but no number;
2199              must assume it's normal chess. */
2200           v = VariantNormal;
2201           break;
2202         default:
2203           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2204           if( (len >= MSG_SIZ) && appData.debugMode )
2205             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2206
2207           DisplayError(buf, 0);
2208           v = VariantUnknown;
2209           break;
2210         }
2211       }
2212     }
2213     if (appData.debugMode) {
2214       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2215               e, wnum, VariantName(v));
2216     }
2217     return v;
2218 }
2219
2220 static int leftover_start = 0, leftover_len = 0;
2221 char star_match[STAR_MATCH_N][MSG_SIZ];
2222
2223 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2224    advance *index beyond it, and set leftover_start to the new value of
2225    *index; else return FALSE.  If pattern contains the character '*', it
2226    matches any sequence of characters not containing '\r', '\n', or the
2227    character following the '*' (if any), and the matched sequence(s) are
2228    copied into star_match.
2229    */
2230 int
2231 looking_at ( char *buf, int *index, char *pattern)
2232 {
2233     char *bufp = &buf[*index], *patternp = pattern;
2234     int star_count = 0;
2235     char *matchp = star_match[0];
2236
2237     for (;;) {
2238         if (*patternp == NULLCHAR) {
2239             *index = leftover_start = bufp - buf;
2240             *matchp = NULLCHAR;
2241             return TRUE;
2242         }
2243         if (*bufp == NULLCHAR) return FALSE;
2244         if (*patternp == '*') {
2245             if (*bufp == *(patternp + 1)) {
2246                 *matchp = NULLCHAR;
2247                 matchp = star_match[++star_count];
2248                 patternp += 2;
2249                 bufp++;
2250                 continue;
2251             } else if (*bufp == '\n' || *bufp == '\r') {
2252                 patternp++;
2253                 if (*patternp == NULLCHAR)
2254                   continue;
2255                 else
2256                   return FALSE;
2257             } else {
2258                 *matchp++ = *bufp++;
2259                 continue;
2260             }
2261         }
2262         if (*patternp != *bufp) return FALSE;
2263         patternp++;
2264         bufp++;
2265     }
2266 }
2267
2268 void
2269 SendToPlayer (char *data, int length)
2270 {
2271     int error, outCount;
2272     outCount = OutputToProcess(NoProc, data, length, &error);
2273     if (outCount < length) {
2274         DisplayFatalError(_("Error writing to display"), error, 1);
2275     }
2276 }
2277
2278 void
2279 PackHolding (char packed[], char *holding)
2280 {
2281     char *p = holding;
2282     char *q = packed;
2283     int runlength = 0;
2284     int curr = 9999;
2285     do {
2286         if (*p == curr) {
2287             runlength++;
2288         } else {
2289             switch (runlength) {
2290               case 0:
2291                 break;
2292               case 1:
2293                 *q++ = curr;
2294                 break;
2295               case 2:
2296                 *q++ = curr;
2297                 *q++ = curr;
2298                 break;
2299               default:
2300                 sprintf(q, "%d", runlength);
2301                 while (*q) q++;
2302                 *q++ = curr;
2303                 break;
2304             }
2305             runlength = 1;
2306             curr = *p;
2307         }
2308     } while (*p++);
2309     *q = NULLCHAR;
2310 }
2311
2312 /* Telnet protocol requests from the front end */
2313 void
2314 TelnetRequest (unsigned char ddww, unsigned char option)
2315 {
2316     unsigned char msg[3];
2317     int outCount, outError;
2318
2319     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2320
2321     if (appData.debugMode) {
2322         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2323         switch (ddww) {
2324           case TN_DO:
2325             ddwwStr = "DO";
2326             break;
2327           case TN_DONT:
2328             ddwwStr = "DONT";
2329             break;
2330           case TN_WILL:
2331             ddwwStr = "WILL";
2332             break;
2333           case TN_WONT:
2334             ddwwStr = "WONT";
2335             break;
2336           default:
2337             ddwwStr = buf1;
2338             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2339             break;
2340         }
2341         switch (option) {
2342           case TN_ECHO:
2343             optionStr = "ECHO";
2344             break;
2345           default:
2346             optionStr = buf2;
2347             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2348             break;
2349         }
2350         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2351     }
2352     msg[0] = TN_IAC;
2353     msg[1] = ddww;
2354     msg[2] = option;
2355     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2356     if (outCount < 3) {
2357         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2358     }
2359 }
2360
2361 void
2362 DoEcho ()
2363 {
2364     if (!appData.icsActive) return;
2365     TelnetRequest(TN_DO, TN_ECHO);
2366 }
2367
2368 void
2369 DontEcho ()
2370 {
2371     if (!appData.icsActive) return;
2372     TelnetRequest(TN_DONT, TN_ECHO);
2373 }
2374
2375 void
2376 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2377 {
2378     /* put the holdings sent to us by the server on the board holdings area */
2379     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2380     char p;
2381     ChessSquare piece;
2382
2383     if(gameInfo.holdingsWidth < 2)  return;
2384     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2385         return; // prevent overwriting by pre-board holdings
2386
2387     if( (int)lowestPiece >= BlackPawn ) {
2388         holdingsColumn = 0;
2389         countsColumn = 1;
2390         holdingsStartRow = BOARD_HEIGHT-1;
2391         direction = -1;
2392     } else {
2393         holdingsColumn = BOARD_WIDTH-1;
2394         countsColumn = BOARD_WIDTH-2;
2395         holdingsStartRow = 0;
2396         direction = 1;
2397     }
2398
2399     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2400         board[i][holdingsColumn] = EmptySquare;
2401         board[i][countsColumn]   = (ChessSquare) 0;
2402     }
2403     while( (p=*holdings++) != NULLCHAR ) {
2404         piece = CharToPiece( ToUpper(p) );
2405         if(piece == EmptySquare) continue;
2406         /*j = (int) piece - (int) WhitePawn;*/
2407         j = PieceToNumber(piece);
2408         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2409         if(j < 0) continue;               /* should not happen */
2410         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2411         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2412         board[holdingsStartRow+j*direction][countsColumn]++;
2413     }
2414 }
2415
2416
2417 void
2418 VariantSwitch (Board board, VariantClass newVariant)
2419 {
2420    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2421    static Board oldBoard;
2422
2423    startedFromPositionFile = FALSE;
2424    if(gameInfo.variant == newVariant) return;
2425
2426    /* [HGM] This routine is called each time an assignment is made to
2427     * gameInfo.variant during a game, to make sure the board sizes
2428     * are set to match the new variant. If that means adding or deleting
2429     * holdings, we shift the playing board accordingly
2430     * This kludge is needed because in ICS observe mode, we get boards
2431     * of an ongoing game without knowing the variant, and learn about the
2432     * latter only later. This can be because of the move list we requested,
2433     * in which case the game history is refilled from the beginning anyway,
2434     * but also when receiving holdings of a crazyhouse game. In the latter
2435     * case we want to add those holdings to the already received position.
2436     */
2437
2438
2439    if (appData.debugMode) {
2440      fprintf(debugFP, "Switch board from %s to %s\n",
2441              VariantName(gameInfo.variant), VariantName(newVariant));
2442      setbuf(debugFP, NULL);
2443    }
2444    shuffleOpenings = 0;       /* [HGM] shuffle */
2445    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2446    switch(newVariant)
2447      {
2448      case VariantShogi:
2449        newWidth = 9;  newHeight = 9;
2450        gameInfo.holdingsSize = 7;
2451      case VariantBughouse:
2452      case VariantCrazyhouse:
2453        newHoldingsWidth = 2; break;
2454      case VariantGreat:
2455        newWidth = 10;
2456      case VariantSuper:
2457        newHoldingsWidth = 2;
2458        gameInfo.holdingsSize = 8;
2459        break;
2460      case VariantGothic:
2461      case VariantCapablanca:
2462      case VariantCapaRandom:
2463        newWidth = 10;
2464      default:
2465        newHoldingsWidth = gameInfo.holdingsSize = 0;
2466      };
2467
2468    if(newWidth  != gameInfo.boardWidth  ||
2469       newHeight != gameInfo.boardHeight ||
2470       newHoldingsWidth != gameInfo.holdingsWidth ) {
2471
2472      /* shift position to new playing area, if needed */
2473      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2474        for(i=0; i<BOARD_HEIGHT; i++)
2475          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2476            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2477              board[i][j];
2478        for(i=0; i<newHeight; i++) {
2479          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2480          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2481        }
2482      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2483        for(i=0; i<BOARD_HEIGHT; i++)
2484          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2485            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2486              board[i][j];
2487      }
2488      board[HOLDINGS_SET] = 0;
2489      gameInfo.boardWidth  = newWidth;
2490      gameInfo.boardHeight = newHeight;
2491      gameInfo.holdingsWidth = newHoldingsWidth;
2492      gameInfo.variant = newVariant;
2493      InitDrawingSizes(-2, 0);
2494    } else gameInfo.variant = newVariant;
2495    CopyBoard(oldBoard, board);   // remember correctly formatted board
2496      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2497    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2498 }
2499
2500 static int loggedOn = FALSE;
2501
2502 /*-- Game start info cache: --*/
2503 int gs_gamenum;
2504 char gs_kind[MSG_SIZ];
2505 static char player1Name[128] = "";
2506 static char player2Name[128] = "";
2507 static char cont_seq[] = "\n\\   ";
2508 static int player1Rating = -1;
2509 static int player2Rating = -1;
2510 /*----------------------------*/
2511
2512 ColorClass curColor = ColorNormal;
2513 int suppressKibitz = 0;
2514
2515 // [HGM] seekgraph
2516 Boolean soughtPending = FALSE;
2517 Boolean seekGraphUp;
2518 #define MAX_SEEK_ADS 200
2519 #define SQUARE 0x80
2520 char *seekAdList[MAX_SEEK_ADS];
2521 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2522 float tcList[MAX_SEEK_ADS];
2523 char colorList[MAX_SEEK_ADS];
2524 int nrOfSeekAds = 0;
2525 int minRating = 1010, maxRating = 2800;
2526 int hMargin = 10, vMargin = 20, h, w;
2527 extern int squareSize, lineGap;
2528
2529 void
2530 PlotSeekAd (int i)
2531 {
2532         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2533         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2534         if(r < minRating+100 && r >=0 ) r = minRating+100;
2535         if(r > maxRating) r = maxRating;
2536         if(tc < 1.f) tc = 1.f;
2537         if(tc > 95.f) tc = 95.f;
2538         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2539         y = ((double)r - minRating)/(maxRating - minRating)
2540             * (h-vMargin-squareSize/8-1) + vMargin;
2541         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2542         if(strstr(seekAdList[i], " u ")) color = 1;
2543         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2544            !strstr(seekAdList[i], "bullet") &&
2545            !strstr(seekAdList[i], "blitz") &&
2546            !strstr(seekAdList[i], "standard") ) color = 2;
2547         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2548         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2549 }
2550
2551 void
2552 PlotSingleSeekAd (int i)
2553 {
2554         PlotSeekAd(i);
2555 }
2556
2557 void
2558 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2559 {
2560         char buf[MSG_SIZ], *ext = "";
2561         VariantClass v = StringToVariant(type);
2562         if(strstr(type, "wild")) {
2563             ext = type + 4; // append wild number
2564             if(v == VariantFischeRandom) type = "chess960"; else
2565             if(v == VariantLoadable) type = "setup"; else
2566             type = VariantName(v);
2567         }
2568         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2569         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2570             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2571             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2572             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2573             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2574             seekNrList[nrOfSeekAds] = nr;
2575             zList[nrOfSeekAds] = 0;
2576             seekAdList[nrOfSeekAds++] = StrSave(buf);
2577             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2578         }
2579 }
2580
2581 void
2582 EraseSeekDot (int i)
2583 {
2584     int x = xList[i], y = yList[i], d=squareSize/4, k;
2585     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2586     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2587     // now replot every dot that overlapped
2588     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2589         int xx = xList[k], yy = yList[k];
2590         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2591             DrawSeekDot(xx, yy, colorList[k]);
2592     }
2593 }
2594
2595 void
2596 RemoveSeekAd (int nr)
2597 {
2598         int i;
2599         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2600             EraseSeekDot(i);
2601             if(seekAdList[i]) free(seekAdList[i]);
2602             seekAdList[i] = seekAdList[--nrOfSeekAds];
2603             seekNrList[i] = seekNrList[nrOfSeekAds];
2604             ratingList[i] = ratingList[nrOfSeekAds];
2605             colorList[i]  = colorList[nrOfSeekAds];
2606             tcList[i] = tcList[nrOfSeekAds];
2607             xList[i]  = xList[nrOfSeekAds];
2608             yList[i]  = yList[nrOfSeekAds];
2609             zList[i]  = zList[nrOfSeekAds];
2610             seekAdList[nrOfSeekAds] = NULL;
2611             break;
2612         }
2613 }
2614
2615 Boolean
2616 MatchSoughtLine (char *line)
2617 {
2618     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2619     int nr, base, inc, u=0; char dummy;
2620
2621     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2622        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2623        (u=1) &&
2624        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2625         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2626         // match: compact and save the line
2627         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2628         return TRUE;
2629     }
2630     return FALSE;
2631 }
2632
2633 int
2634 DrawSeekGraph ()
2635 {
2636     int i;
2637     if(!seekGraphUp) return FALSE;
2638     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2639     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2640
2641     DrawSeekBackground(0, 0, w, h);
2642     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2643     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2644     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2645         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2646         yy = h-1-yy;
2647         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2648         if(i%500 == 0) {
2649             char buf[MSG_SIZ];
2650             snprintf(buf, MSG_SIZ, "%d", i);
2651             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2652         }
2653     }
2654     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2655     for(i=1; i<100; i+=(i<10?1:5)) {
2656         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2657         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2658         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2659             char buf[MSG_SIZ];
2660             snprintf(buf, MSG_SIZ, "%d", i);
2661             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2662         }
2663     }
2664     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2665     return TRUE;
2666 }
2667
2668 int
2669 SeekGraphClick (ClickType click, int x, int y, int moving)
2670 {
2671     static int lastDown = 0, displayed = 0, lastSecond;
2672     if(y < 0) return FALSE;
2673     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2674         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2675         if(!seekGraphUp) return FALSE;
2676         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2677         DrawPosition(TRUE, NULL);
2678         return TRUE;
2679     }
2680     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2681         if(click == Release || moving) return FALSE;
2682         nrOfSeekAds = 0;
2683         soughtPending = TRUE;
2684         SendToICS(ics_prefix);
2685         SendToICS("sought\n"); // should this be "sought all"?
2686     } else { // issue challenge based on clicked ad
2687         int dist = 10000; int i, closest = 0, second = 0;
2688         for(i=0; i<nrOfSeekAds; i++) {
2689             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2690             if(d < dist) { dist = d; closest = i; }
2691             second += (d - zList[i] < 120); // count in-range ads
2692             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2693         }
2694         if(dist < 120) {
2695             char buf[MSG_SIZ];
2696             second = (second > 1);
2697             if(displayed != closest || second != lastSecond) {
2698                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2699                 lastSecond = second; displayed = closest;
2700             }
2701             if(click == Press) {
2702                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2703                 lastDown = closest;
2704                 return TRUE;
2705             } // on press 'hit', only show info
2706             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2707             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2708             SendToICS(ics_prefix);
2709             SendToICS(buf);
2710             return TRUE; // let incoming board of started game pop down the graph
2711         } else if(click == Release) { // release 'miss' is ignored
2712             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2713             if(moving == 2) { // right up-click
2714                 nrOfSeekAds = 0; // refresh graph
2715                 soughtPending = TRUE;
2716                 SendToICS(ics_prefix);
2717                 SendToICS("sought\n"); // should this be "sought all"?
2718             }
2719             return TRUE;
2720         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2721         // press miss or release hit 'pop down' seek graph
2722         seekGraphUp = FALSE;
2723         DrawPosition(TRUE, NULL);
2724     }
2725     return TRUE;
2726 }
2727
2728 void
2729 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2730 {
2731 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2732 #define STARTED_NONE 0
2733 #define STARTED_MOVES 1
2734 #define STARTED_BOARD 2
2735 #define STARTED_OBSERVE 3
2736 #define STARTED_HOLDINGS 4
2737 #define STARTED_CHATTER 5
2738 #define STARTED_COMMENT 6
2739 #define STARTED_MOVES_NOHIDE 7
2740
2741     static int started = STARTED_NONE;
2742     static char parse[20000];
2743     static int parse_pos = 0;
2744     static char buf[BUF_SIZE + 1];
2745     static int firstTime = TRUE, intfSet = FALSE;
2746     static ColorClass prevColor = ColorNormal;
2747     static int savingComment = FALSE;
2748     static int cmatch = 0; // continuation sequence match
2749     char *bp;
2750     char str[MSG_SIZ];
2751     int i, oldi;
2752     int buf_len;
2753     int next_out;
2754     int tkind;
2755     int backup;    /* [DM] For zippy color lines */
2756     char *p;
2757     char talker[MSG_SIZ]; // [HGM] chat
2758     int channel;
2759
2760     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2761
2762     if (appData.debugMode) {
2763       if (!error) {
2764         fprintf(debugFP, "<ICS: ");
2765         show_bytes(debugFP, data, count);
2766         fprintf(debugFP, "\n");
2767       }
2768     }
2769
2770     if (appData.debugMode) { int f = forwardMostMove;
2771         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2772                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2773                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2774     }
2775     if (count > 0) {
2776         /* If last read ended with a partial line that we couldn't parse,
2777            prepend it to the new read and try again. */
2778         if (leftover_len > 0) {
2779             for (i=0; i<leftover_len; i++)
2780               buf[i] = buf[leftover_start + i];
2781         }
2782
2783     /* copy new characters into the buffer */
2784     bp = buf + leftover_len;
2785     buf_len=leftover_len;
2786     for (i=0; i<count; i++)
2787     {
2788         // ignore these
2789         if (data[i] == '\r')
2790             continue;
2791
2792         // join lines split by ICS?
2793         if (!appData.noJoin)
2794         {
2795             /*
2796                 Joining just consists of finding matches against the
2797                 continuation sequence, and discarding that sequence
2798                 if found instead of copying it.  So, until a match
2799                 fails, there's nothing to do since it might be the
2800                 complete sequence, and thus, something we don't want
2801                 copied.
2802             */
2803             if (data[i] == cont_seq[cmatch])
2804             {
2805                 cmatch++;
2806                 if (cmatch == strlen(cont_seq))
2807                 {
2808                     cmatch = 0; // complete match.  just reset the counter
2809
2810                     /*
2811                         it's possible for the ICS to not include the space
2812                         at the end of the last word, making our [correct]
2813                         join operation fuse two separate words.  the server
2814                         does this when the space occurs at the width setting.
2815                     */
2816                     if (!buf_len || buf[buf_len-1] != ' ')
2817                     {
2818                         *bp++ = ' ';
2819                         buf_len++;
2820                     }
2821                 }
2822                 continue;
2823             }
2824             else if (cmatch)
2825             {
2826                 /*
2827                     match failed, so we have to copy what matched before
2828                     falling through and copying this character.  In reality,
2829                     this will only ever be just the newline character, but
2830                     it doesn't hurt to be precise.
2831                 */
2832                 strncpy(bp, cont_seq, cmatch);
2833                 bp += cmatch;
2834                 buf_len += cmatch;
2835                 cmatch = 0;
2836             }
2837         }
2838
2839         // copy this char
2840         *bp++ = data[i];
2841         buf_len++;
2842     }
2843
2844         buf[buf_len] = NULLCHAR;
2845 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2846         next_out = 0;
2847         leftover_start = 0;
2848
2849         i = 0;
2850         while (i < buf_len) {
2851             /* Deal with part of the TELNET option negotiation
2852                protocol.  We refuse to do anything beyond the
2853                defaults, except that we allow the WILL ECHO option,
2854                which ICS uses to turn off password echoing when we are
2855                directly connected to it.  We reject this option
2856                if localLineEditing mode is on (always on in xboard)
2857                and we are talking to port 23, which might be a real
2858                telnet server that will try to keep WILL ECHO on permanently.
2859              */
2860             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2861                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2862                 unsigned char option;
2863                 oldi = i;
2864                 switch ((unsigned char) buf[++i]) {
2865                   case TN_WILL:
2866                     if (appData.debugMode)
2867                       fprintf(debugFP, "\n<WILL ");
2868                     switch (option = (unsigned char) buf[++i]) {
2869                       case TN_ECHO:
2870                         if (appData.debugMode)
2871                           fprintf(debugFP, "ECHO ");
2872                         /* Reply only if this is a change, according
2873                            to the protocol rules. */
2874                         if (remoteEchoOption) break;
2875                         if (appData.localLineEditing &&
2876                             atoi(appData.icsPort) == TN_PORT) {
2877                             TelnetRequest(TN_DONT, TN_ECHO);
2878                         } else {
2879                             EchoOff();
2880                             TelnetRequest(TN_DO, TN_ECHO);
2881                             remoteEchoOption = TRUE;
2882                         }
2883                         break;
2884                       default:
2885                         if (appData.debugMode)
2886                           fprintf(debugFP, "%d ", option);
2887                         /* Whatever this is, we don't want it. */
2888                         TelnetRequest(TN_DONT, option);
2889                         break;
2890                     }
2891                     break;
2892                   case TN_WONT:
2893                     if (appData.debugMode)
2894                       fprintf(debugFP, "\n<WONT ");
2895                     switch (option = (unsigned char) buf[++i]) {
2896                       case TN_ECHO:
2897                         if (appData.debugMode)
2898                           fprintf(debugFP, "ECHO ");
2899                         /* Reply only if this is a change, according
2900                            to the protocol rules. */
2901                         if (!remoteEchoOption) break;
2902                         EchoOn();
2903                         TelnetRequest(TN_DONT, TN_ECHO);
2904                         remoteEchoOption = FALSE;
2905                         break;
2906                       default:
2907                         if (appData.debugMode)
2908                           fprintf(debugFP, "%d ", (unsigned char) option);
2909                         /* Whatever this is, it must already be turned
2910                            off, because we never agree to turn on
2911                            anything non-default, so according to the
2912                            protocol rules, we don't reply. */
2913                         break;
2914                     }
2915                     break;
2916                   case TN_DO:
2917                     if (appData.debugMode)
2918                       fprintf(debugFP, "\n<DO ");
2919                     switch (option = (unsigned char) buf[++i]) {
2920                       default:
2921                         /* Whatever this is, we refuse to do it. */
2922                         if (appData.debugMode)
2923                           fprintf(debugFP, "%d ", option);
2924                         TelnetRequest(TN_WONT, option);
2925                         break;
2926                     }
2927                     break;
2928                   case TN_DONT:
2929                     if (appData.debugMode)
2930                       fprintf(debugFP, "\n<DONT ");
2931                     switch (option = (unsigned char) buf[++i]) {
2932                       default:
2933                         if (appData.debugMode)
2934                           fprintf(debugFP, "%d ", option);
2935                         /* Whatever this is, we are already not doing
2936                            it, because we never agree to do anything
2937                            non-default, so according to the protocol
2938                            rules, we don't reply. */
2939                         break;
2940                     }
2941                     break;
2942                   case TN_IAC:
2943                     if (appData.debugMode)
2944                       fprintf(debugFP, "\n<IAC ");
2945                     /* Doubled IAC; pass it through */
2946                     i--;
2947                     break;
2948                   default:
2949                     if (appData.debugMode)
2950                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2951                     /* Drop all other telnet commands on the floor */
2952                     break;
2953                 }
2954                 if (oldi > next_out)
2955                   SendToPlayer(&buf[next_out], oldi - next_out);
2956                 if (++i > next_out)
2957                   next_out = i;
2958                 continue;
2959             }
2960
2961             /* OK, this at least will *usually* work */
2962             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2963                 loggedOn = TRUE;
2964             }
2965
2966             if (loggedOn && !intfSet) {
2967                 if (ics_type == ICS_ICC) {
2968                   snprintf(str, MSG_SIZ,
2969                           "/set-quietly interface %s\n/set-quietly style 12\n",
2970                           programVersion);
2971                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2972                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2973                 } else if (ics_type == ICS_CHESSNET) {
2974                   snprintf(str, MSG_SIZ, "/style 12\n");
2975                 } else {
2976                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2977                   strcat(str, programVersion);
2978                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2979                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2980                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2981 #ifdef WIN32
2982                   strcat(str, "$iset nohighlight 1\n");
2983 #endif
2984                   strcat(str, "$iset lock 1\n$style 12\n");
2985                 }
2986                 SendToICS(str);
2987                 NotifyFrontendLogin();
2988                 intfSet = TRUE;
2989             }
2990
2991             if (started == STARTED_COMMENT) {
2992                 /* Accumulate characters in comment */
2993                 parse[parse_pos++] = buf[i];
2994                 if (buf[i] == '\n') {
2995                     parse[parse_pos] = NULLCHAR;
2996                     if(chattingPartner>=0) {
2997                         char mess[MSG_SIZ];
2998                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2999                         OutputChatMessage(chattingPartner, mess);
3000                         chattingPartner = -1;
3001                         next_out = i+1; // [HGM] suppress printing in ICS window
3002                     } else
3003                     if(!suppressKibitz) // [HGM] kibitz
3004                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3005                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3006                         int nrDigit = 0, nrAlph = 0, j;
3007                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3008                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3009                         parse[parse_pos] = NULLCHAR;
3010                         // try to be smart: if it does not look like search info, it should go to
3011                         // ICS interaction window after all, not to engine-output window.
3012                         for(j=0; j<parse_pos; j++) { // count letters and digits
3013                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3014                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3015                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3016                         }
3017                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3018                             int depth=0; float score;
3019                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3020                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3021                                 pvInfoList[forwardMostMove-1].depth = depth;
3022                                 pvInfoList[forwardMostMove-1].score = 100*score;
3023                             }
3024                             OutputKibitz(suppressKibitz, parse);
3025                         } else {
3026                             char tmp[MSG_SIZ];
3027                             if(gameMode == IcsObserving) // restore original ICS messages
3028                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3029                             else
3030                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3031                             SendToPlayer(tmp, strlen(tmp));
3032                         }
3033                         next_out = i+1; // [HGM] suppress printing in ICS window
3034                     }
3035                     started = STARTED_NONE;
3036                 } else {
3037                     /* Don't match patterns against characters in comment */
3038                     i++;
3039                     continue;
3040                 }
3041             }
3042             if (started == STARTED_CHATTER) {
3043                 if (buf[i] != '\n') {
3044                     /* Don't match patterns against characters in chatter */
3045                     i++;
3046                     continue;
3047                 }
3048                 started = STARTED_NONE;
3049                 if(suppressKibitz) next_out = i+1;
3050             }
3051
3052             /* Kludge to deal with rcmd protocol */
3053             if (firstTime && looking_at(buf, &i, "\001*")) {
3054                 DisplayFatalError(&buf[1], 0, 1);
3055                 continue;
3056             } else {
3057                 firstTime = FALSE;
3058             }
3059
3060             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3061                 ics_type = ICS_ICC;
3062                 ics_prefix = "/";
3063                 if (appData.debugMode)
3064                   fprintf(debugFP, "ics_type %d\n", ics_type);
3065                 continue;
3066             }
3067             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3068                 ics_type = ICS_FICS;
3069                 ics_prefix = "$";
3070                 if (appData.debugMode)
3071                   fprintf(debugFP, "ics_type %d\n", ics_type);
3072                 continue;
3073             }
3074             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3075                 ics_type = ICS_CHESSNET;
3076                 ics_prefix = "/";
3077                 if (appData.debugMode)
3078                   fprintf(debugFP, "ics_type %d\n", ics_type);
3079                 continue;
3080             }
3081
3082             if (!loggedOn &&
3083                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3084                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3085                  looking_at(buf, &i, "will be \"*\""))) {
3086               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3087               continue;
3088             }
3089
3090             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3091               char buf[MSG_SIZ];
3092               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3093               DisplayIcsInteractionTitle(buf);
3094               have_set_title = TRUE;
3095             }
3096
3097             /* skip finger notes */
3098             if (started == STARTED_NONE &&
3099                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3100                  (buf[i] == '1' && buf[i+1] == '0')) &&
3101                 buf[i+2] == ':' && buf[i+3] == ' ') {
3102               started = STARTED_CHATTER;
3103               i += 3;
3104               continue;
3105             }
3106
3107             oldi = i;
3108             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3109             if(appData.seekGraph) {
3110                 if(soughtPending && MatchSoughtLine(buf+i)) {
3111                     i = strstr(buf+i, "rated") - buf;
3112                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3113                     next_out = leftover_start = i;
3114                     started = STARTED_CHATTER;
3115                     suppressKibitz = TRUE;
3116                     continue;
3117                 }
3118                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3119                         && looking_at(buf, &i, "* ads displayed")) {
3120                     soughtPending = FALSE;
3121                     seekGraphUp = TRUE;
3122                     DrawSeekGraph();
3123                     continue;
3124                 }
3125                 if(appData.autoRefresh) {
3126                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3127                         int s = (ics_type == ICS_ICC); // ICC format differs
3128                         if(seekGraphUp)
3129                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3130                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3131                         looking_at(buf, &i, "*% "); // eat prompt
3132                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3133                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3134                         next_out = i; // suppress
3135                         continue;
3136                     }
3137                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3138                         char *p = star_match[0];
3139                         while(*p) {
3140                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3141                             while(*p && *p++ != ' '); // next
3142                         }
3143                         looking_at(buf, &i, "*% "); // eat prompt
3144                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3145                         next_out = i;
3146                         continue;
3147                     }
3148                 }
3149             }
3150
3151             /* skip formula vars */
3152             if (started == STARTED_NONE &&
3153                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3154               started = STARTED_CHATTER;
3155               i += 3;
3156               continue;
3157             }
3158
3159             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3160             if (appData.autoKibitz && started == STARTED_NONE &&
3161                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3162                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3163                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3164                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3165                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3166                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3167                         suppressKibitz = TRUE;
3168                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3169                         next_out = i;
3170                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3171                                 && (gameMode == IcsPlayingWhite)) ||
3172                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3173                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3174                             started = STARTED_CHATTER; // own kibitz we simply discard
3175                         else {
3176                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3177                             parse_pos = 0; parse[0] = NULLCHAR;
3178                             savingComment = TRUE;
3179                             suppressKibitz = gameMode != IcsObserving ? 2 :
3180                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3181                         }
3182                         continue;
3183                 } else
3184                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3185                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3186                          && atoi(star_match[0])) {
3187                     // suppress the acknowledgements of our own autoKibitz
3188                     char *p;
3189                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3190                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3191                     SendToPlayer(star_match[0], strlen(star_match[0]));
3192                     if(looking_at(buf, &i, "*% ")) // eat prompt
3193                         suppressKibitz = FALSE;
3194                     next_out = i;
3195                     continue;
3196                 }
3197             } // [HGM] kibitz: end of patch
3198
3199             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3200
3201             // [HGM] chat: intercept tells by users for which we have an open chat window
3202             channel = -1;
3203             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3204                                            looking_at(buf, &i, "* whispers:") ||
3205                                            looking_at(buf, &i, "* kibitzes:") ||
3206                                            looking_at(buf, &i, "* shouts:") ||
3207                                            looking_at(buf, &i, "* c-shouts:") ||
3208                                            looking_at(buf, &i, "--> * ") ||
3209                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3210                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3211                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3212                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3213                 int p;
3214                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3215                 chattingPartner = -1;
3216
3217                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3218                 for(p=0; p<MAX_CHAT; p++) {
3219                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3220                     talker[0] = '['; strcat(talker, "] ");
3221                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3222                     chattingPartner = p; break;
3223                     }
3224                 } else
3225                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3226                 for(p=0; p<MAX_CHAT; p++) {
3227                     if(!strcmp("kibitzes", chatPartner[p])) {
3228                         talker[0] = '['; strcat(talker, "] ");
3229                         chattingPartner = p; break;
3230                     }
3231                 } else
3232                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3233                 for(p=0; p<MAX_CHAT; p++) {
3234                     if(!strcmp("whispers", chatPartner[p])) {
3235                         talker[0] = '['; strcat(talker, "] ");
3236                         chattingPartner = p; break;
3237                     }
3238                 } else
3239                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3240                   if(buf[i-8] == '-' && buf[i-3] == 't')
3241                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3242                     if(!strcmp("c-shouts", chatPartner[p])) {
3243                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3244                         chattingPartner = p; break;
3245                     }
3246                   }
3247                   if(chattingPartner < 0)
3248                   for(p=0; p<MAX_CHAT; p++) {
3249                     if(!strcmp("shouts", chatPartner[p])) {
3250                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3251                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3252                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3253                         chattingPartner = p; break;
3254                     }
3255                   }
3256                 }
3257                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3258                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3259                     talker[0] = 0; Colorize(ColorTell, FALSE);
3260                     chattingPartner = p; break;
3261                 }
3262                 if(chattingPartner<0) i = oldi; else {
3263                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3264                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3265                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3266                     started = STARTED_COMMENT;
3267                     parse_pos = 0; parse[0] = NULLCHAR;
3268                     savingComment = 3 + chattingPartner; // counts as TRUE
3269                     suppressKibitz = TRUE;
3270                     continue;
3271                 }
3272             } // [HGM] chat: end of patch
3273
3274           backup = i;
3275             if (appData.zippyTalk || appData.zippyPlay) {
3276                 /* [DM] Backup address for color zippy lines */
3277 #if ZIPPY
3278                if (loggedOn == TRUE)
3279                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3280                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3281 #endif
3282             } // [DM] 'else { ' deleted
3283                 if (
3284                     /* Regular tells and says */
3285                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3286                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3287                     looking_at(buf, &i, "* says: ") ||
3288                     /* Don't color "message" or "messages" output */
3289                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3290                     looking_at(buf, &i, "*. * at *:*: ") ||
3291                     looking_at(buf, &i, "--* (*:*): ") ||
3292                     /* Message notifications (same color as tells) */
3293                     looking_at(buf, &i, "* has left a message ") ||
3294                     looking_at(buf, &i, "* just sent you a message:\n") ||
3295                     /* Whispers and kibitzes */
3296                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3297                     looking_at(buf, &i, "* kibitzes: ") ||
3298                     /* Channel tells */
3299                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3300
3301                   if (tkind == 1 && strchr(star_match[0], ':')) {
3302                       /* Avoid "tells you:" spoofs in channels */
3303                      tkind = 3;
3304                   }
3305                   if (star_match[0][0] == NULLCHAR ||
3306                       strchr(star_match[0], ' ') ||
3307                       (tkind == 3 && strchr(star_match[1], ' '))) {
3308                     /* Reject bogus matches */
3309                     i = oldi;
3310                   } else {
3311                     if (appData.colorize) {
3312                       if (oldi > next_out) {
3313                         SendToPlayer(&buf[next_out], oldi - next_out);
3314                         next_out = oldi;
3315                       }
3316                       switch (tkind) {
3317                       case 1:
3318                         Colorize(ColorTell, FALSE);
3319                         curColor = ColorTell;
3320                         break;
3321                       case 2:
3322                         Colorize(ColorKibitz, FALSE);
3323                         curColor = ColorKibitz;
3324                         break;
3325                       case 3:
3326                         p = strrchr(star_match[1], '(');
3327                         if (p == NULL) {
3328                           p = star_match[1];
3329                         } else {
3330                           p++;
3331                         }
3332                         if (atoi(p) == 1) {
3333                           Colorize(ColorChannel1, FALSE);
3334                           curColor = ColorChannel1;
3335                         } else {
3336                           Colorize(ColorChannel, FALSE);
3337                           curColor = ColorChannel;
3338                         }
3339                         break;
3340                       case 5:
3341                         curColor = ColorNormal;
3342                         break;
3343                       }
3344                     }
3345                     if (started == STARTED_NONE && appData.autoComment &&
3346                         (gameMode == IcsObserving ||
3347                          gameMode == IcsPlayingWhite ||
3348                          gameMode == IcsPlayingBlack)) {
3349                       parse_pos = i - oldi;
3350                       memcpy(parse, &buf[oldi], parse_pos);
3351                       parse[parse_pos] = NULLCHAR;
3352                       started = STARTED_COMMENT;
3353                       savingComment = TRUE;
3354                     } else {
3355                       started = STARTED_CHATTER;
3356                       savingComment = FALSE;
3357                     }
3358                     loggedOn = TRUE;
3359                     continue;
3360                   }
3361                 }
3362
3363                 if (looking_at(buf, &i, "* s-shouts: ") ||
3364                     looking_at(buf, &i, "* c-shouts: ")) {
3365                     if (appData.colorize) {
3366                         if (oldi > next_out) {
3367                             SendToPlayer(&buf[next_out], oldi - next_out);
3368                             next_out = oldi;
3369                         }
3370                         Colorize(ColorSShout, FALSE);
3371                         curColor = ColorSShout;
3372                     }
3373                     loggedOn = TRUE;
3374                     started = STARTED_CHATTER;
3375                     continue;
3376                 }
3377
3378                 if (looking_at(buf, &i, "--->")) {
3379                     loggedOn = TRUE;
3380                     continue;
3381                 }
3382
3383                 if (looking_at(buf, &i, "* shouts: ") ||
3384                     looking_at(buf, &i, "--> ")) {
3385                     if (appData.colorize) {
3386                         if (oldi > next_out) {
3387                             SendToPlayer(&buf[next_out], oldi - next_out);
3388                             next_out = oldi;
3389                         }
3390                         Colorize(ColorShout, FALSE);
3391                         curColor = ColorShout;
3392                     }
3393                     loggedOn = TRUE;
3394                     started = STARTED_CHATTER;
3395                     continue;
3396                 }
3397
3398                 if (looking_at( buf, &i, "Challenge:")) {
3399                     if (appData.colorize) {
3400                         if (oldi > next_out) {
3401                             SendToPlayer(&buf[next_out], oldi - next_out);
3402                             next_out = oldi;
3403                         }
3404                         Colorize(ColorChallenge, FALSE);
3405                         curColor = ColorChallenge;
3406                     }
3407                     loggedOn = TRUE;
3408                     continue;
3409                 }
3410
3411                 if (looking_at(buf, &i, "* offers you") ||
3412                     looking_at(buf, &i, "* offers to be") ||
3413                     looking_at(buf, &i, "* would like to") ||
3414                     looking_at(buf, &i, "* requests to") ||
3415                     looking_at(buf, &i, "Your opponent offers") ||
3416                     looking_at(buf, &i, "Your opponent requests")) {
3417
3418                     if (appData.colorize) {
3419                         if (oldi > next_out) {
3420                             SendToPlayer(&buf[next_out], oldi - next_out);
3421                             next_out = oldi;
3422                         }
3423                         Colorize(ColorRequest, FALSE);
3424                         curColor = ColorRequest;
3425                     }
3426                     continue;
3427                 }
3428
3429                 if (looking_at(buf, &i, "* (*) seeking")) {
3430                     if (appData.colorize) {
3431                         if (oldi > next_out) {
3432                             SendToPlayer(&buf[next_out], oldi - next_out);
3433                             next_out = oldi;
3434                         }
3435                         Colorize(ColorSeek, FALSE);
3436                         curColor = ColorSeek;
3437                     }
3438                     continue;
3439             }
3440
3441           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3442
3443             if (looking_at(buf, &i, "\\   ")) {
3444                 if (prevColor != ColorNormal) {
3445                     if (oldi > next_out) {
3446                         SendToPlayer(&buf[next_out], oldi - next_out);
3447                         next_out = oldi;
3448                     }
3449                     Colorize(prevColor, TRUE);
3450                     curColor = prevColor;
3451                 }
3452                 if (savingComment) {
3453                     parse_pos = i - oldi;
3454                     memcpy(parse, &buf[oldi], parse_pos);
3455                     parse[parse_pos] = NULLCHAR;
3456                     started = STARTED_COMMENT;
3457                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3458                         chattingPartner = savingComment - 3; // kludge to remember the box
3459                 } else {
3460                     started = STARTED_CHATTER;
3461                 }
3462                 continue;
3463             }
3464
3465             if (looking_at(buf, &i, "Black Strength :") ||
3466                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3467                 looking_at(buf, &i, "<10>") ||
3468                 looking_at(buf, &i, "#@#")) {
3469                 /* Wrong board style */
3470                 loggedOn = TRUE;
3471                 SendToICS(ics_prefix);
3472                 SendToICS("set style 12\n");
3473                 SendToICS(ics_prefix);
3474                 SendToICS("refresh\n");
3475                 continue;
3476             }
3477
3478             if (looking_at(buf, &i, "login:")) {
3479               if (!have_sent_ICS_logon) {
3480                 if(ICSInitScript())
3481                   have_sent_ICS_logon = 1;
3482                 else // no init script was found
3483                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3484               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3485                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3486               }
3487                 continue;
3488             }
3489
3490             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3491                 (looking_at(buf, &i, "\n<12> ") ||
3492                  looking_at(buf, &i, "<12> "))) {
3493                 loggedOn = TRUE;
3494                 if (oldi > next_out) {
3495                     SendToPlayer(&buf[next_out], oldi - next_out);
3496                 }
3497                 next_out = i;
3498                 started = STARTED_BOARD;
3499                 parse_pos = 0;
3500                 continue;
3501             }
3502
3503             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3504                 looking_at(buf, &i, "<b1> ")) {
3505                 if (oldi > next_out) {
3506                     SendToPlayer(&buf[next_out], oldi - next_out);
3507                 }
3508                 next_out = i;
3509                 started = STARTED_HOLDINGS;
3510                 parse_pos = 0;
3511                 continue;
3512             }
3513
3514             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3515                 loggedOn = TRUE;
3516                 /* Header for a move list -- first line */
3517
3518                 switch (ics_getting_history) {
3519                   case H_FALSE:
3520                     switch (gameMode) {
3521                       case IcsIdle:
3522                       case BeginningOfGame:
3523                         /* User typed "moves" or "oldmoves" while we
3524                            were idle.  Pretend we asked for these
3525                            moves and soak them up so user can step
3526                            through them and/or save them.
3527                            */
3528                         Reset(FALSE, TRUE);
3529                         gameMode = IcsObserving;
3530                         ModeHighlight();
3531                         ics_gamenum = -1;
3532                         ics_getting_history = H_GOT_UNREQ_HEADER;
3533                         break;
3534                       case EditGame: /*?*/
3535                       case EditPosition: /*?*/
3536                         /* Should above feature work in these modes too? */
3537                         /* For now it doesn't */
3538                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3539                         break;
3540                       default:
3541                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3542                         break;
3543                     }
3544                     break;
3545                   case H_REQUESTED:
3546                     /* Is this the right one? */
3547                     if (gameInfo.white && gameInfo.black &&
3548                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3549                         strcmp(gameInfo.black, star_match[2]) == 0) {
3550                         /* All is well */
3551                         ics_getting_history = H_GOT_REQ_HEADER;
3552                     }
3553                     break;
3554                   case H_GOT_REQ_HEADER:
3555                   case H_GOT_UNREQ_HEADER:
3556                   case H_GOT_UNWANTED_HEADER:
3557                   case H_GETTING_MOVES:
3558                     /* Should not happen */
3559                     DisplayError(_("Error gathering move list: two headers"), 0);
3560                     ics_getting_history = H_FALSE;
3561                     break;
3562                 }
3563
3564                 /* Save player ratings into gameInfo if needed */
3565                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3566                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3567                     (gameInfo.whiteRating == -1 ||
3568                      gameInfo.blackRating == -1)) {
3569
3570                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3571                     gameInfo.blackRating = string_to_rating(star_match[3]);
3572                     if (appData.debugMode)
3573                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3574                               gameInfo.whiteRating, gameInfo.blackRating);
3575                 }
3576                 continue;
3577             }
3578
3579             if (looking_at(buf, &i,
3580               "* * match, initial time: * minute*, increment: * second")) {
3581                 /* Header for a move list -- second line */
3582                 /* Initial board will follow if this is a wild game */
3583                 if (gameInfo.event != NULL) free(gameInfo.event);
3584                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3585                 gameInfo.event = StrSave(str);
3586                 /* [HGM] we switched variant. Translate boards if needed. */
3587                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3588                 continue;
3589             }
3590
3591             if (looking_at(buf, &i, "Move  ")) {
3592                 /* Beginning of a move list */
3593                 switch (ics_getting_history) {
3594                   case H_FALSE:
3595                     /* Normally should not happen */
3596                     /* Maybe user hit reset while we were parsing */
3597                     break;
3598                   case H_REQUESTED:
3599                     /* Happens if we are ignoring a move list that is not
3600                      * the one we just requested.  Common if the user
3601                      * tries to observe two games without turning off
3602                      * getMoveList */
3603                     break;
3604                   case H_GETTING_MOVES:
3605                     /* Should not happen */
3606                     DisplayError(_("Error gathering move list: nested"), 0);
3607                     ics_getting_history = H_FALSE;
3608                     break;
3609                   case H_GOT_REQ_HEADER:
3610                     ics_getting_history = H_GETTING_MOVES;
3611                     started = STARTED_MOVES;
3612                     parse_pos = 0;
3613                     if (oldi > next_out) {
3614                         SendToPlayer(&buf[next_out], oldi - next_out);
3615                     }
3616                     break;
3617                   case H_GOT_UNREQ_HEADER:
3618                     ics_getting_history = H_GETTING_MOVES;
3619                     started = STARTED_MOVES_NOHIDE;
3620                     parse_pos = 0;
3621                     break;
3622                   case H_GOT_UNWANTED_HEADER:
3623                     ics_getting_history = H_FALSE;
3624                     break;
3625                 }
3626                 continue;
3627             }
3628
3629             if (looking_at(buf, &i, "% ") ||
3630                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3631                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3632                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3633                     soughtPending = FALSE;
3634                     seekGraphUp = TRUE;
3635                     DrawSeekGraph();
3636                 }
3637                 if(suppressKibitz) next_out = i;
3638                 savingComment = FALSE;
3639                 suppressKibitz = 0;
3640                 switch (started) {
3641                   case STARTED_MOVES:
3642                   case STARTED_MOVES_NOHIDE:
3643                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3644                     parse[parse_pos + i - oldi] = NULLCHAR;
3645                     ParseGameHistory(parse);
3646 #if ZIPPY
3647                     if (appData.zippyPlay && first.initDone) {
3648                         FeedMovesToProgram(&first, forwardMostMove);
3649                         if (gameMode == IcsPlayingWhite) {
3650                             if (WhiteOnMove(forwardMostMove)) {
3651                                 if (first.sendTime) {
3652                                   if (first.useColors) {
3653                                     SendToProgram("black\n", &first);
3654                                   }
3655                                   SendTimeRemaining(&first, TRUE);
3656                                 }
3657                                 if (first.useColors) {
3658                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3659                                 }
3660                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3661                                 first.maybeThinking = TRUE;
3662                             } else {
3663                                 if (first.usePlayother) {
3664                                   if (first.sendTime) {
3665                                     SendTimeRemaining(&first, TRUE);
3666                                   }
3667                                   SendToProgram("playother\n", &first);
3668                                   firstMove = FALSE;
3669                                 } else {
3670                                   firstMove = TRUE;
3671                                 }
3672                             }
3673                         } else if (gameMode == IcsPlayingBlack) {
3674                             if (!WhiteOnMove(forwardMostMove)) {
3675                                 if (first.sendTime) {
3676                                   if (first.useColors) {
3677                                     SendToProgram("white\n", &first);
3678                                   }
3679                                   SendTimeRemaining(&first, FALSE);
3680                                 }
3681                                 if (first.useColors) {
3682                                   SendToProgram("black\n", &first);
3683                                 }
3684                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3685                                 first.maybeThinking = TRUE;
3686                             } else {
3687                                 if (first.usePlayother) {
3688                                   if (first.sendTime) {
3689                                     SendTimeRemaining(&first, FALSE);
3690                                   }
3691                                   SendToProgram("playother\n", &first);
3692                                   firstMove = FALSE;
3693                                 } else {
3694                                   firstMove = TRUE;
3695                                 }
3696                             }
3697                         }
3698                     }
3699 #endif
3700                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3701                         /* Moves came from oldmoves or moves command
3702                            while we weren't doing anything else.
3703                            */
3704                         currentMove = forwardMostMove;
3705                         ClearHighlights();/*!!could figure this out*/
3706                         flipView = appData.flipView;
3707                         DrawPosition(TRUE, boards[currentMove]);
3708                         DisplayBothClocks();
3709                         snprintf(str, MSG_SIZ, "%s %s %s",
3710                                 gameInfo.white, _("vs."),  gameInfo.black);
3711                         DisplayTitle(str);
3712                         gameMode = IcsIdle;
3713                     } else {
3714                         /* Moves were history of an active game */
3715                         if (gameInfo.resultDetails != NULL) {
3716                             free(gameInfo.resultDetails);
3717                             gameInfo.resultDetails = NULL;
3718                         }
3719                     }
3720                     HistorySet(parseList, backwardMostMove,
3721                                forwardMostMove, currentMove-1);
3722                     DisplayMove(currentMove - 1);
3723                     if (started == STARTED_MOVES) next_out = i;
3724                     started = STARTED_NONE;
3725                     ics_getting_history = H_FALSE;
3726                     break;
3727
3728                   case STARTED_OBSERVE:
3729                     started = STARTED_NONE;
3730                     SendToICS(ics_prefix);
3731                     SendToICS("refresh\n");
3732                     break;
3733
3734                   default:
3735                     break;
3736                 }
3737                 if(bookHit) { // [HGM] book: simulate book reply
3738                     static char bookMove[MSG_SIZ]; // a bit generous?
3739
3740                     programStats.nodes = programStats.depth = programStats.time =
3741                     programStats.score = programStats.got_only_move = 0;
3742                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3743
3744                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3745                     strcat(bookMove, bookHit);
3746                     HandleMachineMove(bookMove, &first);
3747                 }
3748                 continue;
3749             }
3750
3751             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3752                  started == STARTED_HOLDINGS ||
3753                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3754                 /* Accumulate characters in move list or board */
3755                 parse[parse_pos++] = buf[i];
3756             }
3757
3758             /* Start of game messages.  Mostly we detect start of game
3759                when the first board image arrives.  On some versions
3760                of the ICS, though, we need to do a "refresh" after starting
3761                to observe in order to get the current board right away. */
3762             if (looking_at(buf, &i, "Adding game * to observation list")) {
3763                 started = STARTED_OBSERVE;
3764                 continue;
3765             }
3766
3767             /* Handle auto-observe */
3768             if (appData.autoObserve &&
3769                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3770                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3771                 char *player;
3772                 /* Choose the player that was highlighted, if any. */
3773                 if (star_match[0][0] == '\033' ||
3774                     star_match[1][0] != '\033') {
3775                     player = star_match[0];
3776                 } else {
3777                     player = star_match[2];
3778                 }
3779                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3780                         ics_prefix, StripHighlightAndTitle(player));
3781                 SendToICS(str);
3782
3783                 /* Save ratings from notify string */
3784                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3785                 player1Rating = string_to_rating(star_match[1]);
3786                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3787                 player2Rating = string_to_rating(star_match[3]);
3788
3789                 if (appData.debugMode)
3790                   fprintf(debugFP,
3791                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3792                           player1Name, player1Rating,
3793                           player2Name, player2Rating);
3794
3795                 continue;
3796             }
3797
3798             /* Deal with automatic examine mode after a game,
3799                and with IcsObserving -> IcsExamining transition */
3800             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3801                 looking_at(buf, &i, "has made you an examiner of game *")) {
3802
3803                 int gamenum = atoi(star_match[0]);
3804                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3805                     gamenum == ics_gamenum) {
3806                     /* We were already playing or observing this game;
3807                        no need to refetch history */
3808                     gameMode = IcsExamining;
3809                     if (pausing) {
3810                         pauseExamForwardMostMove = forwardMostMove;
3811                     } else if (currentMove < forwardMostMove) {
3812                         ForwardInner(forwardMostMove);
3813                     }
3814                 } else {
3815                     /* I don't think this case really can happen */
3816                     SendToICS(ics_prefix);
3817                     SendToICS("refresh\n");
3818                 }
3819                 continue;
3820             }
3821
3822             /* Error messages */
3823 //          if (ics_user_moved) {
3824             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3825                 if (looking_at(buf, &i, "Illegal move") ||
3826                     looking_at(buf, &i, "Not a legal move") ||
3827                     looking_at(buf, &i, "Your king is in check") ||
3828                     looking_at(buf, &i, "It isn't your turn") ||
3829                     looking_at(buf, &i, "It is not your move")) {
3830                     /* Illegal move */
3831                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3832                         currentMove = forwardMostMove-1;
3833                         DisplayMove(currentMove - 1); /* before DMError */
3834                         DrawPosition(FALSE, boards[currentMove]);
3835                         SwitchClocks(forwardMostMove-1); // [HGM] race
3836                         DisplayBothClocks();
3837                     }
3838                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3839                     ics_user_moved = 0;
3840                     continue;
3841                 }
3842             }
3843
3844             if (looking_at(buf, &i, "still have time") ||
3845                 looking_at(buf, &i, "not out of time") ||
3846                 looking_at(buf, &i, "either player is out of time") ||
3847                 looking_at(buf, &i, "has timeseal; checking")) {
3848                 /* We must have called his flag a little too soon */
3849                 whiteFlag = blackFlag = FALSE;
3850                 continue;
3851             }
3852
3853             if (looking_at(buf, &i, "added * seconds to") ||
3854                 looking_at(buf, &i, "seconds were added to")) {
3855                 /* Update the clocks */
3856                 SendToICS(ics_prefix);
3857                 SendToICS("refresh\n");
3858                 continue;
3859             }
3860
3861             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3862                 ics_clock_paused = TRUE;
3863                 StopClocks();
3864                 continue;
3865             }
3866
3867             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3868                 ics_clock_paused = FALSE;
3869                 StartClocks();
3870                 continue;
3871             }
3872
3873             /* Grab player ratings from the Creating: message.
3874                Note we have to check for the special case when
3875                the ICS inserts things like [white] or [black]. */
3876             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3877                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3878                 /* star_matches:
3879                    0    player 1 name (not necessarily white)
3880                    1    player 1 rating
3881                    2    empty, white, or black (IGNORED)
3882                    3    player 2 name (not necessarily black)
3883                    4    player 2 rating
3884
3885                    The names/ratings are sorted out when the game
3886                    actually starts (below).
3887                 */
3888                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3889                 player1Rating = string_to_rating(star_match[1]);
3890                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3891                 player2Rating = string_to_rating(star_match[4]);
3892
3893                 if (appData.debugMode)
3894                   fprintf(debugFP,
3895                           "Ratings from 'Creating:' %s %d, %s %d\n",
3896                           player1Name, player1Rating,
3897                           player2Name, player2Rating);
3898
3899                 continue;
3900             }
3901
3902             /* Improved generic start/end-of-game messages */
3903             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3904                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3905                 /* If tkind == 0: */
3906                 /* star_match[0] is the game number */
3907                 /*           [1] is the white player's name */
3908                 /*           [2] is the black player's name */
3909                 /* For end-of-game: */
3910                 /*           [3] is the reason for the game end */
3911                 /*           [4] is a PGN end game-token, preceded by " " */
3912                 /* For start-of-game: */
3913                 /*           [3] begins with "Creating" or "Continuing" */
3914                 /*           [4] is " *" or empty (don't care). */
3915                 int gamenum = atoi(star_match[0]);
3916                 char *whitename, *blackname, *why, *endtoken;
3917                 ChessMove endtype = EndOfFile;
3918
3919                 if (tkind == 0) {
3920                   whitename = star_match[1];
3921                   blackname = star_match[2];
3922                   why = star_match[3];
3923                   endtoken = star_match[4];
3924                 } else {
3925                   whitename = star_match[1];
3926                   blackname = star_match[3];
3927                   why = star_match[5];
3928                   endtoken = star_match[6];
3929                 }
3930
3931                 /* Game start messages */
3932                 if (strncmp(why, "Creating ", 9) == 0 ||
3933                     strncmp(why, "Continuing ", 11) == 0) {
3934                     gs_gamenum = gamenum;
3935                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3936                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3937                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3938 #if ZIPPY
3939                     if (appData.zippyPlay) {
3940                         ZippyGameStart(whitename, blackname);
3941                     }
3942 #endif /*ZIPPY*/
3943                     partnerBoardValid = FALSE; // [HGM] bughouse
3944                     continue;
3945                 }
3946
3947                 /* Game end messages */
3948                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3949                     ics_gamenum != gamenum) {
3950                     continue;
3951                 }
3952                 while (endtoken[0] == ' ') endtoken++;
3953                 switch (endtoken[0]) {
3954                   case '*':
3955                   default:
3956                     endtype = GameUnfinished;
3957                     break;
3958                   case '0':
3959                     endtype = BlackWins;
3960                     break;
3961                   case '1':
3962                     if (endtoken[1] == '/')
3963                       endtype = GameIsDrawn;
3964                     else
3965                       endtype = WhiteWins;
3966                     break;
3967                 }
3968                 GameEnds(endtype, why, GE_ICS);
3969 #if ZIPPY
3970                 if (appData.zippyPlay && first.initDone) {
3971                     ZippyGameEnd(endtype, why);
3972                     if (first.pr == NoProc) {
3973                       /* Start the next process early so that we'll
3974                          be ready for the next challenge */
3975                       StartChessProgram(&first);
3976                     }
3977                     /* Send "new" early, in case this command takes
3978                        a long time to finish, so that we'll be ready
3979                        for the next challenge. */
3980                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3981                     Reset(TRUE, TRUE);
3982                 }
3983 #endif /*ZIPPY*/
3984                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3985                 continue;
3986             }
3987
3988             if (looking_at(buf, &i, "Removing game * from observation") ||
3989                 looking_at(buf, &i, "no longer observing game *") ||
3990                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3991                 if (gameMode == IcsObserving &&
3992                     atoi(star_match[0]) == ics_gamenum)
3993                   {
3994                       /* icsEngineAnalyze */
3995                       if (appData.icsEngineAnalyze) {
3996                             ExitAnalyzeMode();
3997                             ModeHighlight();
3998                       }
3999                       StopClocks();
4000                       gameMode = IcsIdle;
4001                       ics_gamenum = -1;
4002                       ics_user_moved = FALSE;
4003                   }
4004                 continue;
4005             }
4006
4007             if (looking_at(buf, &i, "no longer examining game *")) {
4008                 if (gameMode == IcsExamining &&
4009                     atoi(star_match[0]) == ics_gamenum)
4010                   {
4011                       gameMode = IcsIdle;
4012                       ics_gamenum = -1;
4013                       ics_user_moved = FALSE;
4014                   }
4015                 continue;
4016             }
4017
4018             /* Advance leftover_start past any newlines we find,
4019                so only partial lines can get reparsed */
4020             if (looking_at(buf, &i, "\n")) {
4021                 prevColor = curColor;
4022                 if (curColor != ColorNormal) {
4023                     if (oldi > next_out) {
4024                         SendToPlayer(&buf[next_out], oldi - next_out);
4025                         next_out = oldi;
4026                     }
4027                     Colorize(ColorNormal, FALSE);
4028                     curColor = ColorNormal;
4029                 }
4030                 if (started == STARTED_BOARD) {
4031                     started = STARTED_NONE;
4032                     parse[parse_pos] = NULLCHAR;
4033                     ParseBoard12(parse);
4034                     ics_user_moved = 0;
4035
4036                     /* Send premove here */
4037                     if (appData.premove) {
4038                       char str[MSG_SIZ];
4039                       if (currentMove == 0 &&
4040                           gameMode == IcsPlayingWhite &&
4041                           appData.premoveWhite) {
4042                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4043                         if (appData.debugMode)
4044                           fprintf(debugFP, "Sending premove:\n");
4045                         SendToICS(str);
4046                       } else if (currentMove == 1 &&
4047                                  gameMode == IcsPlayingBlack &&
4048                                  appData.premoveBlack) {
4049                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4050                         if (appData.debugMode)
4051                           fprintf(debugFP, "Sending premove:\n");
4052                         SendToICS(str);
4053                       } else if (gotPremove) {
4054                         gotPremove = 0;
4055                         ClearPremoveHighlights();
4056                         if (appData.debugMode)
4057                           fprintf(debugFP, "Sending premove:\n");
4058                           UserMoveEvent(premoveFromX, premoveFromY,
4059                                         premoveToX, premoveToY,
4060                                         premovePromoChar);
4061                       }
4062                     }
4063
4064                     /* Usually suppress following prompt */
4065                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4066                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4067                         if (looking_at(buf, &i, "*% ")) {
4068                             savingComment = FALSE;
4069                             suppressKibitz = 0;
4070                         }
4071                     }
4072                     next_out = i;
4073                 } else if (started == STARTED_HOLDINGS) {
4074                     int gamenum;
4075                     char new_piece[MSG_SIZ];
4076                     started = STARTED_NONE;
4077                     parse[parse_pos] = NULLCHAR;
4078                     if (appData.debugMode)
4079                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4080                                                         parse, currentMove);
4081                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4082                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4083                         if (gameInfo.variant == VariantNormal) {
4084                           /* [HGM] We seem to switch variant during a game!
4085                            * Presumably no holdings were displayed, so we have
4086                            * to move the position two files to the right to
4087                            * create room for them!
4088                            */
4089                           VariantClass newVariant;
4090                           switch(gameInfo.boardWidth) { // base guess on board width
4091                                 case 9:  newVariant = VariantShogi; break;
4092                                 case 10: newVariant = VariantGreat; break;
4093                                 default: newVariant = VariantCrazyhouse; break;
4094                           }
4095                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4096                           /* Get a move list just to see the header, which
4097                              will tell us whether this is really bug or zh */
4098                           if (ics_getting_history == H_FALSE) {
4099                             ics_getting_history = H_REQUESTED;
4100                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4101                             SendToICS(str);
4102                           }
4103                         }
4104                         new_piece[0] = NULLCHAR;
4105                         sscanf(parse, "game %d white [%s black [%s <- %s",
4106                                &gamenum, white_holding, black_holding,
4107                                new_piece);
4108                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4109                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4110                         /* [HGM] copy holdings to board holdings area */
4111                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4112                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4113                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4114 #if ZIPPY
4115                         if (appData.zippyPlay && first.initDone) {
4116                             ZippyHoldings(white_holding, black_holding,
4117                                           new_piece);
4118                         }
4119 #endif /*ZIPPY*/
4120                         if (tinyLayout || smallLayout) {
4121                             char wh[16], bh[16];
4122                             PackHolding(wh, white_holding);
4123                             PackHolding(bh, black_holding);
4124                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4125                                     gameInfo.white, gameInfo.black);
4126                         } else {
4127                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4128                                     gameInfo.white, white_holding, _("vs."),
4129                                     gameInfo.black, black_holding);
4130                         }
4131                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4132                         DrawPosition(FALSE, boards[currentMove]);
4133                         DisplayTitle(str);
4134                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4135                         sscanf(parse, "game %d white [%s black [%s <- %s",
4136                                &gamenum, white_holding, black_holding,
4137                                new_piece);
4138                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4139                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4140                         /* [HGM] copy holdings to partner-board holdings area */
4141                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4142                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4143                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4144                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4145                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4146                       }
4147                     }
4148                     /* Suppress following prompt */
4149                     if (looking_at(buf, &i, "*% ")) {
4150                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4151                         savingComment = FALSE;
4152                         suppressKibitz = 0;
4153                     }
4154                     next_out = i;
4155                 }
4156                 continue;
4157             }
4158
4159             i++;                /* skip unparsed character and loop back */
4160         }
4161
4162         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4163 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4164 //          SendToPlayer(&buf[next_out], i - next_out);
4165             started != STARTED_HOLDINGS && leftover_start > next_out) {
4166             SendToPlayer(&buf[next_out], leftover_start - next_out);
4167             next_out = i;
4168         }
4169
4170         leftover_len = buf_len - leftover_start;
4171         /* if buffer ends with something we couldn't parse,
4172            reparse it after appending the next read */
4173
4174     } else if (count == 0) {
4175         RemoveInputSource(isr);
4176         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4177     } else {
4178         DisplayFatalError(_("Error reading from ICS"), error, 1);
4179     }
4180 }
4181
4182
4183 /* Board style 12 looks like this:
4184
4185    <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
4186
4187  * The "<12> " is stripped before it gets to this routine.  The two
4188  * trailing 0's (flip state and clock ticking) are later addition, and
4189  * some chess servers may not have them, or may have only the first.
4190  * Additional trailing fields may be added in the future.
4191  */
4192
4193 #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"
4194
4195 #define RELATION_OBSERVING_PLAYED    0
4196 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4197 #define RELATION_PLAYING_MYMOVE      1
4198 #define RELATION_PLAYING_NOTMYMOVE  -1
4199 #define RELATION_EXAMINING           2
4200 #define RELATION_ISOLATED_BOARD     -3
4201 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4202
4203 void
4204 ParseBoard12 (char *string)
4205 {
4206 #if ZIPPY
4207     int i, takeback;
4208     char *bookHit = NULL; // [HGM] book
4209 #endif
4210     GameMode newGameMode;
4211     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4212     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4213     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4214     char to_play, board_chars[200];
4215     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4216     char black[32], white[32];
4217     Board board;
4218     int prevMove = currentMove;
4219     int ticking = 2;
4220     ChessMove moveType;
4221     int fromX, fromY, toX, toY;
4222     char promoChar;
4223     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4224     Boolean weird = FALSE, reqFlag = FALSE;
4225
4226     fromX = fromY = toX = toY = -1;
4227
4228     newGame = FALSE;
4229
4230     if (appData.debugMode)
4231       fprintf(debugFP, "Parsing board: %s\n", string);
4232
4233     move_str[0] = NULLCHAR;
4234     elapsed_time[0] = NULLCHAR;
4235     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4236         int  i = 0, j;
4237         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4238             if(string[i] == ' ') { ranks++; files = 0; }
4239             else files++;
4240             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4241             i++;
4242         }
4243         for(j = 0; j <i; j++) board_chars[j] = string[j];
4244         board_chars[i] = '\0';
4245         string += i + 1;
4246     }
4247     n = sscanf(string, PATTERN, &to_play, &double_push,
4248                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4249                &gamenum, white, black, &relation, &basetime, &increment,
4250                &white_stren, &black_stren, &white_time, &black_time,
4251                &moveNum, str, elapsed_time, move_str, &ics_flip,
4252                &ticking);
4253
4254     if (n < 21) {
4255         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4256         DisplayError(str, 0);
4257         return;
4258     }
4259
4260     /* Convert the move number to internal form */
4261     moveNum = (moveNum - 1) * 2;
4262     if (to_play == 'B') moveNum++;
4263     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4264       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4265                         0, 1);
4266       return;
4267     }
4268
4269     switch (relation) {
4270       case RELATION_OBSERVING_PLAYED:
4271       case RELATION_OBSERVING_STATIC:
4272         if (gamenum == -1) {
4273             /* Old ICC buglet */
4274             relation = RELATION_OBSERVING_STATIC;
4275         }
4276         newGameMode = IcsObserving;
4277         break;
4278       case RELATION_PLAYING_MYMOVE:
4279       case RELATION_PLAYING_NOTMYMOVE:
4280         newGameMode =
4281           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4282             IcsPlayingWhite : IcsPlayingBlack;
4283         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4284         break;
4285       case RELATION_EXAMINING:
4286         newGameMode = IcsExamining;
4287         break;
4288       case RELATION_ISOLATED_BOARD:
4289       default:
4290         /* Just display this board.  If user was doing something else,
4291            we will forget about it until the next board comes. */
4292         newGameMode = IcsIdle;
4293         break;
4294       case RELATION_STARTING_POSITION:
4295         newGameMode = gameMode;
4296         break;
4297     }
4298
4299     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4300         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4301          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4302       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4303       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4304       static int lastBgGame = -1;
4305       char *toSqr;
4306       for (k = 0; k < ranks; k++) {
4307         for (j = 0; j < files; j++)
4308           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4309         if(gameInfo.holdingsWidth > 1) {
4310              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4311              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4312         }
4313       }
4314       CopyBoard(partnerBoard, board);
4315       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4316         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4317         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4318       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4319       if(toSqr = strchr(str, '-')) {
4320         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4321         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4322       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4323       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4324       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4325       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4326       if(twoBoards) {
4327           DisplayWhiteClock(white_time*fac, to_play == 'W');
4328           DisplayBlackClock(black_time*fac, to_play != 'W');
4329           activePartner = to_play;
4330           if(gamenum != lastBgGame) {
4331               char buf[MSG_SIZ];
4332               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4333               DisplayTitle(buf);
4334           }
4335           lastBgGame = gamenum;
4336           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4337                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4338       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4339                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4340       if(!twoBoards) DisplayMessage(partnerStatus, "");
4341         partnerBoardValid = TRUE;
4342       return;
4343     }
4344
4345     if(appData.dualBoard && appData.bgObserve) {
4346         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4347             SendToICS(ics_prefix), SendToICS("pobserve\n");
4348         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4349             char buf[MSG_SIZ];
4350             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4351             SendToICS(buf);
4352         }
4353     }
4354
4355     /* Modify behavior for initial board display on move listing
4356        of wild games.
4357        */
4358     switch (ics_getting_history) {
4359       case H_FALSE:
4360       case H_REQUESTED:
4361         break;
4362       case H_GOT_REQ_HEADER:
4363       case H_GOT_UNREQ_HEADER:
4364         /* This is the initial position of the current game */
4365         gamenum = ics_gamenum;
4366         moveNum = 0;            /* old ICS bug workaround */
4367         if (to_play == 'B') {
4368           startedFromSetupPosition = TRUE;
4369           blackPlaysFirst = TRUE;
4370           moveNum = 1;
4371           if (forwardMostMove == 0) forwardMostMove = 1;
4372           if (backwardMostMove == 0) backwardMostMove = 1;
4373           if (currentMove == 0) currentMove = 1;
4374         }
4375         newGameMode = gameMode;
4376         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4377         break;
4378       case H_GOT_UNWANTED_HEADER:
4379         /* This is an initial board that we don't want */
4380         return;
4381       case H_GETTING_MOVES:
4382         /* Should not happen */
4383         DisplayError(_("Error gathering move list: extra board"), 0);
4384         ics_getting_history = H_FALSE;
4385         return;
4386     }
4387
4388    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4389                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4390                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4391      /* [HGM] We seem to have switched variant unexpectedly
4392       * Try to guess new variant from board size
4393       */
4394           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4395           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4396           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4397           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4398           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4399           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4400           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4401           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4402           /* Get a move list just to see the header, which
4403              will tell us whether this is really bug or zh */
4404           if (ics_getting_history == H_FALSE) {
4405             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4406             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4407             SendToICS(str);
4408           }
4409     }
4410
4411     /* Take action if this is the first board of a new game, or of a
4412        different game than is currently being displayed.  */
4413     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4414         relation == RELATION_ISOLATED_BOARD) {
4415
4416         /* Forget the old game and get the history (if any) of the new one */
4417         if (gameMode != BeginningOfGame) {
4418           Reset(TRUE, TRUE);
4419         }
4420         newGame = TRUE;
4421         if (appData.autoRaiseBoard) BoardToTop();
4422         prevMove = -3;
4423         if (gamenum == -1) {
4424             newGameMode = IcsIdle;
4425         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4426                    appData.getMoveList && !reqFlag) {
4427             /* Need to get game history */
4428             ics_getting_history = H_REQUESTED;
4429             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4430             SendToICS(str);
4431         }
4432
4433         /* Initially flip the board to have black on the bottom if playing
4434            black or if the ICS flip flag is set, but let the user change
4435            it with the Flip View button. */
4436         flipView = appData.autoFlipView ?
4437           (newGameMode == IcsPlayingBlack) || ics_flip :
4438           appData.flipView;
4439
4440         /* Done with values from previous mode; copy in new ones */
4441         gameMode = newGameMode;
4442         ModeHighlight();
4443         ics_gamenum = gamenum;
4444         if (gamenum == gs_gamenum) {
4445             int klen = strlen(gs_kind);
4446             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4447             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4448             gameInfo.event = StrSave(str);
4449         } else {
4450             gameInfo.event = StrSave("ICS game");
4451         }
4452         gameInfo.site = StrSave(appData.icsHost);
4453         gameInfo.date = PGNDate();
4454         gameInfo.round = StrSave("-");
4455         gameInfo.white = StrSave(white);
4456         gameInfo.black = StrSave(black);
4457         timeControl = basetime * 60 * 1000;
4458         timeControl_2 = 0;
4459         timeIncrement = increment * 1000;
4460         movesPerSession = 0;
4461         gameInfo.timeControl = TimeControlTagValue();
4462         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4463   if (appData.debugMode) {
4464     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4465     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4466     setbuf(debugFP, NULL);
4467   }
4468
4469         gameInfo.outOfBook = NULL;
4470
4471         /* Do we have the ratings? */
4472         if (strcmp(player1Name, white) == 0 &&
4473             strcmp(player2Name, black) == 0) {
4474             if (appData.debugMode)
4475               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4476                       player1Rating, player2Rating);
4477             gameInfo.whiteRating = player1Rating;
4478             gameInfo.blackRating = player2Rating;
4479         } else if (strcmp(player2Name, white) == 0 &&
4480                    strcmp(player1Name, black) == 0) {
4481             if (appData.debugMode)
4482               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4483                       player2Rating, player1Rating);
4484             gameInfo.whiteRating = player2Rating;
4485             gameInfo.blackRating = player1Rating;
4486         }
4487         player1Name[0] = player2Name[0] = NULLCHAR;
4488
4489         /* Silence shouts if requested */
4490         if (appData.quietPlay &&
4491             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4492             SendToICS(ics_prefix);
4493             SendToICS("set shout 0\n");
4494         }
4495     }
4496
4497     /* Deal with midgame name changes */
4498     if (!newGame) {
4499         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4500             if (gameInfo.white) free(gameInfo.white);
4501             gameInfo.white = StrSave(white);
4502         }
4503         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4504             if (gameInfo.black) free(gameInfo.black);
4505             gameInfo.black = StrSave(black);
4506         }
4507     }
4508
4509     /* Throw away game result if anything actually changes in examine mode */
4510     if (gameMode == IcsExamining && !newGame) {
4511         gameInfo.result = GameUnfinished;
4512         if (gameInfo.resultDetails != NULL) {
4513             free(gameInfo.resultDetails);
4514             gameInfo.resultDetails = NULL;
4515         }
4516     }
4517
4518     /* In pausing && IcsExamining mode, we ignore boards coming
4519        in if they are in a different variation than we are. */
4520     if (pauseExamInvalid) return;
4521     if (pausing && gameMode == IcsExamining) {
4522         if (moveNum <= pauseExamForwardMostMove) {
4523             pauseExamInvalid = TRUE;
4524             forwardMostMove = pauseExamForwardMostMove;
4525             return;
4526         }
4527     }
4528
4529   if (appData.debugMode) {
4530     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4531   }
4532     /* Parse the board */
4533     for (k = 0; k < ranks; k++) {
4534       for (j = 0; j < files; j++)
4535         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4536       if(gameInfo.holdingsWidth > 1) {
4537            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4538            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4539       }
4540     }
4541     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4542       board[5][BOARD_RGHT+1] = WhiteAngel;
4543       board[6][BOARD_RGHT+1] = WhiteMarshall;
4544       board[1][0] = BlackMarshall;
4545       board[2][0] = BlackAngel;
4546       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4547     }
4548     CopyBoard(boards[moveNum], board);
4549     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4550     if (moveNum == 0) {
4551         startedFromSetupPosition =
4552           !CompareBoards(board, initialPosition);
4553         if(startedFromSetupPosition)
4554             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4555     }
4556
4557     /* [HGM] Set castling rights. Take the outermost Rooks,
4558        to make it also work for FRC opening positions. Note that board12
4559        is really defective for later FRC positions, as it has no way to
4560        indicate which Rook can castle if they are on the same side of King.
4561        For the initial position we grant rights to the outermost Rooks,
4562        and remember thos rights, and we then copy them on positions
4563        later in an FRC game. This means WB might not recognize castlings with
4564        Rooks that have moved back to their original position as illegal,
4565        but in ICS mode that is not its job anyway.
4566     */
4567     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4568     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4569
4570         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4571             if(board[0][i] == WhiteRook) j = i;
4572         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4573         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4574             if(board[0][i] == WhiteRook) j = i;
4575         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4576         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4577             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4578         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4579         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4580             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4581         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4582
4583         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4584         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4585         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4586             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4587         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4588             if(board[BOARD_HEIGHT-1][k] == bKing)
4589                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4590         if(gameInfo.variant == VariantTwoKings) {
4591             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4592             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4593             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4594         }
4595     } else { int r;
4596         r = boards[moveNum][CASTLING][0] = initialRights[0];
4597         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4598         r = boards[moveNum][CASTLING][1] = initialRights[1];
4599         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4600         r = boards[moveNum][CASTLING][3] = initialRights[3];
4601         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4602         r = boards[moveNum][CASTLING][4] = initialRights[4];
4603         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4604         /* wildcastle kludge: always assume King has rights */
4605         r = boards[moveNum][CASTLING][2] = initialRights[2];
4606         r = boards[moveNum][CASTLING][5] = initialRights[5];
4607     }
4608     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4609     boards[moveNum][EP_STATUS] = EP_NONE;
4610     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4611     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4612     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4613
4614
4615     if (ics_getting_history == H_GOT_REQ_HEADER ||
4616         ics_getting_history == H_GOT_UNREQ_HEADER) {
4617         /* This was an initial position from a move list, not
4618            the current position */
4619         return;
4620     }
4621
4622     /* Update currentMove and known move number limits */
4623     newMove = newGame || moveNum > forwardMostMove;
4624
4625     if (newGame) {
4626         forwardMostMove = backwardMostMove = currentMove = moveNum;
4627         if (gameMode == IcsExamining && moveNum == 0) {
4628           /* Workaround for ICS limitation: we are not told the wild
4629              type when starting to examine a game.  But if we ask for
4630              the move list, the move list header will tell us */
4631             ics_getting_history = H_REQUESTED;
4632             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4633             SendToICS(str);
4634         }
4635     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4636                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4637 #if ZIPPY
4638         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4639         /* [HGM] applied this also to an engine that is silently watching        */
4640         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4641             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4642             gameInfo.variant == currentlyInitializedVariant) {
4643           takeback = forwardMostMove - moveNum;
4644           for (i = 0; i < takeback; i++) {
4645             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4646             SendToProgram("undo\n", &first);
4647           }
4648         }
4649 #endif
4650
4651         forwardMostMove = moveNum;
4652         if (!pausing || currentMove > forwardMostMove)
4653           currentMove = forwardMostMove;
4654     } else {
4655         /* New part of history that is not contiguous with old part */
4656         if (pausing && gameMode == IcsExamining) {
4657             pauseExamInvalid = TRUE;
4658             forwardMostMove = pauseExamForwardMostMove;
4659             return;
4660         }
4661         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4662 #if ZIPPY
4663             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4664                 // [HGM] when we will receive the move list we now request, it will be
4665                 // fed to the engine from the first move on. So if the engine is not
4666                 // in the initial position now, bring it there.
4667                 InitChessProgram(&first, 0);
4668             }
4669 #endif
4670             ics_getting_history = H_REQUESTED;
4671             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4672             SendToICS(str);
4673         }
4674         forwardMostMove = backwardMostMove = currentMove = moveNum;
4675     }
4676
4677     /* Update the clocks */
4678     if (strchr(elapsed_time, '.')) {
4679       /* Time is in ms */
4680       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4681       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4682     } else {
4683       /* Time is in seconds */
4684       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4685       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4686     }
4687
4688
4689 #if ZIPPY
4690     if (appData.zippyPlay && newGame &&
4691         gameMode != IcsObserving && gameMode != IcsIdle &&
4692         gameMode != IcsExamining)
4693       ZippyFirstBoard(moveNum, basetime, increment);
4694 #endif
4695
4696     /* Put the move on the move list, first converting
4697        to canonical algebraic form. */
4698     if (moveNum > 0) {
4699   if (appData.debugMode) {
4700     int f = forwardMostMove;
4701     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4702             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4703             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4704     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4705     fprintf(debugFP, "moveNum = %d\n", moveNum);
4706     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4707     setbuf(debugFP, NULL);
4708   }
4709         if (moveNum <= backwardMostMove) {
4710             /* We don't know what the board looked like before
4711                this move.  Punt. */
4712           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4713             strcat(parseList[moveNum - 1], " ");
4714             strcat(parseList[moveNum - 1], elapsed_time);
4715             moveList[moveNum - 1][0] = NULLCHAR;
4716         } else if (strcmp(move_str, "none") == 0) {
4717             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4718             /* Again, we don't know what the board looked like;
4719                this is really the start of the game. */
4720             parseList[moveNum - 1][0] = NULLCHAR;
4721             moveList[moveNum - 1][0] = NULLCHAR;
4722             backwardMostMove = moveNum;
4723             startedFromSetupPosition = TRUE;
4724             fromX = fromY = toX = toY = -1;
4725         } else {
4726           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4727           //                 So we parse the long-algebraic move string in stead of the SAN move
4728           int valid; char buf[MSG_SIZ], *prom;
4729
4730           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4731                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4732           // str looks something like "Q/a1-a2"; kill the slash
4733           if(str[1] == '/')
4734             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4735           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4736           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4737                 strcat(buf, prom); // long move lacks promo specification!
4738           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4739                 if(appData.debugMode)
4740                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4741                 safeStrCpy(move_str, buf, MSG_SIZ);
4742           }
4743           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4744                                 &fromX, &fromY, &toX, &toY, &promoChar)
4745                || ParseOneMove(buf, moveNum - 1, &moveType,
4746                                 &fromX, &fromY, &toX, &toY, &promoChar);
4747           // end of long SAN patch
4748           if (valid) {
4749             (void) CoordsToAlgebraic(boards[moveNum - 1],
4750                                      PosFlags(moveNum - 1),
4751                                      fromY, fromX, toY, toX, promoChar,
4752                                      parseList[moveNum-1]);
4753             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4754               case MT_NONE:
4755               case MT_STALEMATE:
4756               default:
4757                 break;
4758               case MT_CHECK:
4759                 if(gameInfo.variant != VariantShogi)
4760                     strcat(parseList[moveNum - 1], "+");
4761                 break;
4762               case MT_CHECKMATE:
4763               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4764                 strcat(parseList[moveNum - 1], "#");
4765                 break;
4766             }
4767             strcat(parseList[moveNum - 1], " ");
4768             strcat(parseList[moveNum - 1], elapsed_time);
4769             /* currentMoveString is set as a side-effect of ParseOneMove */
4770             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4771             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4772             strcat(moveList[moveNum - 1], "\n");
4773
4774             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4775                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4776               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4777                 ChessSquare old, new = boards[moveNum][k][j];
4778                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4779                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4780                   if(old == new) continue;
4781                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4782                   else if(new == WhiteWazir || new == BlackWazir) {
4783                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4784                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4785                       else boards[moveNum][k][j] = old; // preserve type of Gold
4786                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4787                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4788               }
4789           } else {
4790             /* Move from ICS was illegal!?  Punt. */
4791             if (appData.debugMode) {
4792               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4793               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4794             }
4795             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4796             strcat(parseList[moveNum - 1], " ");
4797             strcat(parseList[moveNum - 1], elapsed_time);
4798             moveList[moveNum - 1][0] = NULLCHAR;
4799             fromX = fromY = toX = toY = -1;
4800           }
4801         }
4802   if (appData.debugMode) {
4803     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4804     setbuf(debugFP, NULL);
4805   }
4806
4807 #if ZIPPY
4808         /* Send move to chess program (BEFORE animating it). */
4809         if (appData.zippyPlay && !newGame && newMove &&
4810            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4811
4812             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4813                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4814                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4815                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4816                             move_str);
4817                     DisplayError(str, 0);
4818                 } else {
4819                     if (first.sendTime) {
4820                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4821                     }
4822                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4823                     if (firstMove && !bookHit) {
4824                         firstMove = FALSE;
4825                         if (first.useColors) {
4826                           SendToProgram(gameMode == IcsPlayingWhite ?
4827                                         "white\ngo\n" :
4828                                         "black\ngo\n", &first);
4829                         } else {
4830                           SendToProgram("go\n", &first);
4831                         }
4832                         first.maybeThinking = TRUE;
4833                     }
4834                 }
4835             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4836               if (moveList[moveNum - 1][0] == NULLCHAR) {
4837                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4838                 DisplayError(str, 0);
4839               } else {
4840                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4841                 SendMoveToProgram(moveNum - 1, &first);
4842               }
4843             }
4844         }
4845 #endif
4846     }
4847
4848     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4849         /* If move comes from a remote source, animate it.  If it
4850            isn't remote, it will have already been animated. */
4851         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4852             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4853         }
4854         if (!pausing && appData.highlightLastMove) {
4855             SetHighlights(fromX, fromY, toX, toY);
4856         }
4857     }
4858
4859     /* Start the clocks */
4860     whiteFlag = blackFlag = FALSE;
4861     appData.clockMode = !(basetime == 0 && increment == 0);
4862     if (ticking == 0) {
4863       ics_clock_paused = TRUE;
4864       StopClocks();
4865     } else if (ticking == 1) {
4866       ics_clock_paused = FALSE;
4867     }
4868     if (gameMode == IcsIdle ||
4869         relation == RELATION_OBSERVING_STATIC ||
4870         relation == RELATION_EXAMINING ||
4871         ics_clock_paused)
4872       DisplayBothClocks();
4873     else
4874       StartClocks();
4875
4876     /* Display opponents and material strengths */
4877     if (gameInfo.variant != VariantBughouse &&
4878         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4879         if (tinyLayout || smallLayout) {
4880             if(gameInfo.variant == VariantNormal)
4881               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4882                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4883                     basetime, increment);
4884             else
4885               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4886                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4887                     basetime, increment, (int) gameInfo.variant);
4888         } else {
4889             if(gameInfo.variant == VariantNormal)
4890               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4891                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4892                     basetime, increment);
4893             else
4894               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4895                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4896                     basetime, increment, VariantName(gameInfo.variant));
4897         }
4898         DisplayTitle(str);
4899   if (appData.debugMode) {
4900     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4901   }
4902     }
4903
4904
4905     /* Display the board */
4906     if (!pausing && !appData.noGUI) {
4907
4908       if (appData.premove)
4909           if (!gotPremove ||
4910              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4911              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4912               ClearPremoveHighlights();
4913
4914       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4915         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4916       DrawPosition(j, boards[currentMove]);
4917
4918       DisplayMove(moveNum - 1);
4919       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4920             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4921               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4922         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4923       }
4924     }
4925
4926     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4927 #if ZIPPY
4928     if(bookHit) { // [HGM] book: simulate book reply
4929         static char bookMove[MSG_SIZ]; // a bit generous?
4930
4931         programStats.nodes = programStats.depth = programStats.time =
4932         programStats.score = programStats.got_only_move = 0;
4933         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4934
4935         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4936         strcat(bookMove, bookHit);
4937         HandleMachineMove(bookMove, &first);
4938     }
4939 #endif
4940 }
4941
4942 void
4943 GetMoveListEvent ()
4944 {
4945     char buf[MSG_SIZ];
4946     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4947         ics_getting_history = H_REQUESTED;
4948         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4949         SendToICS(buf);
4950     }
4951 }
4952
4953 void
4954 SendToBoth (char *msg)
4955 {   // to make it easy to keep two engines in step in dual analysis
4956     SendToProgram(msg, &first);
4957     if(second.analyzing) SendToProgram(msg, &second);
4958 }
4959
4960 void
4961 AnalysisPeriodicEvent (int force)
4962 {
4963     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4964          && !force) || !appData.periodicUpdates)
4965       return;
4966
4967     /* Send . command to Crafty to collect stats */
4968     SendToBoth(".\n");
4969
4970     /* Don't send another until we get a response (this makes
4971        us stop sending to old Crafty's which don't understand
4972        the "." command (sending illegal cmds resets node count & time,
4973        which looks bad)) */
4974     programStats.ok_to_send = 0;
4975 }
4976
4977 void
4978 ics_update_width (int new_width)
4979 {
4980         ics_printf("set width %d\n", new_width);
4981 }
4982
4983 void
4984 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4985 {
4986     char buf[MSG_SIZ];
4987
4988     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4989         // null move in variant where engine does not understand it (for analysis purposes)
4990         SendBoard(cps, moveNum + 1); // send position after move in stead.
4991         return;
4992     }
4993     if (cps->useUsermove) {
4994       SendToProgram("usermove ", cps);
4995     }
4996     if (cps->useSAN) {
4997       char *space;
4998       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4999         int len = space - parseList[moveNum];
5000         memcpy(buf, parseList[moveNum], len);
5001         buf[len++] = '\n';
5002         buf[len] = NULLCHAR;
5003       } else {
5004         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5005       }
5006       SendToProgram(buf, cps);
5007     } else {
5008       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5009         AlphaRank(moveList[moveNum], 4);
5010         SendToProgram(moveList[moveNum], cps);
5011         AlphaRank(moveList[moveNum], 4); // and back
5012       } else
5013       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5014        * the engine. It would be nice to have a better way to identify castle
5015        * moves here. */
5016       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5017                                                                          && cps->useOOCastle) {
5018         int fromX = moveList[moveNum][0] - AAA;
5019         int fromY = moveList[moveNum][1] - ONE;
5020         int toX = moveList[moveNum][2] - AAA;
5021         int toY = moveList[moveNum][3] - ONE;
5022         if((boards[moveNum][fromY][fromX] == WhiteKing
5023             && boards[moveNum][toY][toX] == WhiteRook)
5024            || (boards[moveNum][fromY][fromX] == BlackKing
5025                && boards[moveNum][toY][toX] == BlackRook)) {
5026           if(toX > fromX) SendToProgram("O-O\n", cps);
5027           else SendToProgram("O-O-O\n", cps);
5028         }
5029         else SendToProgram(moveList[moveNum], cps);
5030       } else
5031       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5032         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5033           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5034           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5035                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5036         } else
5037           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5038                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5039         SendToProgram(buf, cps);
5040       }
5041       else SendToProgram(moveList[moveNum], cps);
5042       /* End of additions by Tord */
5043     }
5044
5045     /* [HGM] setting up the opening has brought engine in force mode! */
5046     /*       Send 'go' if we are in a mode where machine should play. */
5047     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5048         (gameMode == TwoMachinesPlay   ||
5049 #if ZIPPY
5050          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5051 #endif
5052          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5053         SendToProgram("go\n", cps);
5054   if (appData.debugMode) {
5055     fprintf(debugFP, "(extra)\n");
5056   }
5057     }
5058     setboardSpoiledMachineBlack = 0;
5059 }
5060
5061 void
5062 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5063 {
5064     char user_move[MSG_SIZ];
5065     char suffix[4];
5066
5067     if(gameInfo.variant == VariantSChess && promoChar) {
5068         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5069         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5070     } else suffix[0] = NULLCHAR;
5071
5072     switch (moveType) {
5073       default:
5074         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5075                 (int)moveType, fromX, fromY, toX, toY);
5076         DisplayError(user_move + strlen("say "), 0);
5077         break;
5078       case WhiteKingSideCastle:
5079       case BlackKingSideCastle:
5080       case WhiteQueenSideCastleWild:
5081       case BlackQueenSideCastleWild:
5082       /* PUSH Fabien */
5083       case WhiteHSideCastleFR:
5084       case BlackHSideCastleFR:
5085       /* POP Fabien */
5086         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5087         break;
5088       case WhiteQueenSideCastle:
5089       case BlackQueenSideCastle:
5090       case WhiteKingSideCastleWild:
5091       case BlackKingSideCastleWild:
5092       /* PUSH Fabien */
5093       case WhiteASideCastleFR:
5094       case BlackASideCastleFR:
5095       /* POP Fabien */
5096         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5097         break;
5098       case WhiteNonPromotion:
5099       case BlackNonPromotion:
5100         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5101         break;
5102       case WhitePromotion:
5103       case BlackPromotion:
5104         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5105            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5106           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5107                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5108                 PieceToChar(WhiteFerz));
5109         else if(gameInfo.variant == VariantGreat)
5110           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5111                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5112                 PieceToChar(WhiteMan));
5113         else
5114           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5115                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5116                 promoChar);
5117         break;
5118       case WhiteDrop:
5119       case BlackDrop:
5120       drop:
5121         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5122                  ToUpper(PieceToChar((ChessSquare) fromX)),
5123                  AAA + toX, ONE + toY);
5124         break;
5125       case IllegalMove:  /* could be a variant we don't quite understand */
5126         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5127       case NormalMove:
5128       case WhiteCapturesEnPassant:
5129       case BlackCapturesEnPassant:
5130         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5131                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5132         break;
5133     }
5134     SendToICS(user_move);
5135     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5136         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5137 }
5138
5139 void
5140 UploadGameEvent ()
5141 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5142     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5143     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5144     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5145       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5146       return;
5147     }
5148     if(gameMode != IcsExamining) { // is this ever not the case?
5149         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5150
5151         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5152           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5153         } else { // on FICS we must first go to general examine mode
5154           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5155         }
5156         if(gameInfo.variant != VariantNormal) {
5157             // try figure out wild number, as xboard names are not always valid on ICS
5158             for(i=1; i<=36; i++) {
5159               snprintf(buf, MSG_SIZ, "wild/%d", i);
5160                 if(StringToVariant(buf) == gameInfo.variant) break;
5161             }
5162             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5163             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5164             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5165         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5166         SendToICS(ics_prefix);
5167         SendToICS(buf);
5168         if(startedFromSetupPosition || backwardMostMove != 0) {
5169           fen = PositionToFEN(backwardMostMove, NULL, 1);
5170           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5171             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5172             SendToICS(buf);
5173           } else { // FICS: everything has to set by separate bsetup commands
5174             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5175             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5176             SendToICS(buf);
5177             if(!WhiteOnMove(backwardMostMove)) {
5178                 SendToICS("bsetup tomove black\n");
5179             }
5180             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5181             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5182             SendToICS(buf);
5183             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5184             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5185             SendToICS(buf);
5186             i = boards[backwardMostMove][EP_STATUS];
5187             if(i >= 0) { // set e.p.
5188               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5189                 SendToICS(buf);
5190             }
5191             bsetup++;
5192           }
5193         }
5194       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5195             SendToICS("bsetup done\n"); // switch to normal examining.
5196     }
5197     for(i = backwardMostMove; i<last; i++) {
5198         char buf[20];
5199         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5200         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5201             int len = strlen(moveList[i]);
5202             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5203             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5204         }
5205         SendToICS(buf);
5206     }
5207     SendToICS(ics_prefix);
5208     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5209 }
5210
5211 void
5212 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5213 {
5214     if (rf == DROP_RANK) {
5215       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5216       sprintf(move, "%c@%c%c\n",
5217                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5218     } else {
5219         if (promoChar == 'x' || promoChar == NULLCHAR) {
5220           sprintf(move, "%c%c%c%c\n",
5221                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5222         } else {
5223             sprintf(move, "%c%c%c%c%c\n",
5224                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5225         }
5226     }
5227 }
5228
5229 void
5230 ProcessICSInitScript (FILE *f)
5231 {
5232     char buf[MSG_SIZ];
5233
5234     while (fgets(buf, MSG_SIZ, f)) {
5235         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5236     }
5237
5238     fclose(f);
5239 }
5240
5241
5242 static int lastX, lastY, selectFlag, dragging;
5243
5244 void
5245 Sweep (int step)
5246 {
5247     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5248     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5249     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5250     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5251     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5252     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5253     do {
5254         promoSweep -= step;
5255         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5256         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5257         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5258         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5259         if(!step) step = -1;
5260     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5261             appData.testLegality && (promoSweep == king ||
5262             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5263     if(toX >= 0) {
5264         int victim = boards[currentMove][toY][toX];
5265         boards[currentMove][toY][toX] = promoSweep;
5266         DrawPosition(FALSE, boards[currentMove]);
5267         boards[currentMove][toY][toX] = victim;
5268     } else
5269     ChangeDragPiece(promoSweep);
5270 }
5271
5272 int
5273 PromoScroll (int x, int y)
5274 {
5275   int step = 0;
5276
5277   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5278   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5279   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5280   if(!step) return FALSE;
5281   lastX = x; lastY = y;
5282   if((promoSweep < BlackPawn) == flipView) step = -step;
5283   if(step > 0) selectFlag = 1;
5284   if(!selectFlag) Sweep(step);
5285   return FALSE;
5286 }
5287
5288 void
5289 NextPiece (int step)
5290 {
5291     ChessSquare piece = boards[currentMove][toY][toX];
5292     do {
5293         pieceSweep -= step;
5294         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5295         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5296         if(!step) step = -1;
5297     } while(PieceToChar(pieceSweep) == '.');
5298     boards[currentMove][toY][toX] = pieceSweep;
5299     DrawPosition(FALSE, boards[currentMove]);
5300     boards[currentMove][toY][toX] = piece;
5301 }
5302 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5303 void
5304 AlphaRank (char *move, int n)
5305 {
5306 //    char *p = move, c; int x, y;
5307
5308     if (appData.debugMode) {
5309         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5310     }
5311
5312     if(move[1]=='*' &&
5313        move[2]>='0' && move[2]<='9' &&
5314        move[3]>='a' && move[3]<='x'    ) {
5315         move[1] = '@';
5316         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5317         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5318     } else
5319     if(move[0]>='0' && move[0]<='9' &&
5320        move[1]>='a' && move[1]<='x' &&
5321        move[2]>='0' && move[2]<='9' &&
5322        move[3]>='a' && move[3]<='x'    ) {
5323         /* input move, Shogi -> normal */
5324         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5325         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5326         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5327         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5328     } else
5329     if(move[1]=='@' &&
5330        move[3]>='0' && move[3]<='9' &&
5331        move[2]>='a' && move[2]<='x'    ) {
5332         move[1] = '*';
5333         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5334         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5335     } else
5336     if(
5337        move[0]>='a' && move[0]<='x' &&
5338        move[3]>='0' && move[3]<='9' &&
5339        move[2]>='a' && move[2]<='x'    ) {
5340          /* output move, normal -> Shogi */
5341         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5342         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5343         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5344         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5345         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5346     }
5347     if (appData.debugMode) {
5348         fprintf(debugFP, "   out = '%s'\n", move);
5349     }
5350 }
5351
5352 char yy_textstr[8000];
5353
5354 /* Parser for moves from gnuchess, ICS, or user typein box */
5355 Boolean
5356 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5357 {
5358     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5359
5360     switch (*moveType) {
5361       case WhitePromotion:
5362       case BlackPromotion:
5363       case WhiteNonPromotion:
5364       case BlackNonPromotion:
5365       case NormalMove:
5366       case WhiteCapturesEnPassant:
5367       case BlackCapturesEnPassant:
5368       case WhiteKingSideCastle:
5369       case WhiteQueenSideCastle:
5370       case BlackKingSideCastle:
5371       case BlackQueenSideCastle:
5372       case WhiteKingSideCastleWild:
5373       case WhiteQueenSideCastleWild:
5374       case BlackKingSideCastleWild:
5375       case BlackQueenSideCastleWild:
5376       /* Code added by Tord: */
5377       case WhiteHSideCastleFR:
5378       case WhiteASideCastleFR:
5379       case BlackHSideCastleFR:
5380       case BlackASideCastleFR:
5381       /* End of code added by Tord */
5382       case IllegalMove:         /* bug or odd chess variant */
5383         *fromX = currentMoveString[0] - AAA;
5384         *fromY = currentMoveString[1] - ONE;
5385         *toX = currentMoveString[2] - AAA;
5386         *toY = currentMoveString[3] - ONE;
5387         *promoChar = currentMoveString[4];
5388         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5389             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5390     if (appData.debugMode) {
5391         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5392     }
5393             *fromX = *fromY = *toX = *toY = 0;
5394             return FALSE;
5395         }
5396         if (appData.testLegality) {
5397           return (*moveType != IllegalMove);
5398         } else {
5399           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5400                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5401         }
5402
5403       case WhiteDrop:
5404       case BlackDrop:
5405         *fromX = *moveType == WhiteDrop ?
5406           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5407           (int) CharToPiece(ToLower(currentMoveString[0]));
5408         *fromY = DROP_RANK;
5409         *toX = currentMoveString[2] - AAA;
5410         *toY = currentMoveString[3] - ONE;
5411         *promoChar = NULLCHAR;
5412         return TRUE;
5413
5414       case AmbiguousMove:
5415       case ImpossibleMove:
5416       case EndOfFile:
5417       case ElapsedTime:
5418       case Comment:
5419       case PGNTag:
5420       case NAG:
5421       case WhiteWins:
5422       case BlackWins:
5423       case GameIsDrawn:
5424       default:
5425     if (appData.debugMode) {
5426         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5427     }
5428         /* bug? */
5429         *fromX = *fromY = *toX = *toY = 0;
5430         *promoChar = NULLCHAR;
5431         return FALSE;
5432     }
5433 }
5434
5435 Boolean pushed = FALSE;
5436 char *lastParseAttempt;
5437
5438 void
5439 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5440 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5441   int fromX, fromY, toX, toY; char promoChar;
5442   ChessMove moveType;
5443   Boolean valid;
5444   int nr = 0;
5445
5446   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5447   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5448     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5449     pushed = TRUE;
5450   }
5451   endPV = forwardMostMove;
5452   do {
5453     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5454     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5455     lastParseAttempt = pv;
5456     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5457     if(!valid && nr == 0 &&
5458        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5459         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5460         // Hande case where played move is different from leading PV move
5461         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5462         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5463         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5464         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5465           endPV += 2; // if position different, keep this
5466           moveList[endPV-1][0] = fromX + AAA;
5467           moveList[endPV-1][1] = fromY + ONE;
5468           moveList[endPV-1][2] = toX + AAA;
5469           moveList[endPV-1][3] = toY + ONE;
5470           parseList[endPV-1][0] = NULLCHAR;
5471           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5472         }
5473       }
5474     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5475     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5476     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5477     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5478         valid++; // allow comments in PV
5479         continue;
5480     }
5481     nr++;
5482     if(endPV+1 > framePtr) break; // no space, truncate
5483     if(!valid) break;
5484     endPV++;
5485     CopyBoard(boards[endPV], boards[endPV-1]);
5486     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5487     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5488     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5489     CoordsToAlgebraic(boards[endPV - 1],
5490                              PosFlags(endPV - 1),
5491                              fromY, fromX, toY, toX, promoChar,
5492                              parseList[endPV - 1]);
5493   } while(valid);
5494   if(atEnd == 2) return; // used hidden, for PV conversion
5495   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5496   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5497   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5498                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5499   DrawPosition(TRUE, boards[currentMove]);
5500 }
5501
5502 int
5503 MultiPV (ChessProgramState *cps)
5504 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5505         int i;
5506         for(i=0; i<cps->nrOptions; i++)
5507             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5508                 return i;
5509         return -1;
5510 }
5511
5512 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5513
5514 Boolean
5515 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5516 {
5517         int startPV, multi, lineStart, origIndex = index;
5518         char *p, buf2[MSG_SIZ];
5519         ChessProgramState *cps = (pane ? &second : &first);
5520
5521         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5522         lastX = x; lastY = y;
5523         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5524         lineStart = startPV = index;
5525         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5526         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5527         index = startPV;
5528         do{ while(buf[index] && buf[index] != '\n') index++;
5529         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5530         buf[index] = 0;
5531         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5532                 int n = cps->option[multi].value;
5533                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5534                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5535                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5536                 cps->option[multi].value = n;
5537                 *start = *end = 0;
5538                 return FALSE;
5539         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5540                 ExcludeClick(origIndex - lineStart);
5541                 return FALSE;
5542         }
5543         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5544         *start = startPV; *end = index-1;
5545         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5546         return TRUE;
5547 }
5548
5549 char *
5550 PvToSAN (char *pv)
5551 {
5552         static char buf[10*MSG_SIZ];
5553         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5554         *buf = NULLCHAR;
5555         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5556         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5557         for(i = forwardMostMove; i<endPV; i++){
5558             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5559             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5560             k += strlen(buf+k);
5561         }
5562         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5563         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5564         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5565         endPV = savedEnd;
5566         return buf;
5567 }
5568
5569 Boolean
5570 LoadPV (int x, int y)
5571 { // called on right mouse click to load PV
5572   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5573   lastX = x; lastY = y;
5574   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5575   extendGame = FALSE;
5576   return TRUE;
5577 }
5578
5579 void
5580 UnLoadPV ()
5581 {
5582   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5583   if(endPV < 0) return;
5584   if(appData.autoCopyPV) CopyFENToClipboard();
5585   endPV = -1;
5586   if(extendGame && currentMove > forwardMostMove) {
5587         Boolean saveAnimate = appData.animate;
5588         if(pushed) {
5589             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5590                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5591             } else storedGames--; // abandon shelved tail of original game
5592         }
5593         pushed = FALSE;
5594         forwardMostMove = currentMove;
5595         currentMove = oldFMM;
5596         appData.animate = FALSE;
5597         ToNrEvent(forwardMostMove);
5598         appData.animate = saveAnimate;
5599   }
5600   currentMove = forwardMostMove;
5601   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5602   ClearPremoveHighlights();
5603   DrawPosition(TRUE, boards[currentMove]);
5604 }
5605
5606 void
5607 MovePV (int x, int y, int h)
5608 { // step through PV based on mouse coordinates (called on mouse move)
5609   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5610
5611   // we must somehow check if right button is still down (might be released off board!)
5612   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5613   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5614   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5615   if(!step) return;
5616   lastX = x; lastY = y;
5617
5618   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5619   if(endPV < 0) return;
5620   if(y < margin) step = 1; else
5621   if(y > h - margin) step = -1;
5622   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5623   currentMove += step;
5624   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5625   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5626                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5627   DrawPosition(FALSE, boards[currentMove]);
5628 }
5629
5630
5631 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5632 // All positions will have equal probability, but the current method will not provide a unique
5633 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5634 #define DARK 1
5635 #define LITE 2
5636 #define ANY 3
5637
5638 int squaresLeft[4];
5639 int piecesLeft[(int)BlackPawn];
5640 int seed, nrOfShuffles;
5641
5642 void
5643 GetPositionNumber ()
5644 {       // sets global variable seed
5645         int i;
5646
5647         seed = appData.defaultFrcPosition;
5648         if(seed < 0) { // randomize based on time for negative FRC position numbers
5649                 for(i=0; i<50; i++) seed += random();
5650                 seed = random() ^ random() >> 8 ^ random() << 8;
5651                 if(seed<0) seed = -seed;
5652         }
5653 }
5654
5655 int
5656 put (Board board, int pieceType, int rank, int n, int shade)
5657 // put the piece on the (n-1)-th empty squares of the given shade
5658 {
5659         int i;
5660
5661         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5662                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5663                         board[rank][i] = (ChessSquare) pieceType;
5664                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5665                         squaresLeft[ANY]--;
5666                         piecesLeft[pieceType]--;
5667                         return i;
5668                 }
5669         }
5670         return -1;
5671 }
5672
5673
5674 void
5675 AddOnePiece (Board board, int pieceType, int rank, int shade)
5676 // calculate where the next piece goes, (any empty square), and put it there
5677 {
5678         int i;
5679
5680         i = seed % squaresLeft[shade];
5681         nrOfShuffles *= squaresLeft[shade];
5682         seed /= squaresLeft[shade];
5683         put(board, pieceType, rank, i, shade);
5684 }
5685
5686 void
5687 AddTwoPieces (Board board, int pieceType, int rank)
5688 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5689 {
5690         int i, n=squaresLeft[ANY], j=n-1, k;
5691
5692         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5693         i = seed % k;  // pick one
5694         nrOfShuffles *= k;
5695         seed /= k;
5696         while(i >= j) i -= j--;
5697         j = n - 1 - j; i += j;
5698         put(board, pieceType, rank, j, ANY);
5699         put(board, pieceType, rank, i, ANY);
5700 }
5701
5702 void
5703 SetUpShuffle (Board board, int number)
5704 {
5705         int i, p, first=1;
5706
5707         GetPositionNumber(); nrOfShuffles = 1;
5708
5709         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5710         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5711         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5712
5713         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5714
5715         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5716             p = (int) board[0][i];
5717             if(p < (int) BlackPawn) piecesLeft[p] ++;
5718             board[0][i] = EmptySquare;
5719         }
5720
5721         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5722             // shuffles restricted to allow normal castling put KRR first
5723             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5724                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5725             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5726                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5727             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5728                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5729             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5730                 put(board, WhiteRook, 0, 0, ANY);
5731             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5732         }
5733
5734         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5735             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5736             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5737                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5738                 while(piecesLeft[p] >= 2) {
5739                     AddOnePiece(board, p, 0, LITE);
5740                     AddOnePiece(board, p, 0, DARK);
5741                 }
5742                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5743             }
5744
5745         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5746             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5747             // but we leave King and Rooks for last, to possibly obey FRC restriction
5748             if(p == (int)WhiteRook) continue;
5749             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5750             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5751         }
5752
5753         // now everything is placed, except perhaps King (Unicorn) and Rooks
5754
5755         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5756             // Last King gets castling rights
5757             while(piecesLeft[(int)WhiteUnicorn]) {
5758                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5759                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5760             }
5761
5762             while(piecesLeft[(int)WhiteKing]) {
5763                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5764                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5765             }
5766
5767
5768         } else {
5769             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5770             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5771         }
5772
5773         // Only Rooks can be left; simply place them all
5774         while(piecesLeft[(int)WhiteRook]) {
5775                 i = put(board, WhiteRook, 0, 0, ANY);
5776                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5777                         if(first) {
5778                                 first=0;
5779                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5780                         }
5781                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5782                 }
5783         }
5784         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5785             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5786         }
5787
5788         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5789 }
5790
5791 int
5792 SetCharTable (char *table, const char * map)
5793 /* [HGM] moved here from winboard.c because of its general usefulness */
5794 /*       Basically a safe strcpy that uses the last character as King */
5795 {
5796     int result = FALSE; int NrPieces;
5797
5798     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5799                     && NrPieces >= 12 && !(NrPieces&1)) {
5800         int i; /* [HGM] Accept even length from 12 to 34 */
5801
5802         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5803         for( i=0; i<NrPieces/2-1; i++ ) {
5804             table[i] = map[i];
5805             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5806         }
5807         table[(int) WhiteKing]  = map[NrPieces/2-1];
5808         table[(int) BlackKing]  = map[NrPieces-1];
5809
5810         result = TRUE;
5811     }
5812
5813     return result;
5814 }
5815
5816 void
5817 Prelude (Board board)
5818 {       // [HGM] superchess: random selection of exo-pieces
5819         int i, j, k; ChessSquare p;
5820         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5821
5822         GetPositionNumber(); // use FRC position number
5823
5824         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5825             SetCharTable(pieceToChar, appData.pieceToCharTable);
5826             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5827                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5828         }
5829
5830         j = seed%4;                 seed /= 4;
5831         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5832         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5833         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5834         j = seed%3 + (seed%3 >= j); seed /= 3;
5835         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5836         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5837         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5838         j = seed%3;                 seed /= 3;
5839         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5840         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5841         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5842         j = seed%2 + (seed%2 >= j); seed /= 2;
5843         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5844         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5845         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5846         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5847         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5848         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5849         put(board, exoPieces[0],    0, 0, ANY);
5850         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5851 }
5852
5853 void
5854 InitPosition (int redraw)
5855 {
5856     ChessSquare (* pieces)[BOARD_FILES];
5857     int i, j, pawnRow, overrule,
5858     oldx = gameInfo.boardWidth,
5859     oldy = gameInfo.boardHeight,
5860     oldh = gameInfo.holdingsWidth;
5861     static int oldv;
5862
5863     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5864
5865     /* [AS] Initialize pv info list [HGM] and game status */
5866     {
5867         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5868             pvInfoList[i].depth = 0;
5869             boards[i][EP_STATUS] = EP_NONE;
5870             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5871         }
5872
5873         initialRulePlies = 0; /* 50-move counter start */
5874
5875         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5876         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5877     }
5878
5879
5880     /* [HGM] logic here is completely changed. In stead of full positions */
5881     /* the initialized data only consist of the two backranks. The switch */
5882     /* selects which one we will use, which is than copied to the Board   */
5883     /* initialPosition, which for the rest is initialized by Pawns and    */
5884     /* empty squares. This initial position is then copied to boards[0],  */
5885     /* possibly after shuffling, so that it remains available.            */
5886
5887     gameInfo.holdingsWidth = 0; /* default board sizes */
5888     gameInfo.boardWidth    = 8;
5889     gameInfo.boardHeight   = 8;
5890     gameInfo.holdingsSize  = 0;
5891     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5892     for(i=0; i<BOARD_FILES-2; i++)
5893       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5894     initialPosition[EP_STATUS] = EP_NONE;
5895     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5896     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5897          SetCharTable(pieceNickName, appData.pieceNickNames);
5898     else SetCharTable(pieceNickName, "............");
5899     pieces = FIDEArray;
5900
5901     switch (gameInfo.variant) {
5902     case VariantFischeRandom:
5903       shuffleOpenings = TRUE;
5904     default:
5905       break;
5906     case VariantShatranj:
5907       pieces = ShatranjArray;
5908       nrCastlingRights = 0;
5909       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5910       break;
5911     case VariantMakruk:
5912       pieces = makrukArray;
5913       nrCastlingRights = 0;
5914       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5915       break;
5916     case VariantASEAN:
5917       pieces = aseanArray;
5918       nrCastlingRights = 0;
5919       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5920       break;
5921     case VariantTwoKings:
5922       pieces = twoKingsArray;
5923       break;
5924     case VariantGrand:
5925       pieces = GrandArray;
5926       nrCastlingRights = 0;
5927       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5928       gameInfo.boardWidth = 10;
5929       gameInfo.boardHeight = 10;
5930       gameInfo.holdingsSize = 7;
5931       break;
5932     case VariantCapaRandom:
5933       shuffleOpenings = TRUE;
5934     case VariantCapablanca:
5935       pieces = CapablancaArray;
5936       gameInfo.boardWidth = 10;
5937       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5938       break;
5939     case VariantGothic:
5940       pieces = GothicArray;
5941       gameInfo.boardWidth = 10;
5942       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5943       break;
5944     case VariantSChess:
5945       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5946       gameInfo.holdingsSize = 7;
5947       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5948       break;
5949     case VariantJanus:
5950       pieces = JanusArray;
5951       gameInfo.boardWidth = 10;
5952       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5953       nrCastlingRights = 6;
5954         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5955         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5956         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5957         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5958         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5959         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5960       break;
5961     case VariantFalcon:
5962       pieces = FalconArray;
5963       gameInfo.boardWidth = 10;
5964       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5965       break;
5966     case VariantXiangqi:
5967       pieces = XiangqiArray;
5968       gameInfo.boardWidth  = 9;
5969       gameInfo.boardHeight = 10;
5970       nrCastlingRights = 0;
5971       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5972       break;
5973     case VariantShogi:
5974       pieces = ShogiArray;
5975       gameInfo.boardWidth  = 9;
5976       gameInfo.boardHeight = 9;
5977       gameInfo.holdingsSize = 7;
5978       nrCastlingRights = 0;
5979       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5980       break;
5981     case VariantCourier:
5982       pieces = CourierArray;
5983       gameInfo.boardWidth  = 12;
5984       nrCastlingRights = 0;
5985       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5986       break;
5987     case VariantKnightmate:
5988       pieces = KnightmateArray;
5989       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5990       break;
5991     case VariantSpartan:
5992       pieces = SpartanArray;
5993       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5994       break;
5995     case VariantFairy:
5996       pieces = fairyArray;
5997       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5998       break;
5999     case VariantGreat:
6000       pieces = GreatArray;
6001       gameInfo.boardWidth = 10;
6002       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6003       gameInfo.holdingsSize = 8;
6004       break;
6005     case VariantSuper:
6006       pieces = FIDEArray;
6007       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6008       gameInfo.holdingsSize = 8;
6009       startedFromSetupPosition = TRUE;
6010       break;
6011     case VariantCrazyhouse:
6012     case VariantBughouse:
6013       pieces = FIDEArray;
6014       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6015       gameInfo.holdingsSize = 5;
6016       break;
6017     case VariantWildCastle:
6018       pieces = FIDEArray;
6019       /* !!?shuffle with kings guaranteed to be on d or e file */
6020       shuffleOpenings = 1;
6021       break;
6022     case VariantNoCastle:
6023       pieces = FIDEArray;
6024       nrCastlingRights = 0;
6025       /* !!?unconstrained back-rank shuffle */
6026       shuffleOpenings = 1;
6027       break;
6028     }
6029
6030     overrule = 0;
6031     if(appData.NrFiles >= 0) {
6032         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6033         gameInfo.boardWidth = appData.NrFiles;
6034     }
6035     if(appData.NrRanks >= 0) {
6036         gameInfo.boardHeight = appData.NrRanks;
6037     }
6038     if(appData.holdingsSize >= 0) {
6039         i = appData.holdingsSize;
6040         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6041         gameInfo.holdingsSize = i;
6042     }
6043     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6044     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6045         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6046
6047     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6048     if(pawnRow < 1) pawnRow = 1;
6049     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6050
6051     /* User pieceToChar list overrules defaults */
6052     if(appData.pieceToCharTable != NULL)
6053         SetCharTable(pieceToChar, appData.pieceToCharTable);
6054
6055     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6056
6057         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6058             s = (ChessSquare) 0; /* account holding counts in guard band */
6059         for( i=0; i<BOARD_HEIGHT; i++ )
6060             initialPosition[i][j] = s;
6061
6062         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6063         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6064         initialPosition[pawnRow][j] = WhitePawn;
6065         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6066         if(gameInfo.variant == VariantXiangqi) {
6067             if(j&1) {
6068                 initialPosition[pawnRow][j] =
6069                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6070                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6071                    initialPosition[2][j] = WhiteCannon;
6072                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6073                 }
6074             }
6075         }
6076         if(gameInfo.variant == VariantGrand) {
6077             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6078                initialPosition[0][j] = WhiteRook;
6079                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6080             }
6081         }
6082         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6083     }
6084     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6085
6086             j=BOARD_LEFT+1;
6087             initialPosition[1][j] = WhiteBishop;
6088             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6089             j=BOARD_RGHT-2;
6090             initialPosition[1][j] = WhiteRook;
6091             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6092     }
6093
6094     if( nrCastlingRights == -1) {
6095         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6096         /*       This sets default castling rights from none to normal corners   */
6097         /* Variants with other castling rights must set them themselves above    */
6098         nrCastlingRights = 6;
6099
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;
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;
6106      }
6107
6108      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6109      if(gameInfo.variant == VariantGreat) { // promotion commoners
6110         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6111         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6112         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6113         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6114      }
6115      if( gameInfo.variant == VariantSChess ) {
6116       initialPosition[1][0] = BlackMarshall;
6117       initialPosition[2][0] = BlackAngel;
6118       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6119       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6120       initialPosition[1][1] = initialPosition[2][1] =
6121       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6122      }
6123   if (appData.debugMode) {
6124     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6125   }
6126     if(shuffleOpenings) {
6127         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6128         startedFromSetupPosition = TRUE;
6129     }
6130     if(startedFromPositionFile) {
6131       /* [HGM] loadPos: use PositionFile for every new game */
6132       CopyBoard(initialPosition, filePosition);
6133       for(i=0; i<nrCastlingRights; i++)
6134           initialRights[i] = filePosition[CASTLING][i];
6135       startedFromSetupPosition = TRUE;
6136     }
6137
6138     CopyBoard(boards[0], initialPosition);
6139
6140     if(oldx != gameInfo.boardWidth ||
6141        oldy != gameInfo.boardHeight ||
6142        oldv != gameInfo.variant ||
6143        oldh != gameInfo.holdingsWidth
6144                                          )
6145             InitDrawingSizes(-2 ,0);
6146
6147     oldv = gameInfo.variant;
6148     if (redraw)
6149       DrawPosition(TRUE, boards[currentMove]);
6150 }
6151
6152 void
6153 SendBoard (ChessProgramState *cps, int moveNum)
6154 {
6155     char message[MSG_SIZ];
6156
6157     if (cps->useSetboard) {
6158       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6159       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6160       SendToProgram(message, cps);
6161       free(fen);
6162
6163     } else {
6164       ChessSquare *bp;
6165       int i, j, left=0, right=BOARD_WIDTH;
6166       /* Kludge to set black to move, avoiding the troublesome and now
6167        * deprecated "black" command.
6168        */
6169       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6170         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6171
6172       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6173
6174       SendToProgram("edit\n", cps);
6175       SendToProgram("#\n", cps);
6176       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6177         bp = &boards[moveNum][i][left];
6178         for (j = left; j < right; j++, bp++) {
6179           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6180           if ((int) *bp < (int) BlackPawn) {
6181             if(j == BOARD_RGHT+1)
6182                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6183             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6184             if(message[0] == '+' || message[0] == '~') {
6185               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6186                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6187                         AAA + j, ONE + i);
6188             }
6189             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6190                 message[1] = BOARD_RGHT   - 1 - j + '1';
6191                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6192             }
6193             SendToProgram(message, cps);
6194           }
6195         }
6196       }
6197
6198       SendToProgram("c\n", cps);
6199       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6200         bp = &boards[moveNum][i][left];
6201         for (j = left; j < right; j++, bp++) {
6202           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6203           if (((int) *bp != (int) EmptySquare)
6204               && ((int) *bp >= (int) BlackPawn)) {
6205             if(j == BOARD_LEFT-2)
6206                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6207             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6208                     AAA + j, ONE + i);
6209             if(message[0] == '+' || message[0] == '~') {
6210               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6211                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6212                         AAA + j, ONE + i);
6213             }
6214             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6215                 message[1] = BOARD_RGHT   - 1 - j + '1';
6216                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6217             }
6218             SendToProgram(message, cps);
6219           }
6220         }
6221       }
6222
6223       SendToProgram(".\n", cps);
6224     }
6225     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6226 }
6227
6228 char exclusionHeader[MSG_SIZ];
6229 int exCnt, excludePtr;
6230 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6231 static Exclusion excluTab[200];
6232 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6233
6234 static void
6235 WriteMap (int s)
6236 {
6237     int j;
6238     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6239     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6240 }
6241
6242 static void
6243 ClearMap ()
6244 {
6245     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6246     excludePtr = 24; exCnt = 0;
6247     WriteMap(0);
6248 }
6249
6250 static void
6251 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6252 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6253     char buf[2*MOVE_LEN], *p;
6254     Exclusion *e = excluTab;
6255     int i;
6256     for(i=0; i<exCnt; i++)
6257         if(e[i].ff == fromX && e[i].fr == fromY &&
6258            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6259     if(i == exCnt) { // was not in exclude list; add it
6260         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6261         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6262             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6263             return; // abort
6264         }
6265         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6266         excludePtr++; e[i].mark = excludePtr++;
6267         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6268         exCnt++;
6269     }
6270     exclusionHeader[e[i].mark] = state;
6271 }
6272
6273 static int
6274 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6275 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6276     char buf[MSG_SIZ];
6277     int j, k;
6278     ChessMove moveType;
6279     if((signed char)promoChar == -1) { // kludge to indicate best move
6280         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6281             return 1; // if unparsable, abort
6282     }
6283     // update exclusion map (resolving toggle by consulting existing state)
6284     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6285     j = k%8; k >>= 3;
6286     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6287     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6288          excludeMap[k] |=   1<<j;
6289     else excludeMap[k] &= ~(1<<j);
6290     // update header
6291     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6292     // inform engine
6293     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6294     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6295     SendToBoth(buf);
6296     return (state == '+');
6297 }
6298
6299 static void
6300 ExcludeClick (int index)
6301 {
6302     int i, j;
6303     Exclusion *e = excluTab;
6304     if(index < 25) { // none, best or tail clicked
6305         if(index < 13) { // none: include all
6306             WriteMap(0); // clear map
6307             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6308             SendToBoth("include all\n"); // and inform engine
6309         } else if(index > 18) { // tail
6310             if(exclusionHeader[19] == '-') { // tail was excluded
6311                 SendToBoth("include all\n");
6312                 WriteMap(0); // clear map completely
6313                 // now re-exclude selected moves
6314                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6315                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6316             } else { // tail was included or in mixed state
6317                 SendToBoth("exclude all\n");
6318                 WriteMap(0xFF); // fill map completely
6319                 // now re-include selected moves
6320                 j = 0; // count them
6321                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6322                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6323                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6324             }
6325         } else { // best
6326             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6327         }
6328     } else {
6329         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6330             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6331             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6332             break;
6333         }
6334     }
6335 }
6336
6337 ChessSquare
6338 DefaultPromoChoice (int white)
6339 {
6340     ChessSquare result;
6341     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6342        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6343         result = WhiteFerz; // no choice
6344     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6345         result= WhiteKing; // in Suicide Q is the last thing we want
6346     else if(gameInfo.variant == VariantSpartan)
6347         result = white ? WhiteQueen : WhiteAngel;
6348     else result = WhiteQueen;
6349     if(!white) result = WHITE_TO_BLACK result;
6350     return result;
6351 }
6352
6353 static int autoQueen; // [HGM] oneclick
6354
6355 int
6356 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6357 {
6358     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6359     /* [HGM] add Shogi promotions */
6360     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6361     ChessSquare piece;
6362     ChessMove moveType;
6363     Boolean premove;
6364
6365     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6366     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6367
6368     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6369       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6370         return FALSE;
6371
6372     piece = boards[currentMove][fromY][fromX];
6373     if(gameInfo.variant == VariantShogi) {
6374         promotionZoneSize = BOARD_HEIGHT/3;
6375         highestPromotingPiece = (int)WhiteFerz;
6376     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6377         promotionZoneSize = 3;
6378     }
6379
6380     // Treat Lance as Pawn when it is not representing Amazon
6381     if(gameInfo.variant != VariantSuper) {
6382         if(piece == WhiteLance) piece = WhitePawn; else
6383         if(piece == BlackLance) piece = BlackPawn;
6384     }
6385
6386     // next weed out all moves that do not touch the promotion zone at all
6387     if((int)piece >= BlackPawn) {
6388         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6389              return FALSE;
6390         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6391     } else {
6392         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6393            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6394     }
6395
6396     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6397
6398     // weed out mandatory Shogi promotions
6399     if(gameInfo.variant == VariantShogi) {
6400         if(piece >= BlackPawn) {
6401             if(toY == 0 && piece == BlackPawn ||
6402                toY == 0 && piece == BlackQueen ||
6403                toY <= 1 && piece == BlackKnight) {
6404                 *promoChoice = '+';
6405                 return FALSE;
6406             }
6407         } else {
6408             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6409                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6410                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6411                 *promoChoice = '+';
6412                 return FALSE;
6413             }
6414         }
6415     }
6416
6417     // weed out obviously illegal Pawn moves
6418     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6419         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6420         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6421         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6422         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6423         // note we are not allowed to test for valid (non-)capture, due to premove
6424     }
6425
6426     // we either have a choice what to promote to, or (in Shogi) whether to promote
6427     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6428        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6429         *promoChoice = PieceToChar(BlackFerz);  // no choice
6430         return FALSE;
6431     }
6432     // no sense asking what we must promote to if it is going to explode...
6433     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6434         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6435         return FALSE;
6436     }
6437     // give caller the default choice even if we will not make it
6438     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6439     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6440     if(        sweepSelect && gameInfo.variant != VariantGreat
6441                            && gameInfo.variant != VariantGrand
6442                            && gameInfo.variant != VariantSuper) return FALSE;
6443     if(autoQueen) return FALSE; // predetermined
6444
6445     // suppress promotion popup on illegal moves that are not premoves
6446     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6447               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6448     if(appData.testLegality && !premove) {
6449         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6450                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6451         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6452             return FALSE;
6453     }
6454
6455     return TRUE;
6456 }
6457
6458 int
6459 InPalace (int row, int column)
6460 {   /* [HGM] for Xiangqi */
6461     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6462          column < (BOARD_WIDTH + 4)/2 &&
6463          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6464     return FALSE;
6465 }
6466
6467 int
6468 PieceForSquare (int x, int y)
6469 {
6470   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6471      return -1;
6472   else
6473      return boards[currentMove][y][x];
6474 }
6475
6476 int
6477 OKToStartUserMove (int x, int y)
6478 {
6479     ChessSquare from_piece;
6480     int white_piece;
6481
6482     if (matchMode) return FALSE;
6483     if (gameMode == EditPosition) return TRUE;
6484
6485     if (x >= 0 && y >= 0)
6486       from_piece = boards[currentMove][y][x];
6487     else
6488       from_piece = EmptySquare;
6489
6490     if (from_piece == EmptySquare) return FALSE;
6491
6492     white_piece = (int)from_piece >= (int)WhitePawn &&
6493       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6494
6495     switch (gameMode) {
6496       case AnalyzeFile:
6497       case TwoMachinesPlay:
6498       case EndOfGame:
6499         return FALSE;
6500
6501       case IcsObserving:
6502       case IcsIdle:
6503         return FALSE;
6504
6505       case MachinePlaysWhite:
6506       case IcsPlayingBlack:
6507         if (appData.zippyPlay) return FALSE;
6508         if (white_piece) {
6509             DisplayMoveError(_("You are playing Black"));
6510             return FALSE;
6511         }
6512         break;
6513
6514       case MachinePlaysBlack:
6515       case IcsPlayingWhite:
6516         if (appData.zippyPlay) return FALSE;
6517         if (!white_piece) {
6518             DisplayMoveError(_("You are playing White"));
6519             return FALSE;
6520         }
6521         break;
6522
6523       case PlayFromGameFile:
6524             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6525       case EditGame:
6526         if (!white_piece && WhiteOnMove(currentMove)) {
6527             DisplayMoveError(_("It is White's turn"));
6528             return FALSE;
6529         }
6530         if (white_piece && !WhiteOnMove(currentMove)) {
6531             DisplayMoveError(_("It is Black's turn"));
6532             return FALSE;
6533         }
6534         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6535             /* Editing correspondence game history */
6536             /* Could disallow this or prompt for confirmation */
6537             cmailOldMove = -1;
6538         }
6539         break;
6540
6541       case BeginningOfGame:
6542         if (appData.icsActive) return FALSE;
6543         if (!appData.noChessProgram) {
6544             if (!white_piece) {
6545                 DisplayMoveError(_("You are playing White"));
6546                 return FALSE;
6547             }
6548         }
6549         break;
6550
6551       case Training:
6552         if (!white_piece && WhiteOnMove(currentMove)) {
6553             DisplayMoveError(_("It is White's turn"));
6554             return FALSE;
6555         }
6556         if (white_piece && !WhiteOnMove(currentMove)) {
6557             DisplayMoveError(_("It is Black's turn"));
6558             return FALSE;
6559         }
6560         break;
6561
6562       default:
6563       case IcsExamining:
6564         break;
6565     }
6566     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6567         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6568         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6569         && gameMode != AnalyzeFile && gameMode != Training) {
6570         DisplayMoveError(_("Displayed position is not current"));
6571         return FALSE;
6572     }
6573     return TRUE;
6574 }
6575
6576 Boolean
6577 OnlyMove (int *x, int *y, Boolean captures)
6578 {
6579     DisambiguateClosure cl;
6580     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6581     switch(gameMode) {
6582       case MachinePlaysBlack:
6583       case IcsPlayingWhite:
6584       case BeginningOfGame:
6585         if(!WhiteOnMove(currentMove)) return FALSE;
6586         break;
6587       case MachinePlaysWhite:
6588       case IcsPlayingBlack:
6589         if(WhiteOnMove(currentMove)) return FALSE;
6590         break;
6591       case EditGame:
6592         break;
6593       default:
6594         return FALSE;
6595     }
6596     cl.pieceIn = EmptySquare;
6597     cl.rfIn = *y;
6598     cl.ffIn = *x;
6599     cl.rtIn = -1;
6600     cl.ftIn = -1;
6601     cl.promoCharIn = NULLCHAR;
6602     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6603     if( cl.kind == NormalMove ||
6604         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6605         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6606         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6607       fromX = cl.ff;
6608       fromY = cl.rf;
6609       *x = cl.ft;
6610       *y = cl.rt;
6611       return TRUE;
6612     }
6613     if(cl.kind != ImpossibleMove) return FALSE;
6614     cl.pieceIn = EmptySquare;
6615     cl.rfIn = -1;
6616     cl.ffIn = -1;
6617     cl.rtIn = *y;
6618     cl.ftIn = *x;
6619     cl.promoCharIn = NULLCHAR;
6620     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6621     if( cl.kind == NormalMove ||
6622         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6623         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6624         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6625       fromX = cl.ff;
6626       fromY = cl.rf;
6627       *x = cl.ft;
6628       *y = cl.rt;
6629       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6630       return TRUE;
6631     }
6632     return FALSE;
6633 }
6634
6635 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6636 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6637 int lastLoadGameUseList = FALSE;
6638 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6639 ChessMove lastLoadGameStart = EndOfFile;
6640 int doubleClick;
6641
6642 void
6643 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6644 {
6645     ChessMove moveType;
6646     ChessSquare pup;
6647     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6648
6649     /* Check if the user is playing in turn.  This is complicated because we
6650        let the user "pick up" a piece before it is his turn.  So the piece he
6651        tried to pick up may have been captured by the time he puts it down!
6652        Therefore we use the color the user is supposed to be playing in this
6653        test, not the color of the piece that is currently on the starting
6654        square---except in EditGame mode, where the user is playing both
6655        sides; fortunately there the capture race can't happen.  (It can
6656        now happen in IcsExamining mode, but that's just too bad.  The user
6657        will get a somewhat confusing message in that case.)
6658        */
6659
6660     switch (gameMode) {
6661       case AnalyzeFile:
6662       case TwoMachinesPlay:
6663       case EndOfGame:
6664       case IcsObserving:
6665       case IcsIdle:
6666         /* We switched into a game mode where moves are not accepted,
6667            perhaps while the mouse button was down. */
6668         return;
6669
6670       case MachinePlaysWhite:
6671         /* User is moving for Black */
6672         if (WhiteOnMove(currentMove)) {
6673             DisplayMoveError(_("It is White's turn"));
6674             return;
6675         }
6676         break;
6677
6678       case MachinePlaysBlack:
6679         /* User is moving for White */
6680         if (!WhiteOnMove(currentMove)) {
6681             DisplayMoveError(_("It is Black's turn"));
6682             return;
6683         }
6684         break;
6685
6686       case PlayFromGameFile:
6687             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6688       case EditGame:
6689       case IcsExamining:
6690       case BeginningOfGame:
6691       case AnalyzeMode:
6692       case Training:
6693         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6694         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6695             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6696             /* User is moving for Black */
6697             if (WhiteOnMove(currentMove)) {
6698                 DisplayMoveError(_("It is White's turn"));
6699                 return;
6700             }
6701         } else {
6702             /* User is moving for White */
6703             if (!WhiteOnMove(currentMove)) {
6704                 DisplayMoveError(_("It is Black's turn"));
6705                 return;
6706             }
6707         }
6708         break;
6709
6710       case IcsPlayingBlack:
6711         /* User is moving for Black */
6712         if (WhiteOnMove(currentMove)) {
6713             if (!appData.premove) {
6714                 DisplayMoveError(_("It is White's turn"));
6715             } else if (toX >= 0 && toY >= 0) {
6716                 premoveToX = toX;
6717                 premoveToY = toY;
6718                 premoveFromX = fromX;
6719                 premoveFromY = fromY;
6720                 premovePromoChar = promoChar;
6721                 gotPremove = 1;
6722                 if (appData.debugMode)
6723                     fprintf(debugFP, "Got premove: fromX %d,"
6724                             "fromY %d, toX %d, toY %d\n",
6725                             fromX, fromY, toX, toY);
6726             }
6727             return;
6728         }
6729         break;
6730
6731       case IcsPlayingWhite:
6732         /* User is moving for White */
6733         if (!WhiteOnMove(currentMove)) {
6734             if (!appData.premove) {
6735                 DisplayMoveError(_("It is Black's turn"));
6736             } else if (toX >= 0 && toY >= 0) {
6737                 premoveToX = toX;
6738                 premoveToY = toY;
6739                 premoveFromX = fromX;
6740                 premoveFromY = fromY;
6741                 premovePromoChar = promoChar;
6742                 gotPremove = 1;
6743                 if (appData.debugMode)
6744                     fprintf(debugFP, "Got premove: fromX %d,"
6745                             "fromY %d, toX %d, toY %d\n",
6746                             fromX, fromY, toX, toY);
6747             }
6748             return;
6749         }
6750         break;
6751
6752       default:
6753         break;
6754
6755       case EditPosition:
6756         /* EditPosition, empty square, or different color piece;
6757            click-click move is possible */
6758         if (toX == -2 || toY == -2) {
6759             boards[0][fromY][fromX] = EmptySquare;
6760             DrawPosition(FALSE, boards[currentMove]);
6761             return;
6762         } else if (toX >= 0 && toY >= 0) {
6763             boards[0][toY][toX] = boards[0][fromY][fromX];
6764             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6765                 if(boards[0][fromY][0] != EmptySquare) {
6766                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6767                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6768                 }
6769             } else
6770             if(fromX == BOARD_RGHT+1) {
6771                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6772                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6773                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6774                 }
6775             } else
6776             boards[0][fromY][fromX] = gatingPiece;
6777             DrawPosition(FALSE, boards[currentMove]);
6778             return;
6779         }
6780         return;
6781     }
6782
6783     if(toX < 0 || toY < 0) return;
6784     pup = boards[currentMove][toY][toX];
6785
6786     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6787     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6788          if( pup != EmptySquare ) return;
6789          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6790            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6791                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6792            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6793            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6794            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6795            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6796          fromY = DROP_RANK;
6797     }
6798
6799     /* [HGM] always test for legality, to get promotion info */
6800     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6801                                          fromY, fromX, toY, toX, promoChar);
6802
6803     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6804
6805     /* [HGM] but possibly ignore an IllegalMove result */
6806     if (appData.testLegality) {
6807         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6808             DisplayMoveError(_("Illegal move"));
6809             return;
6810         }
6811     }
6812
6813     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6814         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6815              ClearPremoveHighlights(); // was included
6816         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6817         return;
6818     }
6819
6820     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6821 }
6822
6823 /* Common tail of UserMoveEvent and DropMenuEvent */
6824 int
6825 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6826 {
6827     char *bookHit = 0;
6828
6829     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6830         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6831         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6832         if(WhiteOnMove(currentMove)) {
6833             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6834         } else {
6835             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6836         }
6837     }
6838
6839     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6840        move type in caller when we know the move is a legal promotion */
6841     if(moveType == NormalMove && promoChar)
6842         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6843
6844     /* [HGM] <popupFix> The following if has been moved here from
6845        UserMoveEvent(). Because it seemed to belong here (why not allow
6846        piece drops in training games?), and because it can only be
6847        performed after it is known to what we promote. */
6848     if (gameMode == Training) {
6849       /* compare the move played on the board to the next move in the
6850        * game. If they match, display the move and the opponent's response.
6851        * If they don't match, display an error message.
6852        */
6853       int saveAnimate;
6854       Board testBoard;
6855       CopyBoard(testBoard, boards[currentMove]);
6856       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6857
6858       if (CompareBoards(testBoard, boards[currentMove+1])) {
6859         ForwardInner(currentMove+1);
6860
6861         /* Autoplay the opponent's response.
6862          * if appData.animate was TRUE when Training mode was entered,
6863          * the response will be animated.
6864          */
6865         saveAnimate = appData.animate;
6866         appData.animate = animateTraining;
6867         ForwardInner(currentMove+1);
6868         appData.animate = saveAnimate;
6869
6870         /* check for the end of the game */
6871         if (currentMove >= forwardMostMove) {
6872           gameMode = PlayFromGameFile;
6873           ModeHighlight();
6874           SetTrainingModeOff();
6875           DisplayInformation(_("End of game"));
6876         }
6877       } else {
6878         DisplayError(_("Incorrect move"), 0);
6879       }
6880       return 1;
6881     }
6882
6883   /* Ok, now we know that the move is good, so we can kill
6884      the previous line in Analysis Mode */
6885   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6886                                 && currentMove < forwardMostMove) {
6887     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6888     else forwardMostMove = currentMove;
6889   }
6890
6891   ClearMap();
6892
6893   /* If we need the chess program but it's dead, restart it */
6894   ResurrectChessProgram();
6895
6896   /* A user move restarts a paused game*/
6897   if (pausing)
6898     PauseEvent();
6899
6900   thinkOutput[0] = NULLCHAR;
6901
6902   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6903
6904   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6905     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6906     return 1;
6907   }
6908
6909   if (gameMode == BeginningOfGame) {
6910     if (appData.noChessProgram) {
6911       gameMode = EditGame;
6912       SetGameInfo();
6913     } else {
6914       char buf[MSG_SIZ];
6915       gameMode = MachinePlaysBlack;
6916       StartClocks();
6917       SetGameInfo();
6918       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6919       DisplayTitle(buf);
6920       if (first.sendName) {
6921         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6922         SendToProgram(buf, &first);
6923       }
6924       StartClocks();
6925     }
6926     ModeHighlight();
6927   }
6928
6929   /* Relay move to ICS or chess engine */
6930   if (appData.icsActive) {
6931     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6932         gameMode == IcsExamining) {
6933       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6934         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6935         SendToICS("draw ");
6936         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6937       }
6938       // also send plain move, in case ICS does not understand atomic claims
6939       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6940       ics_user_moved = 1;
6941     }
6942   } else {
6943     if (first.sendTime && (gameMode == BeginningOfGame ||
6944                            gameMode == MachinePlaysWhite ||
6945                            gameMode == MachinePlaysBlack)) {
6946       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6947     }
6948     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6949          // [HGM] book: if program might be playing, let it use book
6950         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6951         first.maybeThinking = TRUE;
6952     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6953         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6954         SendBoard(&first, currentMove+1);
6955         if(second.analyzing) {
6956             if(!second.useSetboard) SendToProgram("undo\n", &second);
6957             SendBoard(&second, currentMove+1);
6958         }
6959     } else {
6960         SendMoveToProgram(forwardMostMove-1, &first);
6961         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6962     }
6963     if (currentMove == cmailOldMove + 1) {
6964       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6965     }
6966   }
6967
6968   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6969
6970   switch (gameMode) {
6971   case EditGame:
6972     if(appData.testLegality)
6973     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6974     case MT_NONE:
6975     case MT_CHECK:
6976       break;
6977     case MT_CHECKMATE:
6978     case MT_STAINMATE:
6979       if (WhiteOnMove(currentMove)) {
6980         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6981       } else {
6982         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6983       }
6984       break;
6985     case MT_STALEMATE:
6986       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6987       break;
6988     }
6989     break;
6990
6991   case MachinePlaysBlack:
6992   case MachinePlaysWhite:
6993     /* disable certain menu options while machine is thinking */
6994     SetMachineThinkingEnables();
6995     break;
6996
6997   default:
6998     break;
6999   }
7000
7001   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7002   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7003
7004   if(bookHit) { // [HGM] book: simulate book reply
7005         static char bookMove[MSG_SIZ]; // a bit generous?
7006
7007         programStats.nodes = programStats.depth = programStats.time =
7008         programStats.score = programStats.got_only_move = 0;
7009         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7010
7011         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7012         strcat(bookMove, bookHit);
7013         HandleMachineMove(bookMove, &first);
7014   }
7015   return 1;
7016 }
7017
7018 void
7019 MarkByFEN(char *fen)
7020 {
7021         int r, f;
7022         if(!appData.markers || !appData.highlightDragging) return;
7023         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) marker[r][f] = legal[r][f] = 0;
7024         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7025         while(*fen) {
7026             int s = 0;
7027             marker[r][f] = 0;
7028             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7029             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7030             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7031             if(*fen == 'T') marker[r][f++] = 0; else
7032             if(*fen == 'Y') marker[r][f++] = 1; else
7033             if(*fen == 'R') marker[r][f++] = 2; else {
7034                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7035               f += s; fen -= s>0;
7036             }
7037             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7038             if(r < 0) break;
7039             fen++;
7040         }
7041         DrawPosition(TRUE, NULL);
7042 }
7043
7044 void
7045 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7046 {
7047     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7048     Markers *m = (Markers *) closure;
7049     if(rf == fromY && ff == fromX)
7050         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7051                          || kind == WhiteCapturesEnPassant
7052                          || kind == BlackCapturesEnPassant);
7053     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7054 }
7055
7056 void
7057 MarkTargetSquares (int clear)
7058 {
7059   int x, y, sum=0;
7060   if(clear) { // no reason to ever suppress clearing
7061     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7062     if(!sum) return; // nothing was cleared,no redraw needed
7063   } else {
7064     int capt = 0;
7065     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7066        !appData.testLegality || gameMode == EditPosition) return;
7067     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7068     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7069       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7070       if(capt)
7071       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7072     }
7073   }
7074   DrawPosition(FALSE, NULL);
7075 }
7076
7077 int
7078 Explode (Board board, int fromX, int fromY, int toX, int toY)
7079 {
7080     if(gameInfo.variant == VariantAtomic &&
7081        (board[toY][toX] != EmptySquare ||                     // capture?
7082         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7083                          board[fromY][fromX] == BlackPawn   )
7084       )) {
7085         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7086         return TRUE;
7087     }
7088     return FALSE;
7089 }
7090
7091 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7092
7093 int
7094 CanPromote (ChessSquare piece, int y)
7095 {
7096         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7097         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7098         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7099            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7100            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7101          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7102         return (piece == BlackPawn && y == 1 ||
7103                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7104                 piece == BlackLance && y == 1 ||
7105                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7106 }
7107
7108 void ReportClick(char *action, int x, int y)
7109 {
7110         char buf[MSG_SIZ]; // Inform engine of what user does
7111         int r, f;
7112         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square
7113           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1;
7114         if(!first.highlight || gameMode == EditPosition) return;
7115         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7116         SendToProgram(buf, &first);
7117 }
7118
7119 void
7120 LeftClick (ClickType clickType, int xPix, int yPix)
7121 {
7122     int x, y;
7123     Boolean saveAnimate;
7124     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7125     char promoChoice = NULLCHAR;
7126     ChessSquare piece;
7127     static TimeMark lastClickTime, prevClickTime;
7128
7129     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7130
7131     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7132
7133     if (clickType == Press) ErrorPopDown();
7134
7135     x = EventToSquare(xPix, BOARD_WIDTH);
7136     y = EventToSquare(yPix, BOARD_HEIGHT);
7137     if (!flipView && y >= 0) {
7138         y = BOARD_HEIGHT - 1 - y;
7139     }
7140     if (flipView && x >= 0) {
7141         x = BOARD_WIDTH - 1 - x;
7142     }
7143
7144     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7145         defaultPromoChoice = promoSweep;
7146         promoSweep = EmptySquare;   // terminate sweep
7147         promoDefaultAltered = TRUE;
7148         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7149     }
7150
7151     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7152         if(clickType == Release) return; // ignore upclick of click-click destination
7153         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7154         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7155         if(gameInfo.holdingsWidth &&
7156                 (WhiteOnMove(currentMove)
7157                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7158                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7159             // click in right holdings, for determining promotion piece
7160             ChessSquare p = boards[currentMove][y][x];
7161             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7162             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7163             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7164                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7165                 fromX = fromY = -1;
7166                 return;
7167             }
7168         }
7169         DrawPosition(FALSE, boards[currentMove]);
7170         return;
7171     }
7172
7173     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7174     if(clickType == Press
7175             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7176               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7177               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7178         return;
7179
7180     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7181         // could be static click on premove from-square: abort premove
7182         gotPremove = 0;
7183         ClearPremoveHighlights();
7184     }
7185
7186     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7187         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7188
7189     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7190         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7191                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7192         defaultPromoChoice = DefaultPromoChoice(side);
7193     }
7194
7195     autoQueen = appData.alwaysPromoteToQueen;
7196
7197     if (fromX == -1) {
7198       int originalY = y;
7199       gatingPiece = EmptySquare;
7200       if (clickType != Press) {
7201         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7202             DragPieceEnd(xPix, yPix); dragging = 0;
7203             DrawPosition(FALSE, NULL);
7204         }
7205         return;
7206       }
7207       doubleClick = FALSE;
7208       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7209         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7210       }
7211       fromX = x; fromY = y; toX = toY = -1;
7212       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7213          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7214          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7215             /* First square */
7216             if (OKToStartUserMove(fromX, fromY)) {
7217                 second = 0;
7218                 ReportClick("lift", x, y);
7219                 MarkTargetSquares(0);
7220                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7221                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7222                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7223                     promoSweep = defaultPromoChoice;
7224                     selectFlag = 0; lastX = xPix; lastY = yPix;
7225                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7226                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7227                 }
7228                 if (appData.highlightDragging) {
7229                     SetHighlights(fromX, fromY, -1, -1);
7230                 } else {
7231                     ClearHighlights();
7232                 }
7233             } else fromX = fromY = -1;
7234             return;
7235         }
7236     }
7237
7238     /* fromX != -1 */
7239     if (clickType == Press && gameMode != EditPosition) {
7240         ChessSquare fromP;
7241         ChessSquare toP;
7242         int frc;
7243
7244         // ignore off-board to clicks
7245         if(y < 0 || x < 0) return;
7246
7247         /* Check if clicking again on the same color piece */
7248         fromP = boards[currentMove][fromY][fromX];
7249         toP = boards[currentMove][y][x];
7250         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7251         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7252              WhitePawn <= toP && toP <= WhiteKing &&
7253              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7254              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7255             (BlackPawn <= fromP && fromP <= BlackKing &&
7256              BlackPawn <= toP && toP <= BlackKing &&
7257              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7258              !(fromP == BlackKing && toP == BlackRook && frc))) {
7259             /* Clicked again on same color piece -- changed his mind */
7260             second = (x == fromX && y == fromY);
7261             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7262                 second = FALSE; // first double-click rather than scond click
7263                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7264             }
7265             promoDefaultAltered = FALSE;
7266             MarkTargetSquares(1);
7267            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7268             if (appData.highlightDragging) {
7269                 SetHighlights(x, y, -1, -1);
7270             } else {
7271                 ClearHighlights();
7272             }
7273             if (OKToStartUserMove(x, y)) {
7274                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7275                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7276                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7277                  gatingPiece = boards[currentMove][fromY][fromX];
7278                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7279                 fromX = x;
7280                 fromY = y; dragging = 1;
7281                 ReportClick("lift", x, y);
7282                 MarkTargetSquares(0);
7283                 DragPieceBegin(xPix, yPix, FALSE);
7284                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7285                     promoSweep = defaultPromoChoice;
7286                     selectFlag = 0; lastX = xPix; lastY = yPix;
7287                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7288                 }
7289             }
7290            }
7291            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7292            second = FALSE;
7293         }
7294         // ignore clicks on holdings
7295         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7296     }
7297
7298     if (clickType == Release && x == fromX && y == fromY) {
7299         DragPieceEnd(xPix, yPix); dragging = 0;
7300         if(clearFlag) {
7301             // a deferred attempt to click-click move an empty square on top of a piece
7302             boards[currentMove][y][x] = EmptySquare;
7303             ClearHighlights();
7304             DrawPosition(FALSE, boards[currentMove]);
7305             fromX = fromY = -1; clearFlag = 0;
7306             return;
7307         }
7308         if (appData.animateDragging) {
7309             /* Undo animation damage if any */
7310             DrawPosition(FALSE, NULL);
7311         }
7312         if (second || sweepSelecting) {
7313             /* Second up/down in same square; just abort move */
7314             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7315             second = sweepSelecting = 0;
7316             fromX = fromY = -1;
7317             gatingPiece = EmptySquare;
7318             MarkTargetSquares(1);
7319             ClearHighlights();
7320             gotPremove = 0;
7321             ClearPremoveHighlights();
7322         } else {
7323             /* First upclick in same square; start click-click mode */
7324             SetHighlights(x, y, -1, -1);
7325         }
7326         return;
7327     }
7328
7329     clearFlag = 0;
7330
7331     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7332         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7333         DisplayMessage(_("only marked squares are legal"),"");
7334         DrawPosition(TRUE, NULL);
7335         return; // ignore to-click
7336     }
7337
7338     /* we now have a different from- and (possibly off-board) to-square */
7339     /* Completed move */
7340     if(!sweepSelecting) {
7341         toX = x;
7342         toY = y;
7343     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7344
7345     saveAnimate = appData.animate;
7346     if (clickType == Press) {
7347         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7348             // must be Edit Position mode with empty-square selected
7349             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7350             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7351             return;
7352         }
7353         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7354           if(appData.sweepSelect) {
7355             ChessSquare piece = boards[currentMove][fromY][fromX];
7356             promoSweep = defaultPromoChoice;
7357             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7358             selectFlag = 0; lastX = xPix; lastY = yPix;
7359             Sweep(0); // Pawn that is going to promote: preview promotion piece
7360             sweepSelecting = 1;
7361             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7362             MarkTargetSquares(1);
7363           }
7364           return; // promo popup appears on up-click
7365         }
7366         /* Finish clickclick move */
7367         if (appData.animate || appData.highlightLastMove) {
7368             SetHighlights(fromX, fromY, toX, toY);
7369         } else {
7370             ClearHighlights();
7371         }
7372     } else {
7373 #if 0
7374 // [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
7375         /* Finish drag move */
7376         if (appData.highlightLastMove) {
7377             SetHighlights(fromX, fromY, toX, toY);
7378         } else {
7379             ClearHighlights();
7380         }
7381 #endif
7382         DragPieceEnd(xPix, yPix); dragging = 0;
7383         /* Don't animate move and drag both */
7384         appData.animate = FALSE;
7385     }
7386
7387     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7388     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7389         ChessSquare piece = boards[currentMove][fromY][fromX];
7390         if(gameMode == EditPosition && piece != EmptySquare &&
7391            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7392             int n;
7393
7394             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7395                 n = PieceToNumber(piece - (int)BlackPawn);
7396                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7397                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7398                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7399             } else
7400             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7401                 n = PieceToNumber(piece);
7402                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7403                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7404                 boards[currentMove][n][BOARD_WIDTH-2]++;
7405             }
7406             boards[currentMove][fromY][fromX] = EmptySquare;
7407         }
7408         ClearHighlights();
7409         fromX = fromY = -1;
7410         MarkTargetSquares(1);
7411         DrawPosition(TRUE, boards[currentMove]);
7412         return;
7413     }
7414
7415     // off-board moves should not be highlighted
7416     if(x < 0 || y < 0) ClearHighlights();
7417     else ReportClick("put", x, y);
7418
7419     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7420
7421     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7422         SetHighlights(fromX, fromY, toX, toY);
7423         MarkTargetSquares(1);
7424         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7425             // [HGM] super: promotion to captured piece selected from holdings
7426             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7427             promotionChoice = TRUE;
7428             // kludge follows to temporarily execute move on display, without promoting yet
7429             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7430             boards[currentMove][toY][toX] = p;
7431             DrawPosition(FALSE, boards[currentMove]);
7432             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7433             boards[currentMove][toY][toX] = q;
7434             DisplayMessage("Click in holdings to choose piece", "");
7435             return;
7436         }
7437         PromotionPopUp();
7438     } else {
7439         int oldMove = currentMove;
7440         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7441         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7442         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7443         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7444            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7445             DrawPosition(TRUE, boards[currentMove]);
7446         MarkTargetSquares(1);
7447         fromX = fromY = -1;
7448     }
7449     appData.animate = saveAnimate;
7450     if (appData.animate || appData.animateDragging) {
7451         /* Undo animation damage if needed */
7452         DrawPosition(FALSE, NULL);
7453     }
7454 }
7455
7456 int
7457 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7458 {   // front-end-free part taken out of PieceMenuPopup
7459     int whichMenu; int xSqr, ySqr;
7460
7461     if(seekGraphUp) { // [HGM] seekgraph
7462         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7463         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7464         return -2;
7465     }
7466
7467     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7468          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7469         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7470         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7471         if(action == Press)   {
7472             originalFlip = flipView;
7473             flipView = !flipView; // temporarily flip board to see game from partners perspective
7474             DrawPosition(TRUE, partnerBoard);
7475             DisplayMessage(partnerStatus, "");
7476             partnerUp = TRUE;
7477         } else if(action == Release) {
7478             flipView = originalFlip;
7479             DrawPosition(TRUE, boards[currentMove]);
7480             partnerUp = FALSE;
7481         }
7482         return -2;
7483     }
7484
7485     xSqr = EventToSquare(x, BOARD_WIDTH);
7486     ySqr = EventToSquare(y, BOARD_HEIGHT);
7487     if (action == Release) {
7488         if(pieceSweep != EmptySquare) {
7489             EditPositionMenuEvent(pieceSweep, toX, toY);
7490             pieceSweep = EmptySquare;
7491         } else UnLoadPV(); // [HGM] pv
7492     }
7493     if (action != Press) return -2; // return code to be ignored
7494     switch (gameMode) {
7495       case IcsExamining:
7496         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7497       case EditPosition:
7498         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7499         if (xSqr < 0 || ySqr < 0) return -1;
7500         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7501         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7502         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7503         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7504         NextPiece(0);
7505         return 2; // grab
7506       case IcsObserving:
7507         if(!appData.icsEngineAnalyze) return -1;
7508       case IcsPlayingWhite:
7509       case IcsPlayingBlack:
7510         if(!appData.zippyPlay) goto noZip;
7511       case AnalyzeMode:
7512       case AnalyzeFile:
7513       case MachinePlaysWhite:
7514       case MachinePlaysBlack:
7515       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7516         if (!appData.dropMenu) {
7517           LoadPV(x, y);
7518           return 2; // flag front-end to grab mouse events
7519         }
7520         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7521            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7522       case EditGame:
7523       noZip:
7524         if (xSqr < 0 || ySqr < 0) return -1;
7525         if (!appData.dropMenu || appData.testLegality &&
7526             gameInfo.variant != VariantBughouse &&
7527             gameInfo.variant != VariantCrazyhouse) return -1;
7528         whichMenu = 1; // drop menu
7529         break;
7530       default:
7531         return -1;
7532     }
7533
7534     if (((*fromX = xSqr) < 0) ||
7535         ((*fromY = ySqr) < 0)) {
7536         *fromX = *fromY = -1;
7537         return -1;
7538     }
7539     if (flipView)
7540       *fromX = BOARD_WIDTH - 1 - *fromX;
7541     else
7542       *fromY = BOARD_HEIGHT - 1 - *fromY;
7543
7544     return whichMenu;
7545 }
7546
7547 void
7548 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7549 {
7550 //    char * hint = lastHint;
7551     FrontEndProgramStats stats;
7552
7553     stats.which = cps == &first ? 0 : 1;
7554     stats.depth = cpstats->depth;
7555     stats.nodes = cpstats->nodes;
7556     stats.score = cpstats->score;
7557     stats.time = cpstats->time;
7558     stats.pv = cpstats->movelist;
7559     stats.hint = lastHint;
7560     stats.an_move_index = 0;
7561     stats.an_move_count = 0;
7562
7563     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7564         stats.hint = cpstats->move_name;
7565         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7566         stats.an_move_count = cpstats->nr_moves;
7567     }
7568
7569     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
7570
7571     SetProgramStats( &stats );
7572 }
7573
7574 void
7575 ClearEngineOutputPane (int which)
7576 {
7577     static FrontEndProgramStats dummyStats;
7578     dummyStats.which = which;
7579     dummyStats.pv = "#";
7580     SetProgramStats( &dummyStats );
7581 }
7582
7583 #define MAXPLAYERS 500
7584
7585 char *
7586 TourneyStandings (int display)
7587 {
7588     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7589     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7590     char result, *p, *names[MAXPLAYERS];
7591
7592     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7593         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7594     names[0] = p = strdup(appData.participants);
7595     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7596
7597     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7598
7599     while(result = appData.results[nr]) {
7600         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7601         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7602         wScore = bScore = 0;
7603         switch(result) {
7604           case '+': wScore = 2; break;
7605           case '-': bScore = 2; break;
7606           case '=': wScore = bScore = 1; break;
7607           case ' ':
7608           case '*': return strdup("busy"); // tourney not finished
7609         }
7610         score[w] += wScore;
7611         score[b] += bScore;
7612         games[w]++;
7613         games[b]++;
7614         nr++;
7615     }
7616     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7617     for(w=0; w<nPlayers; w++) {
7618         bScore = -1;
7619         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7620         ranking[w] = b; points[w] = bScore; score[b] = -2;
7621     }
7622     p = malloc(nPlayers*34+1);
7623     for(w=0; w<nPlayers && w<display; w++)
7624         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7625     free(names[0]);
7626     return p;
7627 }
7628
7629 void
7630 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7631 {       // count all piece types
7632         int p, f, r;
7633         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7634         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7635         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7636                 p = board[r][f];
7637                 pCnt[p]++;
7638                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7639                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7640                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7641                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7642                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7643                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7644         }
7645 }
7646
7647 int
7648 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7649 {
7650         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7651         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7652
7653         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7654         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7655         if(myPawns == 2 && nMine == 3) // KPP
7656             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7657         if(myPawns == 1 && nMine == 2) // KP
7658             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7659         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7660             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7661         if(myPawns) return FALSE;
7662         if(pCnt[WhiteRook+side])
7663             return pCnt[BlackRook-side] ||
7664                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7665                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7666                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7667         if(pCnt[WhiteCannon+side]) {
7668             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7669             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7670         }
7671         if(pCnt[WhiteKnight+side])
7672             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7673         return FALSE;
7674 }
7675
7676 int
7677 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7678 {
7679         VariantClass v = gameInfo.variant;
7680
7681         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7682         if(v == VariantShatranj) return TRUE; // always winnable through baring
7683         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7684         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7685
7686         if(v == VariantXiangqi) {
7687                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7688
7689                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7690                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7691                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7692                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7693                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7694                 if(stale) // we have at least one last-rank P plus perhaps C
7695                     return majors // KPKX
7696                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7697                 else // KCA*E*
7698                     return pCnt[WhiteFerz+side] // KCAK
7699                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7700                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7701                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7702
7703         } else if(v == VariantKnightmate) {
7704                 if(nMine == 1) return FALSE;
7705                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7706         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7707                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7708
7709                 if(nMine == 1) return FALSE; // bare King
7710                 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
7711                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7712                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7713                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7714                 if(pCnt[WhiteKnight+side])
7715                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7716                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7717                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7718                 if(nBishops)
7719                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7720                 if(pCnt[WhiteAlfil+side])
7721                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7722                 if(pCnt[WhiteWazir+side])
7723                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7724         }
7725
7726         return TRUE;
7727 }
7728
7729 int
7730 CompareWithRights (Board b1, Board b2)
7731 {
7732     int rights = 0;
7733     if(!CompareBoards(b1, b2)) return FALSE;
7734     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7735     /* compare castling rights */
7736     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7737            rights++; /* King lost rights, while rook still had them */
7738     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7739         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7740            rights++; /* but at least one rook lost them */
7741     }
7742     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7743            rights++;
7744     if( b1[CASTLING][5] != NoRights ) {
7745         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7746            rights++;
7747     }
7748     return rights == 0;
7749 }
7750
7751 int
7752 Adjudicate (ChessProgramState *cps)
7753 {       // [HGM] some adjudications useful with buggy engines
7754         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7755         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7756         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7757         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7758         int k, drop, count = 0; static int bare = 1;
7759         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7760         Boolean canAdjudicate = !appData.icsActive;
7761
7762         // most tests only when we understand the game, i.e. legality-checking on
7763             if( appData.testLegality )
7764             {   /* [HGM] Some more adjudications for obstinate engines */
7765                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7766                 static int moveCount = 6;
7767                 ChessMove result;
7768                 char *reason = NULL;
7769
7770                 /* Count what is on board. */
7771                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7772
7773                 /* Some material-based adjudications that have to be made before stalemate test */
7774                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7775                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7776                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7777                      if(canAdjudicate && appData.checkMates) {
7778                          if(engineOpponent)
7779                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7780                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7781                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7782                          return 1;
7783                      }
7784                 }
7785
7786                 /* Bare King in Shatranj (loses) or Losers (wins) */
7787                 if( nrW == 1 || nrB == 1) {
7788                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7789                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7790                      if(canAdjudicate && appData.checkMates) {
7791                          if(engineOpponent)
7792                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7793                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7794                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7795                          return 1;
7796                      }
7797                   } else
7798                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7799                   {    /* bare King */
7800                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7801                         if(canAdjudicate && appData.checkMates) {
7802                             /* but only adjudicate if adjudication enabled */
7803                             if(engineOpponent)
7804                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7805                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7806                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7807                             return 1;
7808                         }
7809                   }
7810                 } else bare = 1;
7811
7812
7813             // don't wait for engine to announce game end if we can judge ourselves
7814             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7815               case MT_CHECK:
7816                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7817                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7818                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7819                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7820                             checkCnt++;
7821                         if(checkCnt >= 2) {
7822                             reason = "Xboard adjudication: 3rd check";
7823                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7824                             break;
7825                         }
7826                     }
7827                 }
7828               case MT_NONE:
7829               default:
7830                 break;
7831               case MT_STALEMATE:
7832               case MT_STAINMATE:
7833                 reason = "Xboard adjudication: Stalemate";
7834                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7835                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7836                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7837                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7838                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7839                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7840                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7841                                                                         EP_CHECKMATE : EP_WINS);
7842                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7843                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7844                 }
7845                 break;
7846               case MT_CHECKMATE:
7847                 reason = "Xboard adjudication: Checkmate";
7848                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7849                 if(gameInfo.variant == VariantShogi) {
7850                     if(forwardMostMove > backwardMostMove
7851                        && moveList[forwardMostMove-1][1] == '@'
7852                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7853                         reason = "XBoard adjudication: pawn-drop mate";
7854                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7855                     }
7856                 }
7857                 break;
7858             }
7859
7860                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7861                     case EP_STALEMATE:
7862                         result = GameIsDrawn; break;
7863                     case EP_CHECKMATE:
7864                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7865                     case EP_WINS:
7866                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7867                     default:
7868                         result = EndOfFile;
7869                 }
7870                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7871                     if(engineOpponent)
7872                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7873                     GameEnds( result, reason, GE_XBOARD );
7874                     return 1;
7875                 }
7876
7877                 /* Next absolutely insufficient mating material. */
7878                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7879                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7880                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7881
7882                      /* always flag draws, for judging claims */
7883                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7884
7885                      if(canAdjudicate && appData.materialDraws) {
7886                          /* but only adjudicate them if adjudication enabled */
7887                          if(engineOpponent) {
7888                            SendToProgram("force\n", engineOpponent); // suppress reply
7889                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7890                          }
7891                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7892                          return 1;
7893                      }
7894                 }
7895
7896                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7897                 if(gameInfo.variant == VariantXiangqi ?
7898                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7899                  : nrW + nrB == 4 &&
7900                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7901                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7902                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7903                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7904                    ) ) {
7905                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7906                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7907                           if(engineOpponent) {
7908                             SendToProgram("force\n", engineOpponent); // suppress reply
7909                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7910                           }
7911                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7912                           return 1;
7913                      }
7914                 } else moveCount = 6;
7915             }
7916
7917         // Repetition draws and 50-move rule can be applied independently of legality testing
7918
7919                 /* Check for rep-draws */
7920                 count = 0;
7921                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7922                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7923                 for(k = forwardMostMove-2;
7924                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7925                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7926                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7927                     k-=2)
7928                 {   int rights=0;
7929                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7930                         /* compare castling rights */
7931                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7932                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7933                                 rights++; /* King lost rights, while rook still had them */
7934                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7935                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7936                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7937                                    rights++; /* but at least one rook lost them */
7938                         }
7939                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7940                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7941                                 rights++;
7942                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7943                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7944                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7945                                    rights++;
7946                         }
7947                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7948                             && appData.drawRepeats > 1) {
7949                              /* adjudicate after user-specified nr of repeats */
7950                              int result = GameIsDrawn;
7951                              char *details = "XBoard adjudication: repetition draw";
7952                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7953                                 // [HGM] xiangqi: check for forbidden perpetuals
7954                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7955                                 for(m=forwardMostMove; m>k; m-=2) {
7956                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7957                                         ourPerpetual = 0; // the current mover did not always check
7958                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7959                                         hisPerpetual = 0; // the opponent did not always check
7960                                 }
7961                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7962                                                                         ourPerpetual, hisPerpetual);
7963                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7964                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7965                                     details = "Xboard adjudication: perpetual checking";
7966                                 } else
7967                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7968                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7969                                 } else
7970                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7971                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7972                                         result = BlackWins;
7973                                         details = "Xboard adjudication: repetition";
7974                                     }
7975                                 } else // it must be XQ
7976                                 // Now check for perpetual chases
7977                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7978                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7979                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7980                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7981                                         static char resdet[MSG_SIZ];
7982                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7983                                         details = resdet;
7984                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7985                                     } else
7986                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7987                                         break; // Abort repetition-checking loop.
7988                                 }
7989                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7990                              }
7991                              if(engineOpponent) {
7992                                SendToProgram("force\n", engineOpponent); // suppress reply
7993                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7994                              }
7995                              GameEnds( result, details, GE_XBOARD );
7996                              return 1;
7997                         }
7998                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7999                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8000                     }
8001                 }
8002
8003                 /* Now we test for 50-move draws. Determine ply count */
8004                 count = forwardMostMove;
8005                 /* look for last irreversble move */
8006                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8007                     count--;
8008                 /* if we hit starting position, add initial plies */
8009                 if( count == backwardMostMove )
8010                     count -= initialRulePlies;
8011                 count = forwardMostMove - count;
8012                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8013                         // adjust reversible move counter for checks in Xiangqi
8014                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8015                         if(i < backwardMostMove) i = backwardMostMove;
8016                         while(i <= forwardMostMove) {
8017                                 lastCheck = inCheck; // check evasion does not count
8018                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8019                                 if(inCheck || lastCheck) count--; // check does not count
8020                                 i++;
8021                         }
8022                 }
8023                 if( count >= 100)
8024                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8025                          /* this is used to judge if draw claims are legal */
8026                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8027                          if(engineOpponent) {
8028                            SendToProgram("force\n", engineOpponent); // suppress reply
8029                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8030                          }
8031                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8032                          return 1;
8033                 }
8034
8035                 /* if draw offer is pending, treat it as a draw claim
8036                  * when draw condition present, to allow engines a way to
8037                  * claim draws before making their move to avoid a race
8038                  * condition occurring after their move
8039                  */
8040                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8041                          char *p = NULL;
8042                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8043                              p = "Draw claim: 50-move rule";
8044                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8045                              p = "Draw claim: 3-fold repetition";
8046                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8047                              p = "Draw claim: insufficient mating material";
8048                          if( p != NULL && canAdjudicate) {
8049                              if(engineOpponent) {
8050                                SendToProgram("force\n", engineOpponent); // suppress reply
8051                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8052                              }
8053                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8054                              return 1;
8055                          }
8056                 }
8057
8058                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8059                     if(engineOpponent) {
8060                       SendToProgram("force\n", engineOpponent); // suppress reply
8061                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8062                     }
8063                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8064                     return 1;
8065                 }
8066         return 0;
8067 }
8068
8069 char *
8070 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8071 {   // [HGM] book: this routine intercepts moves to simulate book replies
8072     char *bookHit = NULL;
8073
8074     //first determine if the incoming move brings opponent into his book
8075     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8076         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8077     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8078     if(bookHit != NULL && !cps->bookSuspend) {
8079         // make sure opponent is not going to reply after receiving move to book position
8080         SendToProgram("force\n", cps);
8081         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8082     }
8083     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8084     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8085     // now arrange restart after book miss
8086     if(bookHit) {
8087         // after a book hit we never send 'go', and the code after the call to this routine
8088         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8089         char buf[MSG_SIZ], *move = bookHit;
8090         if(cps->useSAN) {
8091             int fromX, fromY, toX, toY;
8092             char promoChar;
8093             ChessMove moveType;
8094             move = buf + 30;
8095             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8096                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8097                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8098                                     PosFlags(forwardMostMove),
8099                                     fromY, fromX, toY, toX, promoChar, move);
8100             } else {
8101                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8102                 bookHit = NULL;
8103             }
8104         }
8105         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8106         SendToProgram(buf, cps);
8107         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8108     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8109         SendToProgram("go\n", cps);
8110         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8111     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8112         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8113             SendToProgram("go\n", cps);
8114         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8115     }
8116     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8117 }
8118
8119 int
8120 LoadError (char *errmess, ChessProgramState *cps)
8121 {   // unloads engine and switches back to -ncp mode if it was first
8122     if(cps->initDone) return FALSE;
8123     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8124     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8125     cps->pr = NoProc;
8126     if(cps == &first) {
8127         appData.noChessProgram = TRUE;
8128         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8129         gameMode = BeginningOfGame; ModeHighlight();
8130         SetNCPMode();
8131     }
8132     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8133     DisplayMessage("", ""); // erase waiting message
8134     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8135     return TRUE;
8136 }
8137
8138 char *savedMessage;
8139 ChessProgramState *savedState;
8140 void
8141 DeferredBookMove (void)
8142 {
8143         if(savedState->lastPing != savedState->lastPong)
8144                     ScheduleDelayedEvent(DeferredBookMove, 10);
8145         else
8146         HandleMachineMove(savedMessage, savedState);
8147 }
8148
8149 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8150 static ChessProgramState *stalledEngine;
8151 static char stashedInputMove[MSG_SIZ];
8152
8153 void
8154 HandleMachineMove (char *message, ChessProgramState *cps)
8155 {
8156     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8157     char realname[MSG_SIZ];
8158     int fromX, fromY, toX, toY;
8159     ChessMove moveType;
8160     char promoChar;
8161     char *p, *pv=buf1;
8162     int machineWhite, oldError;
8163     char *bookHit;
8164
8165     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8166         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8167         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8168             DisplayError(_("Invalid pairing from pairing engine"), 0);
8169             return;
8170         }
8171         pairingReceived = 1;
8172         NextMatchGame();
8173         return; // Skim the pairing messages here.
8174     }
8175
8176     oldError = cps->userError; cps->userError = 0;
8177
8178 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8179     /*
8180      * Kludge to ignore BEL characters
8181      */
8182     while (*message == '\007') message++;
8183
8184     /*
8185      * [HGM] engine debug message: ignore lines starting with '#' character
8186      */
8187     if(cps->debug && *message == '#') return;
8188
8189     /*
8190      * Look for book output
8191      */
8192     if (cps == &first && bookRequested) {
8193         if (message[0] == '\t' || message[0] == ' ') {
8194             /* Part of the book output is here; append it */
8195             strcat(bookOutput, message);
8196             strcat(bookOutput, "  \n");
8197             return;
8198         } else if (bookOutput[0] != NULLCHAR) {
8199             /* All of book output has arrived; display it */
8200             char *p = bookOutput;
8201             while (*p != NULLCHAR) {
8202                 if (*p == '\t') *p = ' ';
8203                 p++;
8204             }
8205             DisplayInformation(bookOutput);
8206             bookRequested = FALSE;
8207             /* Fall through to parse the current output */
8208         }
8209     }
8210
8211     /*
8212      * Look for machine move.
8213      */
8214     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8215         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8216     {
8217         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8218             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8219             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8220             stalledEngine = cps;
8221             if(appData.ponderNextMove) { // bring opponent out of ponder
8222                 if(gameMode == TwoMachinesPlay) {
8223                     if(cps->other->pause)
8224                         PauseEngine(cps->other);
8225                     else
8226                         SendToProgram("easy\n", cps->other);
8227                 }
8228             }
8229             StopClocks();
8230             return;
8231         }
8232
8233         /* This method is only useful on engines that support ping */
8234         if (cps->lastPing != cps->lastPong) {
8235           if (gameMode == BeginningOfGame) {
8236             /* Extra move from before last new; ignore */
8237             if (appData.debugMode) {
8238                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8239             }
8240           } else {
8241             if (appData.debugMode) {
8242                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8243                         cps->which, gameMode);
8244             }
8245
8246             SendToProgram("undo\n", cps);
8247           }
8248           return;
8249         }
8250
8251         switch (gameMode) {
8252           case BeginningOfGame:
8253             /* Extra move from before last reset; ignore */
8254             if (appData.debugMode) {
8255                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8256             }
8257             return;
8258
8259           case EndOfGame:
8260           case IcsIdle:
8261           default:
8262             /* Extra move after we tried to stop.  The mode test is
8263                not a reliable way of detecting this problem, but it's
8264                the best we can do on engines that don't support ping.
8265             */
8266             if (appData.debugMode) {
8267                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8268                         cps->which, gameMode);
8269             }
8270             SendToProgram("undo\n", cps);
8271             return;
8272
8273           case MachinePlaysWhite:
8274           case IcsPlayingWhite:
8275             machineWhite = TRUE;
8276             break;
8277
8278           case MachinePlaysBlack:
8279           case IcsPlayingBlack:
8280             machineWhite = FALSE;
8281             break;
8282
8283           case TwoMachinesPlay:
8284             machineWhite = (cps->twoMachinesColor[0] == 'w');
8285             break;
8286         }
8287         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8288             if (appData.debugMode) {
8289                 fprintf(debugFP,
8290                         "Ignoring move out of turn by %s, gameMode %d"
8291                         ", forwardMost %d\n",
8292                         cps->which, gameMode, forwardMostMove);
8293             }
8294             return;
8295         }
8296
8297         if(cps->alphaRank) AlphaRank(machineMove, 4);
8298         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8299                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8300             /* Machine move could not be parsed; ignore it. */
8301           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8302                     machineMove, _(cps->which));
8303             DisplayMoveError(buf1);
8304             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8305                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8306             if (gameMode == TwoMachinesPlay) {
8307               GameEnds(machineWhite ? BlackWins : WhiteWins,
8308                        buf1, GE_XBOARD);
8309             }
8310             return;
8311         }
8312
8313         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8314         /* So we have to redo legality test with true e.p. status here,  */
8315         /* to make sure an illegal e.p. capture does not slip through,   */
8316         /* to cause a forfeit on a justified illegal-move complaint      */
8317         /* of the opponent.                                              */
8318         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8319            ChessMove moveType;
8320            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8321                              fromY, fromX, toY, toX, promoChar);
8322             if(moveType == IllegalMove) {
8323               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8324                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8325                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8326                            buf1, GE_XBOARD);
8327                 return;
8328            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8329            /* [HGM] Kludge to handle engines that send FRC-style castling
8330               when they shouldn't (like TSCP-Gothic) */
8331            switch(moveType) {
8332              case WhiteASideCastleFR:
8333              case BlackASideCastleFR:
8334                toX+=2;
8335                currentMoveString[2]++;
8336                break;
8337              case WhiteHSideCastleFR:
8338              case BlackHSideCastleFR:
8339                toX--;
8340                currentMoveString[2]--;
8341                break;
8342              default: ; // nothing to do, but suppresses warning of pedantic compilers
8343            }
8344         }
8345         hintRequested = FALSE;
8346         lastHint[0] = NULLCHAR;
8347         bookRequested = FALSE;
8348         /* Program may be pondering now */
8349         cps->maybeThinking = TRUE;
8350         if (cps->sendTime == 2) cps->sendTime = 1;
8351         if (cps->offeredDraw) cps->offeredDraw--;
8352
8353         /* [AS] Save move info*/
8354         pvInfoList[ forwardMostMove ].score = programStats.score;
8355         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8356         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8357
8358         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8359
8360         /* Test suites abort the 'game' after one move */
8361         if(*appData.finger) {
8362            static FILE *f;
8363            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8364            if(!f) f = fopen(appData.finger, "w");
8365            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8366            else { DisplayFatalError("Bad output file", errno, 0); return; }
8367            free(fen);
8368            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8369         }
8370
8371         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8372         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8373             int count = 0;
8374
8375             while( count < adjudicateLossPlies ) {
8376                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8377
8378                 if( count & 1 ) {
8379                     score = -score; /* Flip score for winning side */
8380                 }
8381
8382                 if( score > adjudicateLossThreshold ) {
8383                     break;
8384                 }
8385
8386                 count++;
8387             }
8388
8389             if( count >= adjudicateLossPlies ) {
8390                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8391
8392                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8393                     "Xboard adjudication",
8394                     GE_XBOARD );
8395
8396                 return;
8397             }
8398         }
8399
8400         if(Adjudicate(cps)) {
8401             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8402             return; // [HGM] adjudicate: for all automatic game ends
8403         }
8404
8405 #if ZIPPY
8406         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8407             first.initDone) {
8408           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8409                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8410                 SendToICS("draw ");
8411                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8412           }
8413           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8414           ics_user_moved = 1;
8415           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8416                 char buf[3*MSG_SIZ];
8417
8418                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8419                         programStats.score / 100.,
8420                         programStats.depth,
8421                         programStats.time / 100.,
8422                         (unsigned int)programStats.nodes,
8423                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8424                         programStats.movelist);
8425                 SendToICS(buf);
8426 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8427           }
8428         }
8429 #endif
8430
8431         /* [AS] Clear stats for next move */
8432         ClearProgramStats();
8433         thinkOutput[0] = NULLCHAR;
8434         hiddenThinkOutputState = 0;
8435
8436         bookHit = NULL;
8437         if (gameMode == TwoMachinesPlay) {
8438             /* [HGM] relaying draw offers moved to after reception of move */
8439             /* and interpreting offer as claim if it brings draw condition */
8440             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8441                 SendToProgram("draw\n", cps->other);
8442             }
8443             if (cps->other->sendTime) {
8444                 SendTimeRemaining(cps->other,
8445                                   cps->other->twoMachinesColor[0] == 'w');
8446             }
8447             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8448             if (firstMove && !bookHit) {
8449                 firstMove = FALSE;
8450                 if (cps->other->useColors) {
8451                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8452                 }
8453                 SendToProgram("go\n", cps->other);
8454             }
8455             cps->other->maybeThinking = TRUE;
8456         }
8457
8458         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8459
8460         if (!pausing && appData.ringBellAfterMoves) {
8461             RingBell();
8462         }
8463
8464         /*
8465          * Reenable menu items that were disabled while
8466          * machine was thinking
8467          */
8468         if (gameMode != TwoMachinesPlay)
8469             SetUserThinkingEnables();
8470
8471         // [HGM] book: after book hit opponent has received move and is now in force mode
8472         // force the book reply into it, and then fake that it outputted this move by jumping
8473         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8474         if(bookHit) {
8475                 static char bookMove[MSG_SIZ]; // a bit generous?
8476
8477                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8478                 strcat(bookMove, bookHit);
8479                 message = bookMove;
8480                 cps = cps->other;
8481                 programStats.nodes = programStats.depth = programStats.time =
8482                 programStats.score = programStats.got_only_move = 0;
8483                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8484
8485                 if(cps->lastPing != cps->lastPong) {
8486                     savedMessage = message; // args for deferred call
8487                     savedState = cps;
8488                     ScheduleDelayedEvent(DeferredBookMove, 10);
8489                     return;
8490                 }
8491                 goto FakeBookMove;
8492         }
8493
8494         return;
8495     }
8496
8497     /* Set special modes for chess engines.  Later something general
8498      *  could be added here; for now there is just one kludge feature,
8499      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8500      *  when "xboard" is given as an interactive command.
8501      */
8502     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8503         cps->useSigint = FALSE;
8504         cps->useSigterm = FALSE;
8505     }
8506     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8507       ParseFeatures(message+8, cps);
8508       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8509     }
8510
8511     if (!strncmp(message, "setup ", 6) && 
8512         (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
8513                                         ) { // [HGM] allow first engine to define opening position
8514       int dummy, s=6; char buf[MSG_SIZ];
8515       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8516       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8517       if(startedFromSetupPosition) return;
8518       if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
8519       ParseFEN(boards[0], &dummy, message+s);
8520       DrawPosition(TRUE, boards[0]);
8521       startedFromSetupPosition = TRUE;
8522       return;
8523     }
8524     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8525      * want this, I was asked to put it in, and obliged.
8526      */
8527     if (!strncmp(message, "setboard ", 9)) {
8528         Board initial_position;
8529
8530         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8531
8532         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8533             DisplayError(_("Bad FEN received from engine"), 0);
8534             return ;
8535         } else {
8536            Reset(TRUE, FALSE);
8537            CopyBoard(boards[0], initial_position);
8538            initialRulePlies = FENrulePlies;
8539            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8540            else gameMode = MachinePlaysBlack;
8541            DrawPosition(FALSE, boards[currentMove]);
8542         }
8543         return;
8544     }
8545
8546     /*
8547      * Look for communication commands
8548      */
8549     if (!strncmp(message, "telluser ", 9)) {
8550         if(message[9] == '\\' && message[10] == '\\')
8551             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8552         PlayTellSound();
8553         DisplayNote(message + 9);
8554         return;
8555     }
8556     if (!strncmp(message, "tellusererror ", 14)) {
8557         cps->userError = 1;
8558         if(message[14] == '\\' && message[15] == '\\')
8559             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8560         PlayTellSound();
8561         DisplayError(message + 14, 0);
8562         return;
8563     }
8564     if (!strncmp(message, "tellopponent ", 13)) {
8565       if (appData.icsActive) {
8566         if (loggedOn) {
8567           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8568           SendToICS(buf1);
8569         }
8570       } else {
8571         DisplayNote(message + 13);
8572       }
8573       return;
8574     }
8575     if (!strncmp(message, "tellothers ", 11)) {
8576       if (appData.icsActive) {
8577         if (loggedOn) {
8578           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8579           SendToICS(buf1);
8580         }
8581       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8582       return;
8583     }
8584     if (!strncmp(message, "tellall ", 8)) {
8585       if (appData.icsActive) {
8586         if (loggedOn) {
8587           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8588           SendToICS(buf1);
8589         }
8590       } else {
8591         DisplayNote(message + 8);
8592       }
8593       return;
8594     }
8595     if (strncmp(message, "warning", 7) == 0) {
8596         /* Undocumented feature, use tellusererror in new code */
8597         DisplayError(message, 0);
8598         return;
8599     }
8600     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8601         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8602         strcat(realname, " query");
8603         AskQuestion(realname, buf2, buf1, cps->pr);
8604         return;
8605     }
8606     /* Commands from the engine directly to ICS.  We don't allow these to be
8607      *  sent until we are logged on. Crafty kibitzes have been known to
8608      *  interfere with the login process.
8609      */
8610     if (loggedOn) {
8611         if (!strncmp(message, "tellics ", 8)) {
8612             SendToICS(message + 8);
8613             SendToICS("\n");
8614             return;
8615         }
8616         if (!strncmp(message, "tellicsnoalias ", 15)) {
8617             SendToICS(ics_prefix);
8618             SendToICS(message + 15);
8619             SendToICS("\n");
8620             return;
8621         }
8622         /* The following are for backward compatibility only */
8623         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8624             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8625             SendToICS(ics_prefix);
8626             SendToICS(message);
8627             SendToICS("\n");
8628             return;
8629         }
8630     }
8631     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8632         return;
8633     }
8634     if(!strncmp(message, "highlight ", 10)) {
8635         if(appData.testLegality && appData.markers) return;
8636         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8637         return;
8638     }
8639     /*
8640      * If the move is illegal, cancel it and redraw the board.
8641      * Also deal with other error cases.  Matching is rather loose
8642      * here to accommodate engines written before the spec.
8643      */
8644     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8645         strncmp(message, "Error", 5) == 0) {
8646         if (StrStr(message, "name") ||
8647             StrStr(message, "rating") || StrStr(message, "?") ||
8648             StrStr(message, "result") || StrStr(message, "board") ||
8649             StrStr(message, "bk") || StrStr(message, "computer") ||
8650             StrStr(message, "variant") || StrStr(message, "hint") ||
8651             StrStr(message, "random") || StrStr(message, "depth") ||
8652             StrStr(message, "accepted")) {
8653             return;
8654         }
8655         if (StrStr(message, "protover")) {
8656           /* Program is responding to input, so it's apparently done
8657              initializing, and this error message indicates it is
8658              protocol version 1.  So we don't need to wait any longer
8659              for it to initialize and send feature commands. */
8660           FeatureDone(cps, 1);
8661           cps->protocolVersion = 1;
8662           return;
8663         }
8664         cps->maybeThinking = FALSE;
8665
8666         if (StrStr(message, "draw")) {
8667             /* Program doesn't have "draw" command */
8668             cps->sendDrawOffers = 0;
8669             return;
8670         }
8671         if (cps->sendTime != 1 &&
8672             (StrStr(message, "time") || StrStr(message, "otim"))) {
8673           /* Program apparently doesn't have "time" or "otim" command */
8674           cps->sendTime = 0;
8675           return;
8676         }
8677         if (StrStr(message, "analyze")) {
8678             cps->analysisSupport = FALSE;
8679             cps->analyzing = FALSE;
8680 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8681             EditGameEvent(); // [HGM] try to preserve loaded game
8682             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8683             DisplayError(buf2, 0);
8684             return;
8685         }
8686         if (StrStr(message, "(no matching move)st")) {
8687           /* Special kludge for GNU Chess 4 only */
8688           cps->stKludge = TRUE;
8689           SendTimeControl(cps, movesPerSession, timeControl,
8690                           timeIncrement, appData.searchDepth,
8691                           searchTime);
8692           return;
8693         }
8694         if (StrStr(message, "(no matching move)sd")) {
8695           /* Special kludge for GNU Chess 4 only */
8696           cps->sdKludge = TRUE;
8697           SendTimeControl(cps, movesPerSession, timeControl,
8698                           timeIncrement, appData.searchDepth,
8699                           searchTime);
8700           return;
8701         }
8702         if (!StrStr(message, "llegal")) {
8703             return;
8704         }
8705         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8706             gameMode == IcsIdle) return;
8707         if (forwardMostMove <= backwardMostMove) return;
8708         if (pausing) PauseEvent();
8709       if(appData.forceIllegal) {
8710             // [HGM] illegal: machine refused move; force position after move into it
8711           SendToProgram("force\n", cps);
8712           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8713                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8714                 // when black is to move, while there might be nothing on a2 or black
8715                 // might already have the move. So send the board as if white has the move.
8716                 // But first we must change the stm of the engine, as it refused the last move
8717                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8718                 if(WhiteOnMove(forwardMostMove)) {
8719                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8720                     SendBoard(cps, forwardMostMove); // kludgeless board
8721                 } else {
8722                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8723                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8724                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8725                 }
8726           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8727             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8728                  gameMode == TwoMachinesPlay)
8729               SendToProgram("go\n", cps);
8730             return;
8731       } else
8732         if (gameMode == PlayFromGameFile) {
8733             /* Stop reading this game file */
8734             gameMode = EditGame;
8735             ModeHighlight();
8736         }
8737         /* [HGM] illegal-move claim should forfeit game when Xboard */
8738         /* only passes fully legal moves                            */
8739         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8740             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8741                                 "False illegal-move claim", GE_XBOARD );
8742             return; // do not take back move we tested as valid
8743         }
8744         currentMove = forwardMostMove-1;
8745         DisplayMove(currentMove-1); /* before DisplayMoveError */
8746         SwitchClocks(forwardMostMove-1); // [HGM] race
8747         DisplayBothClocks();
8748         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8749                 parseList[currentMove], _(cps->which));
8750         DisplayMoveError(buf1);
8751         DrawPosition(FALSE, boards[currentMove]);
8752
8753         SetUserThinkingEnables();
8754         return;
8755     }
8756     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8757         /* Program has a broken "time" command that
8758            outputs a string not ending in newline.
8759            Don't use it. */
8760         cps->sendTime = 0;
8761     }
8762
8763     /*
8764      * If chess program startup fails, exit with an error message.
8765      * Attempts to recover here are futile. [HGM] Well, we try anyway
8766      */
8767     if ((StrStr(message, "unknown host") != NULL)
8768         || (StrStr(message, "No remote directory") != NULL)
8769         || (StrStr(message, "not found") != NULL)
8770         || (StrStr(message, "No such file") != NULL)
8771         || (StrStr(message, "can't alloc") != NULL)
8772         || (StrStr(message, "Permission denied") != NULL)) {
8773
8774         cps->maybeThinking = FALSE;
8775         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8776                 _(cps->which), cps->program, cps->host, message);
8777         RemoveInputSource(cps->isr);
8778         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8779             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8780             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8781         }
8782         return;
8783     }
8784
8785     /*
8786      * Look for hint output
8787      */
8788     if (sscanf(message, "Hint: %s", buf1) == 1) {
8789         if (cps == &first && hintRequested) {
8790             hintRequested = FALSE;
8791             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8792                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8793                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8794                                     PosFlags(forwardMostMove),
8795                                     fromY, fromX, toY, toX, promoChar, buf1);
8796                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8797                 DisplayInformation(buf2);
8798             } else {
8799                 /* Hint move could not be parsed!? */
8800               snprintf(buf2, sizeof(buf2),
8801                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8802                         buf1, _(cps->which));
8803                 DisplayError(buf2, 0);
8804             }
8805         } else {
8806           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8807         }
8808         return;
8809     }
8810
8811     /*
8812      * Ignore other messages if game is not in progress
8813      */
8814     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8815         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8816
8817     /*
8818      * look for win, lose, draw, or draw offer
8819      */
8820     if (strncmp(message, "1-0", 3) == 0) {
8821         char *p, *q, *r = "";
8822         p = strchr(message, '{');
8823         if (p) {
8824             q = strchr(p, '}');
8825             if (q) {
8826                 *q = NULLCHAR;
8827                 r = p + 1;
8828             }
8829         }
8830         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8831         return;
8832     } else if (strncmp(message, "0-1", 3) == 0) {
8833         char *p, *q, *r = "";
8834         p = strchr(message, '{');
8835         if (p) {
8836             q = strchr(p, '}');
8837             if (q) {
8838                 *q = NULLCHAR;
8839                 r = p + 1;
8840             }
8841         }
8842         /* Kludge for Arasan 4.1 bug */
8843         if (strcmp(r, "Black resigns") == 0) {
8844             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8845             return;
8846         }
8847         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8848         return;
8849     } else if (strncmp(message, "1/2", 3) == 0) {
8850         char *p, *q, *r = "";
8851         p = strchr(message, '{');
8852         if (p) {
8853             q = strchr(p, '}');
8854             if (q) {
8855                 *q = NULLCHAR;
8856                 r = p + 1;
8857             }
8858         }
8859
8860         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8861         return;
8862
8863     } else if (strncmp(message, "White resign", 12) == 0) {
8864         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8865         return;
8866     } else if (strncmp(message, "Black resign", 12) == 0) {
8867         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8868         return;
8869     } else if (strncmp(message, "White matches", 13) == 0 ||
8870                strncmp(message, "Black matches", 13) == 0   ) {
8871         /* [HGM] ignore GNUShogi noises */
8872         return;
8873     } else if (strncmp(message, "White", 5) == 0 &&
8874                message[5] != '(' &&
8875                StrStr(message, "Black") == NULL) {
8876         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8877         return;
8878     } else if (strncmp(message, "Black", 5) == 0 &&
8879                message[5] != '(') {
8880         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8881         return;
8882     } else if (strcmp(message, "resign") == 0 ||
8883                strcmp(message, "computer resigns") == 0) {
8884         switch (gameMode) {
8885           case MachinePlaysBlack:
8886           case IcsPlayingBlack:
8887             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8888             break;
8889           case MachinePlaysWhite:
8890           case IcsPlayingWhite:
8891             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8892             break;
8893           case TwoMachinesPlay:
8894             if (cps->twoMachinesColor[0] == 'w')
8895               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8896             else
8897               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8898             break;
8899           default:
8900             /* can't happen */
8901             break;
8902         }
8903         return;
8904     } else if (strncmp(message, "opponent mates", 14) == 0) {
8905         switch (gameMode) {
8906           case MachinePlaysBlack:
8907           case IcsPlayingBlack:
8908             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8909             break;
8910           case MachinePlaysWhite:
8911           case IcsPlayingWhite:
8912             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8913             break;
8914           case TwoMachinesPlay:
8915             if (cps->twoMachinesColor[0] == 'w')
8916               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8917             else
8918               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8919             break;
8920           default:
8921             /* can't happen */
8922             break;
8923         }
8924         return;
8925     } else if (strncmp(message, "computer mates", 14) == 0) {
8926         switch (gameMode) {
8927           case MachinePlaysBlack:
8928           case IcsPlayingBlack:
8929             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8930             break;
8931           case MachinePlaysWhite:
8932           case IcsPlayingWhite:
8933             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8934             break;
8935           case TwoMachinesPlay:
8936             if (cps->twoMachinesColor[0] == 'w')
8937               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8938             else
8939               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8940             break;
8941           default:
8942             /* can't happen */
8943             break;
8944         }
8945         return;
8946     } else if (strncmp(message, "checkmate", 9) == 0) {
8947         if (WhiteOnMove(forwardMostMove)) {
8948             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8949         } else {
8950             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8951         }
8952         return;
8953     } else if (strstr(message, "Draw") != NULL ||
8954                strstr(message, "game is a draw") != NULL) {
8955         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8956         return;
8957     } else if (strstr(message, "offer") != NULL &&
8958                strstr(message, "draw") != NULL) {
8959 #if ZIPPY
8960         if (appData.zippyPlay && first.initDone) {
8961             /* Relay offer to ICS */
8962             SendToICS(ics_prefix);
8963             SendToICS("draw\n");
8964         }
8965 #endif
8966         cps->offeredDraw = 2; /* valid until this engine moves twice */
8967         if (gameMode == TwoMachinesPlay) {
8968             if (cps->other->offeredDraw) {
8969                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8970             /* [HGM] in two-machine mode we delay relaying draw offer      */
8971             /* until after we also have move, to see if it is really claim */
8972             }
8973         } else if (gameMode == MachinePlaysWhite ||
8974                    gameMode == MachinePlaysBlack) {
8975           if (userOfferedDraw) {
8976             DisplayInformation(_("Machine accepts your draw offer"));
8977             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8978           } else {
8979             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8980           }
8981         }
8982     }
8983
8984
8985     /*
8986      * Look for thinking output
8987      */
8988     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8989           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8990                                 ) {
8991         int plylev, mvleft, mvtot, curscore, time;
8992         char mvname[MOVE_LEN];
8993         u64 nodes; // [DM]
8994         char plyext;
8995         int ignore = FALSE;
8996         int prefixHint = FALSE;
8997         mvname[0] = NULLCHAR;
8998
8999         switch (gameMode) {
9000           case MachinePlaysBlack:
9001           case IcsPlayingBlack:
9002             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9003             break;
9004           case MachinePlaysWhite:
9005           case IcsPlayingWhite:
9006             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9007             break;
9008           case AnalyzeMode:
9009           case AnalyzeFile:
9010             break;
9011           case IcsObserving: /* [DM] icsEngineAnalyze */
9012             if (!appData.icsEngineAnalyze) ignore = TRUE;
9013             break;
9014           case TwoMachinesPlay:
9015             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9016                 ignore = TRUE;
9017             }
9018             break;
9019           default:
9020             ignore = TRUE;
9021             break;
9022         }
9023
9024         if (!ignore) {
9025             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9026             buf1[0] = NULLCHAR;
9027             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9028                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9029
9030                 if (plyext != ' ' && plyext != '\t') {
9031                     time *= 100;
9032                 }
9033
9034                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9035                 if( cps->scoreIsAbsolute &&
9036                     ( gameMode == MachinePlaysBlack ||
9037                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9038                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9039                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9040                      !WhiteOnMove(currentMove)
9041                     ) )
9042                 {
9043                     curscore = -curscore;
9044                 }
9045
9046                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9047
9048                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9049                         char buf[MSG_SIZ];
9050                         FILE *f;
9051                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9052                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9053                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9054                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9055                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9056                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9057                                 fclose(f);
9058                         } else DisplayError(_("failed writing PV"), 0);
9059                 }
9060
9061                 tempStats.depth = plylev;
9062                 tempStats.nodes = nodes;
9063                 tempStats.time = time;
9064                 tempStats.score = curscore;
9065                 tempStats.got_only_move = 0;
9066
9067                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9068                         int ticklen;
9069
9070                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9071                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9072                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9073                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9074                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9075                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9076                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9077                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9078                 }
9079
9080                 /* Buffer overflow protection */
9081                 if (pv[0] != NULLCHAR) {
9082                     if (strlen(pv) >= sizeof(tempStats.movelist)
9083                         && appData.debugMode) {
9084                         fprintf(debugFP,
9085                                 "PV is too long; using the first %u bytes.\n",
9086                                 (unsigned) sizeof(tempStats.movelist) - 1);
9087                     }
9088
9089                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9090                 } else {
9091                     sprintf(tempStats.movelist, " no PV\n");
9092                 }
9093
9094                 if (tempStats.seen_stat) {
9095                     tempStats.ok_to_send = 1;
9096                 }
9097
9098                 if (strchr(tempStats.movelist, '(') != NULL) {
9099                     tempStats.line_is_book = 1;
9100                     tempStats.nr_moves = 0;
9101                     tempStats.moves_left = 0;
9102                 } else {
9103                     tempStats.line_is_book = 0;
9104                 }
9105
9106                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9107                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9108
9109                 SendProgramStatsToFrontend( cps, &tempStats );
9110
9111                 /*
9112                     [AS] Protect the thinkOutput buffer from overflow... this
9113                     is only useful if buf1 hasn't overflowed first!
9114                 */
9115                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9116                          plylev,
9117                          (gameMode == TwoMachinesPlay ?
9118                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9119                          ((double) curscore) / 100.0,
9120                          prefixHint ? lastHint : "",
9121                          prefixHint ? " " : "" );
9122
9123                 if( buf1[0] != NULLCHAR ) {
9124                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9125
9126                     if( strlen(pv) > max_len ) {
9127                         if( appData.debugMode) {
9128                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9129                         }
9130                         pv[max_len+1] = '\0';
9131                     }
9132
9133                     strcat( thinkOutput, pv);
9134                 }
9135
9136                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9137                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9138                     DisplayMove(currentMove - 1);
9139                 }
9140                 return;
9141
9142             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9143                 /* crafty (9.25+) says "(only move) <move>"
9144                  * if there is only 1 legal move
9145                  */
9146                 sscanf(p, "(only move) %s", buf1);
9147                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9148                 sprintf(programStats.movelist, "%s (only move)", buf1);
9149                 programStats.depth = 1;
9150                 programStats.nr_moves = 1;
9151                 programStats.moves_left = 1;
9152                 programStats.nodes = 1;
9153                 programStats.time = 1;
9154                 programStats.got_only_move = 1;
9155
9156                 /* Not really, but we also use this member to
9157                    mean "line isn't going to change" (Crafty
9158                    isn't searching, so stats won't change) */
9159                 programStats.line_is_book = 1;
9160
9161                 SendProgramStatsToFrontend( cps, &programStats );
9162
9163                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9164                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9165                     DisplayMove(currentMove - 1);
9166                 }
9167                 return;
9168             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9169                               &time, &nodes, &plylev, &mvleft,
9170                               &mvtot, mvname) >= 5) {
9171                 /* The stat01: line is from Crafty (9.29+) in response
9172                    to the "." command */
9173                 programStats.seen_stat = 1;
9174                 cps->maybeThinking = TRUE;
9175
9176                 if (programStats.got_only_move || !appData.periodicUpdates)
9177                   return;
9178
9179                 programStats.depth = plylev;
9180                 programStats.time = time;
9181                 programStats.nodes = nodes;
9182                 programStats.moves_left = mvleft;
9183                 programStats.nr_moves = mvtot;
9184                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9185                 programStats.ok_to_send = 1;
9186                 programStats.movelist[0] = '\0';
9187
9188                 SendProgramStatsToFrontend( cps, &programStats );
9189
9190                 return;
9191
9192             } else if (strncmp(message,"++",2) == 0) {
9193                 /* Crafty 9.29+ outputs this */
9194                 programStats.got_fail = 2;
9195                 return;
9196
9197             } else if (strncmp(message,"--",2) == 0) {
9198                 /* Crafty 9.29+ outputs this */
9199                 programStats.got_fail = 1;
9200                 return;
9201
9202             } else if (thinkOutput[0] != NULLCHAR &&
9203                        strncmp(message, "    ", 4) == 0) {
9204                 unsigned message_len;
9205
9206                 p = message;
9207                 while (*p && *p == ' ') p++;
9208
9209                 message_len = strlen( p );
9210
9211                 /* [AS] Avoid buffer overflow */
9212                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9213                     strcat(thinkOutput, " ");
9214                     strcat(thinkOutput, p);
9215                 }
9216
9217                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9218                     strcat(programStats.movelist, " ");
9219                     strcat(programStats.movelist, p);
9220                 }
9221
9222                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9223                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9224                     DisplayMove(currentMove - 1);
9225                 }
9226                 return;
9227             }
9228         }
9229         else {
9230             buf1[0] = NULLCHAR;
9231
9232             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9233                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9234             {
9235                 ChessProgramStats cpstats;
9236
9237                 if (plyext != ' ' && plyext != '\t') {
9238                     time *= 100;
9239                 }
9240
9241                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9242                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9243                     curscore = -curscore;
9244                 }
9245
9246                 cpstats.depth = plylev;
9247                 cpstats.nodes = nodes;
9248                 cpstats.time = time;
9249                 cpstats.score = curscore;
9250                 cpstats.got_only_move = 0;
9251                 cpstats.movelist[0] = '\0';
9252
9253                 if (buf1[0] != NULLCHAR) {
9254                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9255                 }
9256
9257                 cpstats.ok_to_send = 0;
9258                 cpstats.line_is_book = 0;
9259                 cpstats.nr_moves = 0;
9260                 cpstats.moves_left = 0;
9261
9262                 SendProgramStatsToFrontend( cps, &cpstats );
9263             }
9264         }
9265     }
9266 }
9267
9268
9269 /* Parse a game score from the character string "game", and
9270    record it as the history of the current game.  The game
9271    score is NOT assumed to start from the standard position.
9272    The display is not updated in any way.
9273    */
9274 void
9275 ParseGameHistory (char *game)
9276 {
9277     ChessMove moveType;
9278     int fromX, fromY, toX, toY, boardIndex;
9279     char promoChar;
9280     char *p, *q;
9281     char buf[MSG_SIZ];
9282
9283     if (appData.debugMode)
9284       fprintf(debugFP, "Parsing game history: %s\n", game);
9285
9286     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9287     gameInfo.site = StrSave(appData.icsHost);
9288     gameInfo.date = PGNDate();
9289     gameInfo.round = StrSave("-");
9290
9291     /* Parse out names of players */
9292     while (*game == ' ') game++;
9293     p = buf;
9294     while (*game != ' ') *p++ = *game++;
9295     *p = NULLCHAR;
9296     gameInfo.white = StrSave(buf);
9297     while (*game == ' ') game++;
9298     p = buf;
9299     while (*game != ' ' && *game != '\n') *p++ = *game++;
9300     *p = NULLCHAR;
9301     gameInfo.black = StrSave(buf);
9302
9303     /* Parse moves */
9304     boardIndex = blackPlaysFirst ? 1 : 0;
9305     yynewstr(game);
9306     for (;;) {
9307         yyboardindex = boardIndex;
9308         moveType = (ChessMove) Myylex();
9309         switch (moveType) {
9310           case IllegalMove:             /* maybe suicide chess, etc. */
9311   if (appData.debugMode) {
9312     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9313     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9314     setbuf(debugFP, NULL);
9315   }
9316           case WhitePromotion:
9317           case BlackPromotion:
9318           case WhiteNonPromotion:
9319           case BlackNonPromotion:
9320           case NormalMove:
9321           case WhiteCapturesEnPassant:
9322           case BlackCapturesEnPassant:
9323           case WhiteKingSideCastle:
9324           case WhiteQueenSideCastle:
9325           case BlackKingSideCastle:
9326           case BlackQueenSideCastle:
9327           case WhiteKingSideCastleWild:
9328           case WhiteQueenSideCastleWild:
9329           case BlackKingSideCastleWild:
9330           case BlackQueenSideCastleWild:
9331           /* PUSH Fabien */
9332           case WhiteHSideCastleFR:
9333           case WhiteASideCastleFR:
9334           case BlackHSideCastleFR:
9335           case BlackASideCastleFR:
9336           /* POP Fabien */
9337             fromX = currentMoveString[0] - AAA;
9338             fromY = currentMoveString[1] - ONE;
9339             toX = currentMoveString[2] - AAA;
9340             toY = currentMoveString[3] - ONE;
9341             promoChar = currentMoveString[4];
9342             break;
9343           case WhiteDrop:
9344           case BlackDrop:
9345             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9346             fromX = moveType == WhiteDrop ?
9347               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9348             (int) CharToPiece(ToLower(currentMoveString[0]));
9349             fromY = DROP_RANK;
9350             toX = currentMoveString[2] - AAA;
9351             toY = currentMoveString[3] - ONE;
9352             promoChar = NULLCHAR;
9353             break;
9354           case AmbiguousMove:
9355             /* bug? */
9356             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9357   if (appData.debugMode) {
9358     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9359     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9360     setbuf(debugFP, NULL);
9361   }
9362             DisplayError(buf, 0);
9363             return;
9364           case ImpossibleMove:
9365             /* bug? */
9366             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9367   if (appData.debugMode) {
9368     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9369     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9370     setbuf(debugFP, NULL);
9371   }
9372             DisplayError(buf, 0);
9373             return;
9374           case EndOfFile:
9375             if (boardIndex < backwardMostMove) {
9376                 /* Oops, gap.  How did that happen? */
9377                 DisplayError(_("Gap in move list"), 0);
9378                 return;
9379             }
9380             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9381             if (boardIndex > forwardMostMove) {
9382                 forwardMostMove = boardIndex;
9383             }
9384             return;
9385           case ElapsedTime:
9386             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9387                 strcat(parseList[boardIndex-1], " ");
9388                 strcat(parseList[boardIndex-1], yy_text);
9389             }
9390             continue;
9391           case Comment:
9392           case PGNTag:
9393           case NAG:
9394           default:
9395             /* ignore */
9396             continue;
9397           case WhiteWins:
9398           case BlackWins:
9399           case GameIsDrawn:
9400           case GameUnfinished:
9401             if (gameMode == IcsExamining) {
9402                 if (boardIndex < backwardMostMove) {
9403                     /* Oops, gap.  How did that happen? */
9404                     return;
9405                 }
9406                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9407                 return;
9408             }
9409             gameInfo.result = moveType;
9410             p = strchr(yy_text, '{');
9411             if (p == NULL) p = strchr(yy_text, '(');
9412             if (p == NULL) {
9413                 p = yy_text;
9414                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9415             } else {
9416                 q = strchr(p, *p == '{' ? '}' : ')');
9417                 if (q != NULL) *q = NULLCHAR;
9418                 p++;
9419             }
9420             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9421             gameInfo.resultDetails = StrSave(p);
9422             continue;
9423         }
9424         if (boardIndex >= forwardMostMove &&
9425             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9426             backwardMostMove = blackPlaysFirst ? 1 : 0;
9427             return;
9428         }
9429         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9430                                  fromY, fromX, toY, toX, promoChar,
9431                                  parseList[boardIndex]);
9432         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9433         /* currentMoveString is set as a side-effect of yylex */
9434         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9435         strcat(moveList[boardIndex], "\n");
9436         boardIndex++;
9437         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9438         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9439           case MT_NONE:
9440           case MT_STALEMATE:
9441           default:
9442             break;
9443           case MT_CHECK:
9444             if(gameInfo.variant != VariantShogi)
9445                 strcat(parseList[boardIndex - 1], "+");
9446             break;
9447           case MT_CHECKMATE:
9448           case MT_STAINMATE:
9449             strcat(parseList[boardIndex - 1], "#");
9450             break;
9451         }
9452     }
9453 }
9454
9455
9456 /* Apply a move to the given board  */
9457 void
9458 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9459 {
9460   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9461   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9462
9463     /* [HGM] compute & store e.p. status and castling rights for new position */
9464     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9465
9466       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9467       oldEP = (signed char)board[EP_STATUS];
9468       board[EP_STATUS] = EP_NONE;
9469
9470   if (fromY == DROP_RANK) {
9471         /* must be first */
9472         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9473             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9474             return;
9475         }
9476         piece = board[toY][toX] = (ChessSquare) fromX;
9477   } else {
9478       int i;
9479
9480       if( board[toY][toX] != EmptySquare )
9481            board[EP_STATUS] = EP_CAPTURE;
9482
9483       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9484            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9485                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9486       } else
9487       if( board[fromY][fromX] == WhitePawn ) {
9488            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9489                board[EP_STATUS] = EP_PAWN_MOVE;
9490            if( toY-fromY==2) {
9491                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9492                         gameInfo.variant != VariantBerolina || toX < fromX)
9493                       board[EP_STATUS] = toX | berolina;
9494                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9495                         gameInfo.variant != VariantBerolina || toX > fromX)
9496                       board[EP_STATUS] = toX;
9497            }
9498       } else
9499       if( board[fromY][fromX] == BlackPawn ) {
9500            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9501                board[EP_STATUS] = EP_PAWN_MOVE;
9502            if( toY-fromY== -2) {
9503                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9504                         gameInfo.variant != VariantBerolina || toX < fromX)
9505                       board[EP_STATUS] = toX | berolina;
9506                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9507                         gameInfo.variant != VariantBerolina || toX > fromX)
9508                       board[EP_STATUS] = toX;
9509            }
9510        }
9511
9512        for(i=0; i<nrCastlingRights; i++) {
9513            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9514               board[CASTLING][i] == toX   && castlingRank[i] == toY
9515              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9516        }
9517
9518        if(gameInfo.variant == VariantSChess) { // update virginity
9519            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9520            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9521            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9522            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9523        }
9524
9525      if (fromX == toX && fromY == toY) return;
9526
9527      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9528      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9529      if(gameInfo.variant == VariantKnightmate)
9530          king += (int) WhiteUnicorn - (int) WhiteKing;
9531
9532     /* Code added by Tord: */
9533     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9534     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9535         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9536       board[fromY][fromX] = EmptySquare;
9537       board[toY][toX] = EmptySquare;
9538       if((toX > fromX) != (piece == WhiteRook)) {
9539         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9540       } else {
9541         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9542       }
9543     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9544                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9545       board[fromY][fromX] = EmptySquare;
9546       board[toY][toX] = EmptySquare;
9547       if((toX > fromX) != (piece == BlackRook)) {
9548         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9549       } else {
9550         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9551       }
9552     /* End of code added by Tord */
9553
9554     } else if (board[fromY][fromX] == king
9555         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9556         && toY == fromY && toX > fromX+1) {
9557         board[fromY][fromX] = EmptySquare;
9558         board[toY][toX] = king;
9559         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9560         board[fromY][BOARD_RGHT-1] = EmptySquare;
9561     } else if (board[fromY][fromX] == king
9562         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9563                && toY == fromY && toX < fromX-1) {
9564         board[fromY][fromX] = EmptySquare;
9565         board[toY][toX] = king;
9566         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9567         board[fromY][BOARD_LEFT] = EmptySquare;
9568     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9569                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9570                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9571                ) {
9572         /* white pawn promotion */
9573         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9574         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9575             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9576         board[fromY][fromX] = EmptySquare;
9577     } else if ((fromY >= BOARD_HEIGHT>>1)
9578                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9579                && (toX != fromX)
9580                && gameInfo.variant != VariantXiangqi
9581                && gameInfo.variant != VariantBerolina
9582                && (board[fromY][fromX] == WhitePawn)
9583                && (board[toY][toX] == EmptySquare)) {
9584         board[fromY][fromX] = EmptySquare;
9585         board[toY][toX] = WhitePawn;
9586         captured = board[toY - 1][toX];
9587         board[toY - 1][toX] = EmptySquare;
9588     } else if ((fromY == BOARD_HEIGHT-4)
9589                && (toX == fromX)
9590                && gameInfo.variant == VariantBerolina
9591                && (board[fromY][fromX] == WhitePawn)
9592                && (board[toY][toX] == EmptySquare)) {
9593         board[fromY][fromX] = EmptySquare;
9594         board[toY][toX] = WhitePawn;
9595         if(oldEP & EP_BEROLIN_A) {
9596                 captured = board[fromY][fromX-1];
9597                 board[fromY][fromX-1] = EmptySquare;
9598         }else{  captured = board[fromY][fromX+1];
9599                 board[fromY][fromX+1] = EmptySquare;
9600         }
9601     } else if (board[fromY][fromX] == king
9602         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9603                && toY == fromY && toX > fromX+1) {
9604         board[fromY][fromX] = EmptySquare;
9605         board[toY][toX] = king;
9606         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9607         board[fromY][BOARD_RGHT-1] = EmptySquare;
9608     } else if (board[fromY][fromX] == king
9609         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9610                && toY == fromY && toX < fromX-1) {
9611         board[fromY][fromX] = EmptySquare;
9612         board[toY][toX] = king;
9613         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9614         board[fromY][BOARD_LEFT] = EmptySquare;
9615     } else if (fromY == 7 && fromX == 3
9616                && board[fromY][fromX] == BlackKing
9617                && toY == 7 && toX == 5) {
9618         board[fromY][fromX] = EmptySquare;
9619         board[toY][toX] = BlackKing;
9620         board[fromY][7] = EmptySquare;
9621         board[toY][4] = BlackRook;
9622     } else if (fromY == 7 && fromX == 3
9623                && board[fromY][fromX] == BlackKing
9624                && toY == 7 && toX == 1) {
9625         board[fromY][fromX] = EmptySquare;
9626         board[toY][toX] = BlackKing;
9627         board[fromY][0] = EmptySquare;
9628         board[toY][2] = BlackRook;
9629     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9630                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9631                && toY < promoRank && promoChar
9632                ) {
9633         /* black pawn promotion */
9634         board[toY][toX] = CharToPiece(ToLower(promoChar));
9635         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9636             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9637         board[fromY][fromX] = EmptySquare;
9638     } else if ((fromY < BOARD_HEIGHT>>1)
9639                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9640                && (toX != fromX)
9641                && gameInfo.variant != VariantXiangqi
9642                && gameInfo.variant != VariantBerolina
9643                && (board[fromY][fromX] == BlackPawn)
9644                && (board[toY][toX] == EmptySquare)) {
9645         board[fromY][fromX] = EmptySquare;
9646         board[toY][toX] = BlackPawn;
9647         captured = board[toY + 1][toX];
9648         board[toY + 1][toX] = EmptySquare;
9649     } else if ((fromY == 3)
9650                && (toX == fromX)
9651                && gameInfo.variant == VariantBerolina
9652                && (board[fromY][fromX] == BlackPawn)
9653                && (board[toY][toX] == EmptySquare)) {
9654         board[fromY][fromX] = EmptySquare;
9655         board[toY][toX] = BlackPawn;
9656         if(oldEP & EP_BEROLIN_A) {
9657                 captured = board[fromY][fromX-1];
9658                 board[fromY][fromX-1] = EmptySquare;
9659         }else{  captured = board[fromY][fromX+1];
9660                 board[fromY][fromX+1] = EmptySquare;
9661         }
9662     } else {
9663         board[toY][toX] = board[fromY][fromX];
9664         board[fromY][fromX] = EmptySquare;
9665     }
9666   }
9667
9668     if (gameInfo.holdingsWidth != 0) {
9669
9670       /* !!A lot more code needs to be written to support holdings  */
9671       /* [HGM] OK, so I have written it. Holdings are stored in the */
9672       /* penultimate board files, so they are automaticlly stored   */
9673       /* in the game history.                                       */
9674       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9675                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9676         /* Delete from holdings, by decreasing count */
9677         /* and erasing image if necessary            */
9678         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9679         if(p < (int) BlackPawn) { /* white drop */
9680              p -= (int)WhitePawn;
9681                  p = PieceToNumber((ChessSquare)p);
9682              if(p >= gameInfo.holdingsSize) p = 0;
9683              if(--board[p][BOARD_WIDTH-2] <= 0)
9684                   board[p][BOARD_WIDTH-1] = EmptySquare;
9685              if((int)board[p][BOARD_WIDTH-2] < 0)
9686                         board[p][BOARD_WIDTH-2] = 0;
9687         } else {                  /* black drop */
9688              p -= (int)BlackPawn;
9689                  p = PieceToNumber((ChessSquare)p);
9690              if(p >= gameInfo.holdingsSize) p = 0;
9691              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9692                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9693              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9694                         board[BOARD_HEIGHT-1-p][1] = 0;
9695         }
9696       }
9697       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9698           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9699         /* [HGM] holdings: Add to holdings, if holdings exist */
9700         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9701                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9702                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9703         }
9704         p = (int) captured;
9705         if (p >= (int) BlackPawn) {
9706           p -= (int)BlackPawn;
9707           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9708                   /* in Shogi restore piece to its original  first */
9709                   captured = (ChessSquare) (DEMOTED captured);
9710                   p = DEMOTED p;
9711           }
9712           p = PieceToNumber((ChessSquare)p);
9713           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9714           board[p][BOARD_WIDTH-2]++;
9715           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9716         } else {
9717           p -= (int)WhitePawn;
9718           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9719                   captured = (ChessSquare) (DEMOTED captured);
9720                   p = DEMOTED p;
9721           }
9722           p = PieceToNumber((ChessSquare)p);
9723           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9724           board[BOARD_HEIGHT-1-p][1]++;
9725           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9726         }
9727       }
9728     } else if (gameInfo.variant == VariantAtomic) {
9729       if (captured != EmptySquare) {
9730         int y, x;
9731         for (y = toY-1; y <= toY+1; y++) {
9732           for (x = toX-1; x <= toX+1; x++) {
9733             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9734                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9735               board[y][x] = EmptySquare;
9736             }
9737           }
9738         }
9739         board[toY][toX] = EmptySquare;
9740       }
9741     }
9742     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9743         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9744     } else
9745     if(promoChar == '+') {
9746         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9747         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9748     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9749         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9750         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9751            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9752         board[toY][toX] = newPiece;
9753     }
9754     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9755                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9756         // [HGM] superchess: take promotion piece out of holdings
9757         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9758         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9759             if(!--board[k][BOARD_WIDTH-2])
9760                 board[k][BOARD_WIDTH-1] = EmptySquare;
9761         } else {
9762             if(!--board[BOARD_HEIGHT-1-k][1])
9763                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9764         }
9765     }
9766
9767 }
9768
9769 /* Updates forwardMostMove */
9770 void
9771 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9772 {
9773 //    forwardMostMove++; // [HGM] bare: moved downstream
9774
9775     (void) CoordsToAlgebraic(boards[forwardMostMove],
9776                              PosFlags(forwardMostMove),
9777                              fromY, fromX, toY, toX, promoChar,
9778                              parseList[forwardMostMove]);
9779
9780     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9781         int timeLeft; static int lastLoadFlag=0; int king, piece;
9782         piece = boards[forwardMostMove][fromY][fromX];
9783         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9784         if(gameInfo.variant == VariantKnightmate)
9785             king += (int) WhiteUnicorn - (int) WhiteKing;
9786         if(forwardMostMove == 0) {
9787             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9788                 fprintf(serverMoves, "%s;", UserName());
9789             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9790                 fprintf(serverMoves, "%s;", second.tidy);
9791             fprintf(serverMoves, "%s;", first.tidy);
9792             if(gameMode == MachinePlaysWhite)
9793                 fprintf(serverMoves, "%s;", UserName());
9794             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9795                 fprintf(serverMoves, "%s;", second.tidy);
9796         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9797         lastLoadFlag = loadFlag;
9798         // print base move
9799         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9800         // print castling suffix
9801         if( toY == fromY && piece == king ) {
9802             if(toX-fromX > 1)
9803                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9804             if(fromX-toX >1)
9805                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9806         }
9807         // e.p. suffix
9808         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9809              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9810              boards[forwardMostMove][toY][toX] == EmptySquare
9811              && fromX != toX && fromY != toY)
9812                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9813         // promotion suffix
9814         if(promoChar != NULLCHAR) {
9815             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9816                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9817                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9818             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9819         }
9820         if(!loadFlag) {
9821                 char buf[MOVE_LEN*2], *p; int len;
9822             fprintf(serverMoves, "/%d/%d",
9823                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9824             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9825             else                      timeLeft = blackTimeRemaining/1000;
9826             fprintf(serverMoves, "/%d", timeLeft);
9827                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9828                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9829                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9830                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9831             fprintf(serverMoves, "/%s", buf);
9832         }
9833         fflush(serverMoves);
9834     }
9835
9836     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9837         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9838       return;
9839     }
9840     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9841     if (commentList[forwardMostMove+1] != NULL) {
9842         free(commentList[forwardMostMove+1]);
9843         commentList[forwardMostMove+1] = NULL;
9844     }
9845     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9846     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9847     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9848     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9849     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9850     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9851     adjustedClock = FALSE;
9852     gameInfo.result = GameUnfinished;
9853     if (gameInfo.resultDetails != NULL) {
9854         free(gameInfo.resultDetails);
9855         gameInfo.resultDetails = NULL;
9856     }
9857     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9858                               moveList[forwardMostMove - 1]);
9859     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9860       case MT_NONE:
9861       case MT_STALEMATE:
9862       default:
9863         break;
9864       case MT_CHECK:
9865         if(gameInfo.variant != VariantShogi)
9866             strcat(parseList[forwardMostMove - 1], "+");
9867         break;
9868       case MT_CHECKMATE:
9869       case MT_STAINMATE:
9870         strcat(parseList[forwardMostMove - 1], "#");
9871         break;
9872     }
9873
9874 }
9875
9876 /* Updates currentMove if not pausing */
9877 void
9878 ShowMove (int fromX, int fromY, int toX, int toY)
9879 {
9880     int instant = (gameMode == PlayFromGameFile) ?
9881         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9882     if(appData.noGUI) return;
9883     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9884         if (!instant) {
9885             if (forwardMostMove == currentMove + 1) {
9886                 AnimateMove(boards[forwardMostMove - 1],
9887                             fromX, fromY, toX, toY);
9888             }
9889         }
9890         currentMove = forwardMostMove;
9891     }
9892
9893     if (instant) return;
9894
9895     DisplayMove(currentMove - 1);
9896     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9897             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9898                 SetHighlights(fromX, fromY, toX, toY);
9899             }
9900     }
9901     DrawPosition(FALSE, boards[currentMove]);
9902     DisplayBothClocks();
9903     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9904 }
9905
9906 void
9907 SendEgtPath (ChessProgramState *cps)
9908 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9909         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9910
9911         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9912
9913         while(*p) {
9914             char c, *q = name+1, *r, *s;
9915
9916             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9917             while(*p && *p != ',') *q++ = *p++;
9918             *q++ = ':'; *q = 0;
9919             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9920                 strcmp(name, ",nalimov:") == 0 ) {
9921                 // take nalimov path from the menu-changeable option first, if it is defined
9922               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9923                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9924             } else
9925             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9926                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9927                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9928                 s = r = StrStr(s, ":") + 1; // beginning of path info
9929                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9930                 c = *r; *r = 0;             // temporarily null-terminate path info
9931                     *--q = 0;               // strip of trailig ':' from name
9932                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9933                 *r = c;
9934                 SendToProgram(buf,cps);     // send egtbpath command for this format
9935             }
9936             if(*p == ',') p++; // read away comma to position for next format name
9937         }
9938 }
9939
9940 static int
9941 NonStandardBoardSize ()
9942 {
9943       /* [HGM] Awkward testing. Should really be a table */
9944       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9945       if( gameInfo.variant == VariantXiangqi )
9946            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9947       if( gameInfo.variant == VariantShogi )
9948            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9949       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9950            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9951       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9952           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9953            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9954       if( gameInfo.variant == VariantCourier )
9955            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9956       if( gameInfo.variant == VariantSuper )
9957            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9958       if( gameInfo.variant == VariantGreat )
9959            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9960       if( gameInfo.variant == VariantSChess )
9961            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9962       if( gameInfo.variant == VariantGrand )
9963            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9964       return overruled;
9965 }
9966
9967 void
9968 InitChessProgram (ChessProgramState *cps, int setup)
9969 /* setup needed to setup FRC opening position */
9970 {
9971     char buf[MSG_SIZ], b[MSG_SIZ];
9972     if (appData.noChessProgram) return;
9973     hintRequested = FALSE;
9974     bookRequested = FALSE;
9975
9976     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9977     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9978     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9979     if(cps->memSize) { /* [HGM] memory */
9980       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9981         SendToProgram(buf, cps);
9982     }
9983     SendEgtPath(cps); /* [HGM] EGT */
9984     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9985       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9986         SendToProgram(buf, cps);
9987     }
9988
9989     SendToProgram(cps->initString, cps);
9990     if (gameInfo.variant != VariantNormal &&
9991         gameInfo.variant != VariantLoadable
9992         /* [HGM] also send variant if board size non-standard */
9993         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9994                                             ) {
9995       char *v = VariantName(gameInfo.variant);
9996       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9997         /* [HGM] in protocol 1 we have to assume all variants valid */
9998         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9999         DisplayFatalError(buf, 0, 1);
10000         return;
10001       }
10002
10003       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10004         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10005                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10006            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10007            if(StrStr(cps->variants, b) == NULL) {
10008                // specific sized variant not known, check if general sizing allowed
10009                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10010                    if(StrStr(cps->variants, "boardsize") == NULL) {
10011                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10012                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10013                        DisplayFatalError(buf, 0, 1);
10014                        return;
10015                    }
10016                    /* [HGM] here we really should compare with the maximum supported board size */
10017                }
10018            }
10019       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10020       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10021       SendToProgram(buf, cps);
10022     }
10023     currentlyInitializedVariant = gameInfo.variant;
10024
10025     /* [HGM] send opening position in FRC to first engine */
10026     if(setup) {
10027           SendToProgram("force\n", cps);
10028           SendBoard(cps, 0);
10029           /* engine is now in force mode! Set flag to wake it up after first move. */
10030           setboardSpoiledMachineBlack = 1;
10031     }
10032
10033     if (cps->sendICS) {
10034       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10035       SendToProgram(buf, cps);
10036     }
10037     cps->maybeThinking = FALSE;
10038     cps->offeredDraw = 0;
10039     if (!appData.icsActive) {
10040         SendTimeControl(cps, movesPerSession, timeControl,
10041                         timeIncrement, appData.searchDepth,
10042                         searchTime);
10043     }
10044     if (appData.showThinking
10045         // [HGM] thinking: four options require thinking output to be sent
10046         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10047                                 ) {
10048         SendToProgram("post\n", cps);
10049     }
10050     SendToProgram("hard\n", cps);
10051     if (!appData.ponderNextMove) {
10052         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10053            it without being sure what state we are in first.  "hard"
10054            is not a toggle, so that one is OK.
10055          */
10056         SendToProgram("easy\n", cps);
10057     }
10058     if (cps->usePing) {
10059       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10060       SendToProgram(buf, cps);
10061     }
10062     cps->initDone = TRUE;
10063     ClearEngineOutputPane(cps == &second);
10064 }
10065
10066
10067 void
10068 ResendOptions (ChessProgramState *cps)
10069 { // send the stored value of the options
10070   int i;
10071   char buf[MSG_SIZ];
10072   Option *opt = cps->option;
10073   for(i=0; i<cps->nrOptions; i++, opt++) {
10074       switch(opt->type) {
10075         case Spin:
10076         case Slider:
10077         case CheckBox:
10078             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10079           break;
10080         case ComboBox:
10081           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10082           break;
10083         default:
10084             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10085           break;
10086         case Button:
10087         case SaveButton:
10088           continue;
10089       }
10090       SendToProgram(buf, cps);
10091   }
10092 }
10093
10094 void
10095 StartChessProgram (ChessProgramState *cps)
10096 {
10097     char buf[MSG_SIZ];
10098     int err;
10099
10100     if (appData.noChessProgram) return;
10101     cps->initDone = FALSE;
10102
10103     if (strcmp(cps->host, "localhost") == 0) {
10104         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10105     } else if (*appData.remoteShell == NULLCHAR) {
10106         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10107     } else {
10108         if (*appData.remoteUser == NULLCHAR) {
10109           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10110                     cps->program);
10111         } else {
10112           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10113                     cps->host, appData.remoteUser, cps->program);
10114         }
10115         err = StartChildProcess(buf, "", &cps->pr);
10116     }
10117
10118     if (err != 0) {
10119       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10120         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10121         if(cps != &first) return;
10122         appData.noChessProgram = TRUE;
10123         ThawUI();
10124         SetNCPMode();
10125 //      DisplayFatalError(buf, err, 1);
10126 //      cps->pr = NoProc;
10127 //      cps->isr = NULL;
10128         return;
10129     }
10130
10131     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10132     if (cps->protocolVersion > 1) {
10133       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10134       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10135         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10136         cps->comboCnt = 0;  //                and values of combo boxes
10137       }
10138       SendToProgram(buf, cps);
10139       if(cps->reload) ResendOptions(cps);
10140     } else {
10141       SendToProgram("xboard\n", cps);
10142     }
10143 }
10144
10145 void
10146 TwoMachinesEventIfReady P((void))
10147 {
10148   static int curMess = 0;
10149   if (first.lastPing != first.lastPong) {
10150     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10151     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10152     return;
10153   }
10154   if (second.lastPing != second.lastPong) {
10155     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10156     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10157     return;
10158   }
10159   DisplayMessage("", ""); curMess = 0;
10160   TwoMachinesEvent();
10161 }
10162
10163 char *
10164 MakeName (char *template)
10165 {
10166     time_t clock;
10167     struct tm *tm;
10168     static char buf[MSG_SIZ];
10169     char *p = buf;
10170     int i;
10171
10172     clock = time((time_t *)NULL);
10173     tm = localtime(&clock);
10174
10175     while(*p++ = *template++) if(p[-1] == '%') {
10176         switch(*template++) {
10177           case 0:   *p = 0; return buf;
10178           case 'Y': i = tm->tm_year+1900; break;
10179           case 'y': i = tm->tm_year-100; break;
10180           case 'M': i = tm->tm_mon+1; break;
10181           case 'd': i = tm->tm_mday; break;
10182           case 'h': i = tm->tm_hour; break;
10183           case 'm': i = tm->tm_min; break;
10184           case 's': i = tm->tm_sec; break;
10185           default:  i = 0;
10186         }
10187         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10188     }
10189     return buf;
10190 }
10191
10192 int
10193 CountPlayers (char *p)
10194 {
10195     int n = 0;
10196     while(p = strchr(p, '\n')) p++, n++; // count participants
10197     return n;
10198 }
10199
10200 FILE *
10201 WriteTourneyFile (char *results, FILE *f)
10202 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10203     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10204     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10205         // create a file with tournament description
10206         fprintf(f, "-participants {%s}\n", appData.participants);
10207         fprintf(f, "-seedBase %d\n", appData.seedBase);
10208         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10209         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10210         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10211         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10212         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10213         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10214         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10215         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10216         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10217         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10218         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10219         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10220         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10221         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10222         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10223         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10224         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10225         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10226         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10227         fprintf(f, "-smpCores %d\n", appData.smpCores);
10228         if(searchTime > 0)
10229                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10230         else {
10231                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10232                 fprintf(f, "-tc %s\n", appData.timeControl);
10233                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10234         }
10235         fprintf(f, "-results \"%s\"\n", results);
10236     }
10237     return f;
10238 }
10239
10240 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10241
10242 void
10243 Substitute (char *participants, int expunge)
10244 {
10245     int i, changed, changes=0, nPlayers=0;
10246     char *p, *q, *r, buf[MSG_SIZ];
10247     if(participants == NULL) return;
10248     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10249     r = p = participants; q = appData.participants;
10250     while(*p && *p == *q) {
10251         if(*p == '\n') r = p+1, nPlayers++;
10252         p++; q++;
10253     }
10254     if(*p) { // difference
10255         while(*p && *p++ != '\n');
10256         while(*q && *q++ != '\n');
10257       changed = nPlayers;
10258         changes = 1 + (strcmp(p, q) != 0);
10259     }
10260     if(changes == 1) { // a single engine mnemonic was changed
10261         q = r; while(*q) nPlayers += (*q++ == '\n');
10262         p = buf; while(*r && (*p = *r++) != '\n') p++;
10263         *p = NULLCHAR;
10264         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10265         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10266         if(mnemonic[i]) { // The substitute is valid
10267             FILE *f;
10268             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10269                 flock(fileno(f), LOCK_EX);
10270                 ParseArgsFromFile(f);
10271                 fseek(f, 0, SEEK_SET);
10272                 FREE(appData.participants); appData.participants = participants;
10273                 if(expunge) { // erase results of replaced engine
10274                     int len = strlen(appData.results), w, b, dummy;
10275                     for(i=0; i<len; i++) {
10276                         Pairing(i, nPlayers, &w, &b, &dummy);
10277                         if((w == changed || b == changed) && appData.results[i] == '*') {
10278                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10279                             fclose(f);
10280                             return;
10281                         }
10282                     }
10283                     for(i=0; i<len; i++) {
10284                         Pairing(i, nPlayers, &w, &b, &dummy);
10285                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10286                     }
10287                 }
10288                 WriteTourneyFile(appData.results, f);
10289                 fclose(f); // release lock
10290                 return;
10291             }
10292         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10293     }
10294     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10295     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10296     free(participants);
10297     return;
10298 }
10299
10300 int
10301 CheckPlayers (char *participants)
10302 {
10303         int i;
10304         char buf[MSG_SIZ], *p;
10305         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10306         while(p = strchr(participants, '\n')) {
10307             *p = NULLCHAR;
10308             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10309             if(!mnemonic[i]) {
10310                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10311                 *p = '\n';
10312                 DisplayError(buf, 0);
10313                 return 1;
10314             }
10315             *p = '\n';
10316             participants = p + 1;
10317         }
10318         return 0;
10319 }
10320
10321 int
10322 CreateTourney (char *name)
10323 {
10324         FILE *f;
10325         if(matchMode && strcmp(name, appData.tourneyFile)) {
10326              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10327         }
10328         if(name[0] == NULLCHAR) {
10329             if(appData.participants[0])
10330                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10331             return 0;
10332         }
10333         f = fopen(name, "r");
10334         if(f) { // file exists
10335             ASSIGN(appData.tourneyFile, name);
10336             ParseArgsFromFile(f); // parse it
10337         } else {
10338             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10339             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10340                 DisplayError(_("Not enough participants"), 0);
10341                 return 0;
10342             }
10343             if(CheckPlayers(appData.participants)) return 0;
10344             ASSIGN(appData.tourneyFile, name);
10345             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10346             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10347         }
10348         fclose(f);
10349         appData.noChessProgram = FALSE;
10350         appData.clockMode = TRUE;
10351         SetGNUMode();
10352         return 1;
10353 }
10354
10355 int
10356 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10357 {
10358     char buf[MSG_SIZ], *p, *q;
10359     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10360     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10361     skip = !all && group[0]; // if group requested, we start in skip mode
10362     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10363         p = names; q = buf; header = 0;
10364         while(*p && *p != '\n') *q++ = *p++;
10365         *q = 0;
10366         if(*p == '\n') p++;
10367         if(buf[0] == '#') {
10368             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10369             depth++; // we must be entering a new group
10370             if(all) continue; // suppress printing group headers when complete list requested
10371             header = 1;
10372             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10373         }
10374         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10375         if(engineList[i]) free(engineList[i]);
10376         engineList[i] = strdup(buf);
10377         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10378         if(engineMnemonic[i]) free(engineMnemonic[i]);
10379         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10380             strcat(buf, " (");
10381             sscanf(q + 8, "%s", buf + strlen(buf));
10382             strcat(buf, ")");
10383         }
10384         engineMnemonic[i] = strdup(buf);
10385         i++;
10386     }
10387     engineList[i] = engineMnemonic[i] = NULL;
10388     return i;
10389 }
10390
10391 // following implemented as macro to avoid type limitations
10392 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10393
10394 void
10395 SwapEngines (int n)
10396 {   // swap settings for first engine and other engine (so far only some selected options)
10397     int h;
10398     char *p;
10399     if(n == 0) return;
10400     SWAP(directory, p)
10401     SWAP(chessProgram, p)
10402     SWAP(isUCI, h)
10403     SWAP(hasOwnBookUCI, h)
10404     SWAP(protocolVersion, h)
10405     SWAP(reuse, h)
10406     SWAP(scoreIsAbsolute, h)
10407     SWAP(timeOdds, h)
10408     SWAP(logo, p)
10409     SWAP(pgnName, p)
10410     SWAP(pvSAN, h)
10411     SWAP(engOptions, p)
10412     SWAP(engInitString, p)
10413     SWAP(computerString, p)
10414     SWAP(features, p)
10415     SWAP(fenOverride, p)
10416     SWAP(NPS, h)
10417     SWAP(accumulateTC, h)
10418     SWAP(host, p)
10419 }
10420
10421 int
10422 GetEngineLine (char *s, int n)
10423 {
10424     int i;
10425     char buf[MSG_SIZ];
10426     extern char *icsNames;
10427     if(!s || !*s) return 0;
10428     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10429     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10430     if(!mnemonic[i]) return 0;
10431     if(n == 11) return 1; // just testing if there was a match
10432     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10433     if(n == 1) SwapEngines(n);
10434     ParseArgsFromString(buf);
10435     if(n == 1) SwapEngines(n);
10436     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10437         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10438         ParseArgsFromString(buf);
10439     }
10440     return 1;
10441 }
10442
10443 int
10444 SetPlayer (int player, char *p)
10445 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10446     int i;
10447     char buf[MSG_SIZ], *engineName;
10448     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10449     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10450     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10451     if(mnemonic[i]) {
10452         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10453         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10454         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10455         ParseArgsFromString(buf);
10456     } else { // no engine with this nickname is installed!
10457         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10458         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10459         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10460         ModeHighlight();
10461         DisplayError(buf, 0);
10462         return 0;
10463     }
10464     free(engineName);
10465     return i;
10466 }
10467
10468 char *recentEngines;
10469
10470 void
10471 RecentEngineEvent (int nr)
10472 {
10473     int n;
10474 //    SwapEngines(1); // bump first to second
10475 //    ReplaceEngine(&second, 1); // and load it there
10476     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10477     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10478     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10479         ReplaceEngine(&first, 0);
10480         FloatToFront(&appData.recentEngineList, command[n]);
10481     }
10482 }
10483
10484 int
10485 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10486 {   // determine players from game number
10487     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10488
10489     if(appData.tourneyType == 0) {
10490         roundsPerCycle = (nPlayers - 1) | 1;
10491         pairingsPerRound = nPlayers / 2;
10492     } else if(appData.tourneyType > 0) {
10493         roundsPerCycle = nPlayers - appData.tourneyType;
10494         pairingsPerRound = appData.tourneyType;
10495     }
10496     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10497     gamesPerCycle = gamesPerRound * roundsPerCycle;
10498     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10499     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10500     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10501     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10502     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10503     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10504
10505     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10506     if(appData.roundSync) *syncInterval = gamesPerRound;
10507
10508     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10509
10510     if(appData.tourneyType == 0) {
10511         if(curPairing == (nPlayers-1)/2 ) {
10512             *whitePlayer = curRound;
10513             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10514         } else {
10515             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10516             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10517             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10518             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10519         }
10520     } else if(appData.tourneyType > 1) {
10521         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10522         *whitePlayer = curRound + appData.tourneyType;
10523     } else if(appData.tourneyType > 0) {
10524         *whitePlayer = curPairing;
10525         *blackPlayer = curRound + appData.tourneyType;
10526     }
10527
10528     // take care of white/black alternation per round.
10529     // For cycles and games this is already taken care of by default, derived from matchGame!
10530     return curRound & 1;
10531 }
10532
10533 int
10534 NextTourneyGame (int nr, int *swapColors)
10535 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10536     char *p, *q;
10537     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10538     FILE *tf;
10539     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10540     tf = fopen(appData.tourneyFile, "r");
10541     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10542     ParseArgsFromFile(tf); fclose(tf);
10543     InitTimeControls(); // TC might be altered from tourney file
10544
10545     nPlayers = CountPlayers(appData.participants); // count participants
10546     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10547     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10548
10549     if(syncInterval) {
10550         p = q = appData.results;
10551         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10552         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10553             DisplayMessage(_("Waiting for other game(s)"),"");
10554             waitingForGame = TRUE;
10555             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10556             return 0;
10557         }
10558         waitingForGame = FALSE;
10559     }
10560
10561     if(appData.tourneyType < 0) {
10562         if(nr>=0 && !pairingReceived) {
10563             char buf[1<<16];
10564             if(pairing.pr == NoProc) {
10565                 if(!appData.pairingEngine[0]) {
10566                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10567                     return 0;
10568                 }
10569                 StartChessProgram(&pairing); // starts the pairing engine
10570             }
10571             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10572             SendToProgram(buf, &pairing);
10573             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10574             SendToProgram(buf, &pairing);
10575             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10576         }
10577         pairingReceived = 0;                              // ... so we continue here
10578         *swapColors = 0;
10579         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10580         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10581         matchGame = 1; roundNr = nr / syncInterval + 1;
10582     }
10583
10584     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10585
10586     // redefine engines, engine dir, etc.
10587     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10588     if(first.pr == NoProc) {
10589       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10590       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10591     }
10592     if(second.pr == NoProc) {
10593       SwapEngines(1);
10594       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10595       SwapEngines(1);         // and make that valid for second engine by swapping
10596       InitEngine(&second, 1);
10597     }
10598     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10599     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10600     return OK;
10601 }
10602
10603 void
10604 NextMatchGame ()
10605 {   // performs game initialization that does not invoke engines, and then tries to start the game
10606     int res, firstWhite, swapColors = 0;
10607     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10608     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
10609         char buf[MSG_SIZ];
10610         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10611         if(strcmp(buf, currentDebugFile)) { // name has changed
10612             FILE *f = fopen(buf, "w");
10613             if(f) { // if opening the new file failed, just keep using the old one
10614                 ASSIGN(currentDebugFile, buf);
10615                 fclose(debugFP);
10616                 debugFP = f;
10617             }
10618             if(appData.serverFileName) {
10619                 if(serverFP) fclose(serverFP);
10620                 serverFP = fopen(appData.serverFileName, "w");
10621                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10622                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10623             }
10624         }
10625     }
10626     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10627     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10628     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10629     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10630     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10631     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10632     Reset(FALSE, first.pr != NoProc);
10633     res = LoadGameOrPosition(matchGame); // setup game
10634     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10635     if(!res) return; // abort when bad game/pos file
10636     TwoMachinesEvent();
10637 }
10638
10639 void
10640 UserAdjudicationEvent (int result)
10641 {
10642     ChessMove gameResult = GameIsDrawn;
10643
10644     if( result > 0 ) {
10645         gameResult = WhiteWins;
10646     }
10647     else if( result < 0 ) {
10648         gameResult = BlackWins;
10649     }
10650
10651     if( gameMode == TwoMachinesPlay ) {
10652         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10653     }
10654 }
10655
10656
10657 // [HGM] save: calculate checksum of game to make games easily identifiable
10658 int
10659 StringCheckSum (char *s)
10660 {
10661         int i = 0;
10662         if(s==NULL) return 0;
10663         while(*s) i = i*259 + *s++;
10664         return i;
10665 }
10666
10667 int
10668 GameCheckSum ()
10669 {
10670         int i, sum=0;
10671         for(i=backwardMostMove; i<forwardMostMove; i++) {
10672                 sum += pvInfoList[i].depth;
10673                 sum += StringCheckSum(parseList[i]);
10674                 sum += StringCheckSum(commentList[i]);
10675                 sum *= 261;
10676         }
10677         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10678         return sum + StringCheckSum(commentList[i]);
10679 } // end of save patch
10680
10681 void
10682 GameEnds (ChessMove result, char *resultDetails, int whosays)
10683 {
10684     GameMode nextGameMode;
10685     int isIcsGame;
10686     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10687
10688     if(endingGame) return; /* [HGM] crash: forbid recursion */
10689     endingGame = 1;
10690     if(twoBoards) { // [HGM] dual: switch back to one board
10691         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10692         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10693     }
10694     if (appData.debugMode) {
10695       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10696               result, resultDetails ? resultDetails : "(null)", whosays);
10697     }
10698
10699     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10700
10701     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10702
10703     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10704         /* If we are playing on ICS, the server decides when the
10705            game is over, but the engine can offer to draw, claim
10706            a draw, or resign.
10707          */
10708 #if ZIPPY
10709         if (appData.zippyPlay && first.initDone) {
10710             if (result == GameIsDrawn) {
10711                 /* In case draw still needs to be claimed */
10712                 SendToICS(ics_prefix);
10713                 SendToICS("draw\n");
10714             } else if (StrCaseStr(resultDetails, "resign")) {
10715                 SendToICS(ics_prefix);
10716                 SendToICS("resign\n");
10717             }
10718         }
10719 #endif
10720         endingGame = 0; /* [HGM] crash */
10721         return;
10722     }
10723
10724     /* If we're loading the game from a file, stop */
10725     if (whosays == GE_FILE) {
10726       (void) StopLoadGameTimer();
10727       gameFileFP = NULL;
10728     }
10729
10730     /* Cancel draw offers */
10731     first.offeredDraw = second.offeredDraw = 0;
10732
10733     /* If this is an ICS game, only ICS can really say it's done;
10734        if not, anyone can. */
10735     isIcsGame = (gameMode == IcsPlayingWhite ||
10736                  gameMode == IcsPlayingBlack ||
10737                  gameMode == IcsObserving    ||
10738                  gameMode == IcsExamining);
10739
10740     if (!isIcsGame || whosays == GE_ICS) {
10741         /* OK -- not an ICS game, or ICS said it was done */
10742         StopClocks();
10743         if (!isIcsGame && !appData.noChessProgram)
10744           SetUserThinkingEnables();
10745
10746         /* [HGM] if a machine claims the game end we verify this claim */
10747         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10748             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10749                 char claimer;
10750                 ChessMove trueResult = (ChessMove) -1;
10751
10752                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10753                                             first.twoMachinesColor[0] :
10754                                             second.twoMachinesColor[0] ;
10755
10756                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10757                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10758                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10759                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10760                 } else
10761                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10762                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10763                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10764                 } else
10765                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10766                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10767                 }
10768
10769                 // now verify win claims, but not in drop games, as we don't understand those yet
10770                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10771                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10772                     (result == WhiteWins && claimer == 'w' ||
10773                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10774                       if (appData.debugMode) {
10775                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10776                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10777                       }
10778                       if(result != trueResult) {
10779                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10780                               result = claimer == 'w' ? BlackWins : WhiteWins;
10781                               resultDetails = buf;
10782                       }
10783                 } else
10784                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10785                     && (forwardMostMove <= backwardMostMove ||
10786                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10787                         (claimer=='b')==(forwardMostMove&1))
10788                                                                                   ) {
10789                       /* [HGM] verify: draws that were not flagged are false claims */
10790                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10791                       result = claimer == 'w' ? BlackWins : WhiteWins;
10792                       resultDetails = buf;
10793                 }
10794                 /* (Claiming a loss is accepted no questions asked!) */
10795             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10796                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10797                 result = GameUnfinished;
10798                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10799             }
10800             /* [HGM] bare: don't allow bare King to win */
10801             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10802                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10803                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10804                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10805                && result != GameIsDrawn)
10806             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10807                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10808                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10809                         if(p >= 0 && p <= (int)WhiteKing) k++;
10810                 }
10811                 if (appData.debugMode) {
10812                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10813                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10814                 }
10815                 if(k <= 1) {
10816                         result = GameIsDrawn;
10817                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10818                         resultDetails = buf;
10819                 }
10820             }
10821         }
10822
10823
10824         if(serverMoves != NULL && !loadFlag) { char c = '=';
10825             if(result==WhiteWins) c = '+';
10826             if(result==BlackWins) c = '-';
10827             if(resultDetails != NULL)
10828                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10829         }
10830         if (resultDetails != NULL) {
10831             gameInfo.result = result;
10832             gameInfo.resultDetails = StrSave(resultDetails);
10833
10834             /* display last move only if game was not loaded from file */
10835             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10836                 DisplayMove(currentMove - 1);
10837
10838             if (forwardMostMove != 0) {
10839                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10840                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10841                                                                 ) {
10842                     if (*appData.saveGameFile != NULLCHAR) {
10843                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10844                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10845                         else
10846                         SaveGameToFile(appData.saveGameFile, TRUE);
10847                     } else if (appData.autoSaveGames) {
10848                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10849                     }
10850                     if (*appData.savePositionFile != NULLCHAR) {
10851                         SavePositionToFile(appData.savePositionFile);
10852                     }
10853                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10854                 }
10855             }
10856
10857             /* Tell program how game ended in case it is learning */
10858             /* [HGM] Moved this to after saving the PGN, just in case */
10859             /* engine died and we got here through time loss. In that */
10860             /* case we will get a fatal error writing the pipe, which */
10861             /* would otherwise lose us the PGN.                       */
10862             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10863             /* output during GameEnds should never be fatal anymore   */
10864             if (gameMode == MachinePlaysWhite ||
10865                 gameMode == MachinePlaysBlack ||
10866                 gameMode == TwoMachinesPlay ||
10867                 gameMode == IcsPlayingWhite ||
10868                 gameMode == IcsPlayingBlack ||
10869                 gameMode == BeginningOfGame) {
10870                 char buf[MSG_SIZ];
10871                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10872                         resultDetails);
10873                 if (first.pr != NoProc) {
10874                     SendToProgram(buf, &first);
10875                 }
10876                 if (second.pr != NoProc &&
10877                     gameMode == TwoMachinesPlay) {
10878                     SendToProgram(buf, &second);
10879                 }
10880             }
10881         }
10882
10883         if (appData.icsActive) {
10884             if (appData.quietPlay &&
10885                 (gameMode == IcsPlayingWhite ||
10886                  gameMode == IcsPlayingBlack)) {
10887                 SendToICS(ics_prefix);
10888                 SendToICS("set shout 1\n");
10889             }
10890             nextGameMode = IcsIdle;
10891             ics_user_moved = FALSE;
10892             /* clean up premove.  It's ugly when the game has ended and the
10893              * premove highlights are still on the board.
10894              */
10895             if (gotPremove) {
10896               gotPremove = FALSE;
10897               ClearPremoveHighlights();
10898               DrawPosition(FALSE, boards[currentMove]);
10899             }
10900             if (whosays == GE_ICS) {
10901                 switch (result) {
10902                 case WhiteWins:
10903                     if (gameMode == IcsPlayingWhite)
10904                         PlayIcsWinSound();
10905                     else if(gameMode == IcsPlayingBlack)
10906                         PlayIcsLossSound();
10907                     break;
10908                 case BlackWins:
10909                     if (gameMode == IcsPlayingBlack)
10910                         PlayIcsWinSound();
10911                     else if(gameMode == IcsPlayingWhite)
10912                         PlayIcsLossSound();
10913                     break;
10914                 case GameIsDrawn:
10915                     PlayIcsDrawSound();
10916                     break;
10917                 default:
10918                     PlayIcsUnfinishedSound();
10919                 }
10920             }
10921             if(appData.quitNext) { ExitEvent(0); return; }
10922         } else if (gameMode == EditGame ||
10923                    gameMode == PlayFromGameFile ||
10924                    gameMode == AnalyzeMode ||
10925                    gameMode == AnalyzeFile) {
10926             nextGameMode = gameMode;
10927         } else {
10928             nextGameMode = EndOfGame;
10929         }
10930         pausing = FALSE;
10931         ModeHighlight();
10932     } else {
10933         nextGameMode = gameMode;
10934     }
10935
10936     if (appData.noChessProgram) {
10937         gameMode = nextGameMode;
10938         ModeHighlight();
10939         endingGame = 0; /* [HGM] crash */
10940         return;
10941     }
10942
10943     if (first.reuse) {
10944         /* Put first chess program into idle state */
10945         if (first.pr != NoProc &&
10946             (gameMode == MachinePlaysWhite ||
10947              gameMode == MachinePlaysBlack ||
10948              gameMode == TwoMachinesPlay ||
10949              gameMode == IcsPlayingWhite ||
10950              gameMode == IcsPlayingBlack ||
10951              gameMode == BeginningOfGame)) {
10952             SendToProgram("force\n", &first);
10953             if (first.usePing) {
10954               char buf[MSG_SIZ];
10955               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10956               SendToProgram(buf, &first);
10957             }
10958         }
10959     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10960         /* Kill off first chess program */
10961         if (first.isr != NULL)
10962           RemoveInputSource(first.isr);
10963         first.isr = NULL;
10964
10965         if (first.pr != NoProc) {
10966             ExitAnalyzeMode();
10967             DoSleep( appData.delayBeforeQuit );
10968             SendToProgram("quit\n", &first);
10969             DoSleep( appData.delayAfterQuit );
10970             DestroyChildProcess(first.pr, first.useSigterm);
10971             first.reload = TRUE;
10972         }
10973         first.pr = NoProc;
10974     }
10975     if (second.reuse) {
10976         /* Put second chess program into idle state */
10977         if (second.pr != NoProc &&
10978             gameMode == TwoMachinesPlay) {
10979             SendToProgram("force\n", &second);
10980             if (second.usePing) {
10981               char buf[MSG_SIZ];
10982               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10983               SendToProgram(buf, &second);
10984             }
10985         }
10986     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10987         /* Kill off second chess program */
10988         if (second.isr != NULL)
10989           RemoveInputSource(second.isr);
10990         second.isr = NULL;
10991
10992         if (second.pr != NoProc) {
10993             DoSleep( appData.delayBeforeQuit );
10994             SendToProgram("quit\n", &second);
10995             DoSleep( appData.delayAfterQuit );
10996             DestroyChildProcess(second.pr, second.useSigterm);
10997             second.reload = TRUE;
10998         }
10999         second.pr = NoProc;
11000     }
11001
11002     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11003         char resChar = '=';
11004         switch (result) {
11005         case WhiteWins:
11006           resChar = '+';
11007           if (first.twoMachinesColor[0] == 'w') {
11008             first.matchWins++;
11009           } else {
11010             second.matchWins++;
11011           }
11012           break;
11013         case BlackWins:
11014           resChar = '-';
11015           if (first.twoMachinesColor[0] == 'b') {
11016             first.matchWins++;
11017           } else {
11018             second.matchWins++;
11019           }
11020           break;
11021         case GameUnfinished:
11022           resChar = ' ';
11023         default:
11024           break;
11025         }
11026
11027         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11028         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11029             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11030             ReserveGame(nextGame, resChar); // sets nextGame
11031             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11032             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11033         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11034
11035         if (nextGame <= appData.matchGames && !abortMatch) {
11036             gameMode = nextGameMode;
11037             matchGame = nextGame; // this will be overruled in tourney mode!
11038             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11039             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11040             endingGame = 0; /* [HGM] crash */
11041             return;
11042         } else {
11043             gameMode = nextGameMode;
11044             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11045                      first.tidy, second.tidy,
11046                      first.matchWins, second.matchWins,
11047                      appData.matchGames - (first.matchWins + second.matchWins));
11048             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11049             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11050             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11051             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11052                 first.twoMachinesColor = "black\n";
11053                 second.twoMachinesColor = "white\n";
11054             } else {
11055                 first.twoMachinesColor = "white\n";
11056                 second.twoMachinesColor = "black\n";
11057             }
11058         }
11059     }
11060     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11061         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11062       ExitAnalyzeMode();
11063     gameMode = nextGameMode;
11064     ModeHighlight();
11065     endingGame = 0;  /* [HGM] crash */
11066     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11067         if(matchMode == TRUE) { // match through command line: exit with or without popup
11068             if(ranking) {
11069                 ToNrEvent(forwardMostMove);
11070                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11071                 else ExitEvent(0);
11072             } else DisplayFatalError(buf, 0, 0);
11073         } else { // match through menu; just stop, with or without popup
11074             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11075             ModeHighlight();
11076             if(ranking){
11077                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11078             } else DisplayNote(buf);
11079       }
11080       if(ranking) free(ranking);
11081     }
11082 }
11083
11084 /* Assumes program was just initialized (initString sent).
11085    Leaves program in force mode. */
11086 void
11087 FeedMovesToProgram (ChessProgramState *cps, int upto)
11088 {
11089     int i;
11090
11091     if (appData.debugMode)
11092       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11093               startedFromSetupPosition ? "position and " : "",
11094               backwardMostMove, upto, cps->which);
11095     if(currentlyInitializedVariant != gameInfo.variant) {
11096       char buf[MSG_SIZ];
11097         // [HGM] variantswitch: make engine aware of new variant
11098         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11099                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11100         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11101         SendToProgram(buf, cps);
11102         currentlyInitializedVariant = gameInfo.variant;
11103     }
11104     SendToProgram("force\n", cps);
11105     if (startedFromSetupPosition) {
11106         SendBoard(cps, backwardMostMove);
11107     if (appData.debugMode) {
11108         fprintf(debugFP, "feedMoves\n");
11109     }
11110     }
11111     for (i = backwardMostMove; i < upto; i++) {
11112         SendMoveToProgram(i, cps);
11113     }
11114 }
11115
11116
11117 int
11118 ResurrectChessProgram ()
11119 {
11120      /* The chess program may have exited.
11121         If so, restart it and feed it all the moves made so far. */
11122     static int doInit = 0;
11123
11124     if (appData.noChessProgram) return 1;
11125
11126     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11127         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11128         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11129         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11130     } else {
11131         if (first.pr != NoProc) return 1;
11132         StartChessProgram(&first);
11133     }
11134     InitChessProgram(&first, FALSE);
11135     FeedMovesToProgram(&first, currentMove);
11136
11137     if (!first.sendTime) {
11138         /* can't tell gnuchess what its clock should read,
11139            so we bow to its notion. */
11140         ResetClocks();
11141         timeRemaining[0][currentMove] = whiteTimeRemaining;
11142         timeRemaining[1][currentMove] = blackTimeRemaining;
11143     }
11144
11145     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11146                 appData.icsEngineAnalyze) && first.analysisSupport) {
11147       SendToProgram("analyze\n", &first);
11148       first.analyzing = TRUE;
11149     }
11150     return 1;
11151 }
11152
11153 /*
11154  * Button procedures
11155  */
11156 void
11157 Reset (int redraw, int init)
11158 {
11159     int i;
11160
11161     if (appData.debugMode) {
11162         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11163                 redraw, init, gameMode);
11164     }
11165     CleanupTail(); // [HGM] vari: delete any stored variations
11166     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11167     pausing = pauseExamInvalid = FALSE;
11168     startedFromSetupPosition = blackPlaysFirst = FALSE;
11169     firstMove = TRUE;
11170     whiteFlag = blackFlag = FALSE;
11171     userOfferedDraw = FALSE;
11172     hintRequested = bookRequested = FALSE;
11173     first.maybeThinking = FALSE;
11174     second.maybeThinking = FALSE;
11175     first.bookSuspend = FALSE; // [HGM] book
11176     second.bookSuspend = FALSE;
11177     thinkOutput[0] = NULLCHAR;
11178     lastHint[0] = NULLCHAR;
11179     ClearGameInfo(&gameInfo);
11180     gameInfo.variant = StringToVariant(appData.variant);
11181     ics_user_moved = ics_clock_paused = FALSE;
11182     ics_getting_history = H_FALSE;
11183     ics_gamenum = -1;
11184     white_holding[0] = black_holding[0] = NULLCHAR;
11185     ClearProgramStats();
11186     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11187
11188     ResetFrontEnd();
11189     ClearHighlights();
11190     flipView = appData.flipView;
11191     ClearPremoveHighlights();
11192     gotPremove = FALSE;
11193     alarmSounded = FALSE;
11194
11195     GameEnds(EndOfFile, NULL, GE_PLAYER);
11196     if(appData.serverMovesName != NULL) {
11197         /* [HGM] prepare to make moves file for broadcasting */
11198         clock_t t = clock();
11199         if(serverMoves != NULL) fclose(serverMoves);
11200         serverMoves = fopen(appData.serverMovesName, "r");
11201         if(serverMoves != NULL) {
11202             fclose(serverMoves);
11203             /* delay 15 sec before overwriting, so all clients can see end */
11204             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11205         }
11206         serverMoves = fopen(appData.serverMovesName, "w");
11207     }
11208
11209     ExitAnalyzeMode();
11210     gameMode = BeginningOfGame;
11211     ModeHighlight();
11212     if(appData.icsActive) gameInfo.variant = VariantNormal;
11213     currentMove = forwardMostMove = backwardMostMove = 0;
11214     MarkTargetSquares(1);
11215     InitPosition(redraw);
11216     for (i = 0; i < MAX_MOVES; i++) {
11217         if (commentList[i] != NULL) {
11218             free(commentList[i]);
11219             commentList[i] = NULL;
11220         }
11221     }
11222     ResetClocks();
11223     timeRemaining[0][0] = whiteTimeRemaining;
11224     timeRemaining[1][0] = blackTimeRemaining;
11225
11226     if (first.pr == NoProc) {
11227         StartChessProgram(&first);
11228     }
11229     if (init) {
11230             InitChessProgram(&first, startedFromSetupPosition);
11231     }
11232     DisplayTitle("");
11233     DisplayMessage("", "");
11234     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11235     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11236     ClearMap();        // [HGM] exclude: invalidate map
11237 }
11238
11239 void
11240 AutoPlayGameLoop ()
11241 {
11242     for (;;) {
11243         if (!AutoPlayOneMove())
11244           return;
11245         if (matchMode || appData.timeDelay == 0)
11246           continue;
11247         if (appData.timeDelay < 0)
11248           return;
11249         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11250         break;
11251     }
11252 }
11253
11254 void
11255 AnalyzeNextGame()
11256 {
11257     ReloadGame(1); // next game
11258 }
11259
11260 int
11261 AutoPlayOneMove ()
11262 {
11263     int fromX, fromY, toX, toY;
11264
11265     if (appData.debugMode) {
11266       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11267     }
11268
11269     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11270       return FALSE;
11271
11272     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11273       pvInfoList[currentMove].depth = programStats.depth;
11274       pvInfoList[currentMove].score = programStats.score;
11275       pvInfoList[currentMove].time  = 0;
11276       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11277       else { // append analysis of final position as comment
11278         char buf[MSG_SIZ];
11279         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11280         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11281       }
11282       programStats.depth = 0;
11283     }
11284
11285     if (currentMove >= forwardMostMove) {
11286       if(gameMode == AnalyzeFile) {
11287           if(appData.loadGameIndex == -1) {
11288             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11289           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11290           } else {
11291           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11292         }
11293       }
11294 //      gameMode = EndOfGame;
11295 //      ModeHighlight();
11296
11297       /* [AS] Clear current move marker at the end of a game */
11298       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11299
11300       return FALSE;
11301     }
11302
11303     toX = moveList[currentMove][2] - AAA;
11304     toY = moveList[currentMove][3] - ONE;
11305
11306     if (moveList[currentMove][1] == '@') {
11307         if (appData.highlightLastMove) {
11308             SetHighlights(-1, -1, toX, toY);
11309         }
11310     } else {
11311         fromX = moveList[currentMove][0] - AAA;
11312         fromY = moveList[currentMove][1] - ONE;
11313
11314         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11315
11316         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11317
11318         if (appData.highlightLastMove) {
11319             SetHighlights(fromX, fromY, toX, toY);
11320         }
11321     }
11322     DisplayMove(currentMove);
11323     SendMoveToProgram(currentMove++, &first);
11324     DisplayBothClocks();
11325     DrawPosition(FALSE, boards[currentMove]);
11326     // [HGM] PV info: always display, routine tests if empty
11327     DisplayComment(currentMove - 1, commentList[currentMove]);
11328     return TRUE;
11329 }
11330
11331
11332 int
11333 LoadGameOneMove (ChessMove readAhead)
11334 {
11335     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11336     char promoChar = NULLCHAR;
11337     ChessMove moveType;
11338     char move[MSG_SIZ];
11339     char *p, *q;
11340
11341     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11342         gameMode != AnalyzeMode && gameMode != Training) {
11343         gameFileFP = NULL;
11344         return FALSE;
11345     }
11346
11347     yyboardindex = forwardMostMove;
11348     if (readAhead != EndOfFile) {
11349       moveType = readAhead;
11350     } else {
11351       if (gameFileFP == NULL)
11352           return FALSE;
11353       moveType = (ChessMove) Myylex();
11354     }
11355
11356     done = FALSE;
11357     switch (moveType) {
11358       case Comment:
11359         if (appData.debugMode)
11360           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11361         p = yy_text;
11362
11363         /* append the comment but don't display it */
11364         AppendComment(currentMove, p, FALSE);
11365         return TRUE;
11366
11367       case WhiteCapturesEnPassant:
11368       case BlackCapturesEnPassant:
11369       case WhitePromotion:
11370       case BlackPromotion:
11371       case WhiteNonPromotion:
11372       case BlackNonPromotion:
11373       case NormalMove:
11374       case WhiteKingSideCastle:
11375       case WhiteQueenSideCastle:
11376       case BlackKingSideCastle:
11377       case BlackQueenSideCastle:
11378       case WhiteKingSideCastleWild:
11379       case WhiteQueenSideCastleWild:
11380       case BlackKingSideCastleWild:
11381       case BlackQueenSideCastleWild:
11382       /* PUSH Fabien */
11383       case WhiteHSideCastleFR:
11384       case WhiteASideCastleFR:
11385       case BlackHSideCastleFR:
11386       case BlackASideCastleFR:
11387       /* POP Fabien */
11388         if (appData.debugMode)
11389           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11390         fromX = currentMoveString[0] - AAA;
11391         fromY = currentMoveString[1] - ONE;
11392         toX = currentMoveString[2] - AAA;
11393         toY = currentMoveString[3] - ONE;
11394         promoChar = currentMoveString[4];
11395         break;
11396
11397       case WhiteDrop:
11398       case BlackDrop:
11399         if (appData.debugMode)
11400           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11401         fromX = moveType == WhiteDrop ?
11402           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11403         (int) CharToPiece(ToLower(currentMoveString[0]));
11404         fromY = DROP_RANK;
11405         toX = currentMoveString[2] - AAA;
11406         toY = currentMoveString[3] - ONE;
11407         break;
11408
11409       case WhiteWins:
11410       case BlackWins:
11411       case GameIsDrawn:
11412       case GameUnfinished:
11413         if (appData.debugMode)
11414           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11415         p = strchr(yy_text, '{');
11416         if (p == NULL) p = strchr(yy_text, '(');
11417         if (p == NULL) {
11418             p = yy_text;
11419             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11420         } else {
11421             q = strchr(p, *p == '{' ? '}' : ')');
11422             if (q != NULL) *q = NULLCHAR;
11423             p++;
11424         }
11425         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11426         GameEnds(moveType, p, GE_FILE);
11427         done = TRUE;
11428         if (cmailMsgLoaded) {
11429             ClearHighlights();
11430             flipView = WhiteOnMove(currentMove);
11431             if (moveType == GameUnfinished) flipView = !flipView;
11432             if (appData.debugMode)
11433               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11434         }
11435         break;
11436
11437       case EndOfFile:
11438         if (appData.debugMode)
11439           fprintf(debugFP, "Parser hit end of file\n");
11440         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11441           case MT_NONE:
11442           case MT_CHECK:
11443             break;
11444           case MT_CHECKMATE:
11445           case MT_STAINMATE:
11446             if (WhiteOnMove(currentMove)) {
11447                 GameEnds(BlackWins, "Black mates", GE_FILE);
11448             } else {
11449                 GameEnds(WhiteWins, "White mates", GE_FILE);
11450             }
11451             break;
11452           case MT_STALEMATE:
11453             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11454             break;
11455         }
11456         done = TRUE;
11457         break;
11458
11459       case MoveNumberOne:
11460         if (lastLoadGameStart == GNUChessGame) {
11461             /* GNUChessGames have numbers, but they aren't move numbers */
11462             if (appData.debugMode)
11463               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11464                       yy_text, (int) moveType);
11465             return LoadGameOneMove(EndOfFile); /* tail recursion */
11466         }
11467         /* else fall thru */
11468
11469       case XBoardGame:
11470       case GNUChessGame:
11471       case PGNTag:
11472         /* Reached start of next game in file */
11473         if (appData.debugMode)
11474           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11475         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11476           case MT_NONE:
11477           case MT_CHECK:
11478             break;
11479           case MT_CHECKMATE:
11480           case MT_STAINMATE:
11481             if (WhiteOnMove(currentMove)) {
11482                 GameEnds(BlackWins, "Black mates", GE_FILE);
11483             } else {
11484                 GameEnds(WhiteWins, "White mates", GE_FILE);
11485             }
11486             break;
11487           case MT_STALEMATE:
11488             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11489             break;
11490         }
11491         done = TRUE;
11492         break;
11493
11494       case PositionDiagram:     /* should not happen; ignore */
11495       case ElapsedTime:         /* ignore */
11496       case NAG:                 /* ignore */
11497         if (appData.debugMode)
11498           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11499                   yy_text, (int) moveType);
11500         return LoadGameOneMove(EndOfFile); /* tail recursion */
11501
11502       case IllegalMove:
11503         if (appData.testLegality) {
11504             if (appData.debugMode)
11505               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11506             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11507                     (forwardMostMove / 2) + 1,
11508                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11509             DisplayError(move, 0);
11510             done = TRUE;
11511         } else {
11512             if (appData.debugMode)
11513               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11514                       yy_text, currentMoveString);
11515             fromX = currentMoveString[0] - AAA;
11516             fromY = currentMoveString[1] - ONE;
11517             toX = currentMoveString[2] - AAA;
11518             toY = currentMoveString[3] - ONE;
11519             promoChar = currentMoveString[4];
11520         }
11521         break;
11522
11523       case AmbiguousMove:
11524         if (appData.debugMode)
11525           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11526         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11527                 (forwardMostMove / 2) + 1,
11528                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11529         DisplayError(move, 0);
11530         done = TRUE;
11531         break;
11532
11533       default:
11534       case ImpossibleMove:
11535         if (appData.debugMode)
11536           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11537         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11538                 (forwardMostMove / 2) + 1,
11539                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11540         DisplayError(move, 0);
11541         done = TRUE;
11542         break;
11543     }
11544
11545     if (done) {
11546         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11547             DrawPosition(FALSE, boards[currentMove]);
11548             DisplayBothClocks();
11549             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11550               DisplayComment(currentMove - 1, commentList[currentMove]);
11551         }
11552         (void) StopLoadGameTimer();
11553         gameFileFP = NULL;
11554         cmailOldMove = forwardMostMove;
11555         return FALSE;
11556     } else {
11557         /* currentMoveString is set as a side-effect of yylex */
11558
11559         thinkOutput[0] = NULLCHAR;
11560         MakeMove(fromX, fromY, toX, toY, promoChar);
11561         currentMove = forwardMostMove;
11562         return TRUE;
11563     }
11564 }
11565
11566 /* Load the nth game from the given file */
11567 int
11568 LoadGameFromFile (char *filename, int n, char *title, int useList)
11569 {
11570     FILE *f;
11571     char buf[MSG_SIZ];
11572
11573     if (strcmp(filename, "-") == 0) {
11574         f = stdin;
11575         title = "stdin";
11576     } else {
11577         f = fopen(filename, "rb");
11578         if (f == NULL) {
11579           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11580             DisplayError(buf, errno);
11581             return FALSE;
11582         }
11583     }
11584     if (fseek(f, 0, 0) == -1) {
11585         /* f is not seekable; probably a pipe */
11586         useList = FALSE;
11587     }
11588     if (useList && n == 0) {
11589         int error = GameListBuild(f);
11590         if (error) {
11591             DisplayError(_("Cannot build game list"), error);
11592         } else if (!ListEmpty(&gameList) &&
11593                    ((ListGame *) gameList.tailPred)->number > 1) {
11594             GameListPopUp(f, title);
11595             return TRUE;
11596         }
11597         GameListDestroy();
11598         n = 1;
11599     }
11600     if (n == 0) n = 1;
11601     return LoadGame(f, n, title, FALSE);
11602 }
11603
11604
11605 void
11606 MakeRegisteredMove ()
11607 {
11608     int fromX, fromY, toX, toY;
11609     char promoChar;
11610     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11611         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11612           case CMAIL_MOVE:
11613           case CMAIL_DRAW:
11614             if (appData.debugMode)
11615               fprintf(debugFP, "Restoring %s for game %d\n",
11616                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11617
11618             thinkOutput[0] = NULLCHAR;
11619             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11620             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11621             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11622             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11623             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11624             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11625             MakeMove(fromX, fromY, toX, toY, promoChar);
11626             ShowMove(fromX, fromY, toX, toY);
11627
11628             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11629               case MT_NONE:
11630               case MT_CHECK:
11631                 break;
11632
11633               case MT_CHECKMATE:
11634               case MT_STAINMATE:
11635                 if (WhiteOnMove(currentMove)) {
11636                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11637                 } else {
11638                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11639                 }
11640                 break;
11641
11642               case MT_STALEMATE:
11643                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11644                 break;
11645             }
11646
11647             break;
11648
11649           case CMAIL_RESIGN:
11650             if (WhiteOnMove(currentMove)) {
11651                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11652             } else {
11653                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11654             }
11655             break;
11656
11657           case CMAIL_ACCEPT:
11658             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11659             break;
11660
11661           default:
11662             break;
11663         }
11664     }
11665
11666     return;
11667 }
11668
11669 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11670 int
11671 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11672 {
11673     int retVal;
11674
11675     if (gameNumber > nCmailGames) {
11676         DisplayError(_("No more games in this message"), 0);
11677         return FALSE;
11678     }
11679     if (f == lastLoadGameFP) {
11680         int offset = gameNumber - lastLoadGameNumber;
11681         if (offset == 0) {
11682             cmailMsg[0] = NULLCHAR;
11683             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11684                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11685                 nCmailMovesRegistered--;
11686             }
11687             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11688             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11689                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11690             }
11691         } else {
11692             if (! RegisterMove()) return FALSE;
11693         }
11694     }
11695
11696     retVal = LoadGame(f, gameNumber, title, useList);
11697
11698     /* Make move registered during previous look at this game, if any */
11699     MakeRegisteredMove();
11700
11701     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11702         commentList[currentMove]
11703           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11704         DisplayComment(currentMove - 1, commentList[currentMove]);
11705     }
11706
11707     return retVal;
11708 }
11709
11710 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11711 int
11712 ReloadGame (int offset)
11713 {
11714     int gameNumber = lastLoadGameNumber + offset;
11715     if (lastLoadGameFP == NULL) {
11716         DisplayError(_("No game has been loaded yet"), 0);
11717         return FALSE;
11718     }
11719     if (gameNumber <= 0) {
11720         DisplayError(_("Can't back up any further"), 0);
11721         return FALSE;
11722     }
11723     if (cmailMsgLoaded) {
11724         return CmailLoadGame(lastLoadGameFP, gameNumber,
11725                              lastLoadGameTitle, lastLoadGameUseList);
11726     } else {
11727         return LoadGame(lastLoadGameFP, gameNumber,
11728                         lastLoadGameTitle, lastLoadGameUseList);
11729     }
11730 }
11731
11732 int keys[EmptySquare+1];
11733
11734 int
11735 PositionMatches (Board b1, Board b2)
11736 {
11737     int r, f, sum=0;
11738     switch(appData.searchMode) {
11739         case 1: return CompareWithRights(b1, b2);
11740         case 2:
11741             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11742                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11743             }
11744             return TRUE;
11745         case 3:
11746             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11747               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11748                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11749             }
11750             return sum==0;
11751         case 4:
11752             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11753                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11754             }
11755             return sum==0;
11756     }
11757     return TRUE;
11758 }
11759
11760 #define Q_PROMO  4
11761 #define Q_EP     3
11762 #define Q_BCASTL 2
11763 #define Q_WCASTL 1
11764
11765 int pieceList[256], quickBoard[256];
11766 ChessSquare pieceType[256] = { EmptySquare };
11767 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11768 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11769 int soughtTotal, turn;
11770 Boolean epOK, flipSearch;
11771
11772 typedef struct {
11773     unsigned char piece, to;
11774 } Move;
11775
11776 #define DSIZE (250000)
11777
11778 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11779 Move *moveDatabase = initialSpace;
11780 unsigned int movePtr, dataSize = DSIZE;
11781
11782 int
11783 MakePieceList (Board board, int *counts)
11784 {
11785     int r, f, n=Q_PROMO, total=0;
11786     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11787     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11788         int sq = f + (r<<4);
11789         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11790             quickBoard[sq] = ++n;
11791             pieceList[n] = sq;
11792             pieceType[n] = board[r][f];
11793             counts[board[r][f]]++;
11794             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11795             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11796             total++;
11797         }
11798     }
11799     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11800     return total;
11801 }
11802
11803 void
11804 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11805 {
11806     int sq = fromX + (fromY<<4);
11807     int piece = quickBoard[sq];
11808     quickBoard[sq] = 0;
11809     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11810     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11811         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11812         moveDatabase[movePtr++].piece = Q_WCASTL;
11813         quickBoard[sq] = piece;
11814         piece = quickBoard[from]; quickBoard[from] = 0;
11815         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11816     } else
11817     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11818         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11819         moveDatabase[movePtr++].piece = Q_BCASTL;
11820         quickBoard[sq] = piece;
11821         piece = quickBoard[from]; quickBoard[from] = 0;
11822         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11823     } else
11824     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11825         quickBoard[(fromY<<4)+toX] = 0;
11826         moveDatabase[movePtr].piece = Q_EP;
11827         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11828         moveDatabase[movePtr].to = sq;
11829     } else
11830     if(promoPiece != pieceType[piece]) {
11831         moveDatabase[movePtr++].piece = Q_PROMO;
11832         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11833     }
11834     moveDatabase[movePtr].piece = piece;
11835     quickBoard[sq] = piece;
11836     movePtr++;
11837 }
11838
11839 int
11840 PackGame (Board board)
11841 {
11842     Move *newSpace = NULL;
11843     moveDatabase[movePtr].piece = 0; // terminate previous game
11844     if(movePtr > dataSize) {
11845         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11846         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11847         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11848         if(newSpace) {
11849             int i;
11850             Move *p = moveDatabase, *q = newSpace;
11851             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11852             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11853             moveDatabase = newSpace;
11854         } else { // calloc failed, we must be out of memory. Too bad...
11855             dataSize = 0; // prevent calloc events for all subsequent games
11856             return 0;     // and signal this one isn't cached
11857         }
11858     }
11859     movePtr++;
11860     MakePieceList(board, counts);
11861     return movePtr;
11862 }
11863
11864 int
11865 QuickCompare (Board board, int *minCounts, int *maxCounts)
11866 {   // compare according to search mode
11867     int r, f;
11868     switch(appData.searchMode)
11869     {
11870       case 1: // exact position match
11871         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11872         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11873             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11874         }
11875         break;
11876       case 2: // can have extra material on empty squares
11877         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11878             if(board[r][f] == EmptySquare) continue;
11879             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11880         }
11881         break;
11882       case 3: // material with exact Pawn structure
11883         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11884             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11885             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11886         } // fall through to material comparison
11887       case 4: // exact material
11888         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11889         break;
11890       case 6: // material range with given imbalance
11891         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11892         // fall through to range comparison
11893       case 5: // material range
11894         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11895     }
11896     return TRUE;
11897 }
11898
11899 int
11900 QuickScan (Board board, Move *move)
11901 {   // reconstruct game,and compare all positions in it
11902     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11903     do {
11904         int piece = move->piece;
11905         int to = move->to, from = pieceList[piece];
11906         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11907           if(!piece) return -1;
11908           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11909             piece = (++move)->piece;
11910             from = pieceList[piece];
11911             counts[pieceType[piece]]--;
11912             pieceType[piece] = (ChessSquare) move->to;
11913             counts[move->to]++;
11914           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11915             counts[pieceType[quickBoard[to]]]--;
11916             quickBoard[to] = 0; total--;
11917             move++;
11918             continue;
11919           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11920             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11921             from  = pieceList[piece]; // so this must be King
11922             quickBoard[from] = 0;
11923             pieceList[piece] = to;
11924             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11925             quickBoard[from] = 0; // rook
11926             quickBoard[to] = piece;
11927             to = move->to; piece = move->piece;
11928             goto aftercastle;
11929           }
11930         }
11931         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11932         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11933         quickBoard[from] = 0;
11934       aftercastle:
11935         quickBoard[to] = piece;
11936         pieceList[piece] = to;
11937         cnt++; turn ^= 3;
11938         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11939            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11940            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11941                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11942           ) {
11943             static int lastCounts[EmptySquare+1];
11944             int i;
11945             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11946             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11947         } else stretch = 0;
11948         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11949         move++;
11950     } while(1);
11951 }
11952
11953 void
11954 InitSearch ()
11955 {
11956     int r, f;
11957     flipSearch = FALSE;
11958     CopyBoard(soughtBoard, boards[currentMove]);
11959     soughtTotal = MakePieceList(soughtBoard, maxSought);
11960     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11961     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11962     CopyBoard(reverseBoard, boards[currentMove]);
11963     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11964         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11965         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11966         reverseBoard[r][f] = piece;
11967     }
11968     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11969     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11970     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11971                  || (boards[currentMove][CASTLING][2] == NoRights ||
11972                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11973                  && (boards[currentMove][CASTLING][5] == NoRights ||
11974                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11975       ) {
11976         flipSearch = TRUE;
11977         CopyBoard(flipBoard, soughtBoard);
11978         CopyBoard(rotateBoard, reverseBoard);
11979         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11980             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11981             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11982         }
11983     }
11984     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11985     if(appData.searchMode >= 5) {
11986         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11987         MakePieceList(soughtBoard, minSought);
11988         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11989     }
11990     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11991         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11992 }
11993
11994 GameInfo dummyInfo;
11995 static int creatingBook;
11996
11997 int
11998 GameContainsPosition (FILE *f, ListGame *lg)
11999 {
12000     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12001     int fromX, fromY, toX, toY;
12002     char promoChar;
12003     static int initDone=FALSE;
12004
12005     // weed out games based on numerical tag comparison
12006     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12007     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12008     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12009     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12010     if(!initDone) {
12011         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12012         initDone = TRUE;
12013     }
12014     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12015     else CopyBoard(boards[scratch], initialPosition); // default start position
12016     if(lg->moves) {
12017         turn = btm + 1;
12018         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12019         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12020     }
12021     if(btm) plyNr++;
12022     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12023     fseek(f, lg->offset, 0);
12024     yynewfile(f);
12025     while(1) {
12026         yyboardindex = scratch;
12027         quickFlag = plyNr+1;
12028         next = Myylex();
12029         quickFlag = 0;
12030         switch(next) {
12031             case PGNTag:
12032                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12033             default:
12034                 continue;
12035
12036             case XBoardGame:
12037             case GNUChessGame:
12038                 if(plyNr) return -1; // after we have seen moves, this is for new game
12039               continue;
12040
12041             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12042             case ImpossibleMove:
12043             case WhiteWins: // game ends here with these four
12044             case BlackWins:
12045             case GameIsDrawn:
12046             case GameUnfinished:
12047                 return -1;
12048
12049             case IllegalMove:
12050                 if(appData.testLegality) return -1;
12051             case WhiteCapturesEnPassant:
12052             case BlackCapturesEnPassant:
12053             case WhitePromotion:
12054             case BlackPromotion:
12055             case WhiteNonPromotion:
12056             case BlackNonPromotion:
12057             case NormalMove:
12058             case WhiteKingSideCastle:
12059             case WhiteQueenSideCastle:
12060             case BlackKingSideCastle:
12061             case BlackQueenSideCastle:
12062             case WhiteKingSideCastleWild:
12063             case WhiteQueenSideCastleWild:
12064             case BlackKingSideCastleWild:
12065             case BlackQueenSideCastleWild:
12066             case WhiteHSideCastleFR:
12067             case WhiteASideCastleFR:
12068             case BlackHSideCastleFR:
12069             case BlackASideCastleFR:
12070                 fromX = currentMoveString[0] - AAA;
12071                 fromY = currentMoveString[1] - ONE;
12072                 toX = currentMoveString[2] - AAA;
12073                 toY = currentMoveString[3] - ONE;
12074                 promoChar = currentMoveString[4];
12075                 break;
12076             case WhiteDrop:
12077             case BlackDrop:
12078                 fromX = next == WhiteDrop ?
12079                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12080                   (int) CharToPiece(ToLower(currentMoveString[0]));
12081                 fromY = DROP_RANK;
12082                 toX = currentMoveString[2] - AAA;
12083                 toY = currentMoveString[3] - ONE;
12084                 promoChar = 0;
12085                 break;
12086         }
12087         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12088         plyNr++;
12089         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12090         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12091         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12092         if(appData.findMirror) {
12093             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12094             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12095         }
12096     }
12097 }
12098
12099 /* Load the nth game from open file f */
12100 int
12101 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12102 {
12103     ChessMove cm;
12104     char buf[MSG_SIZ];
12105     int gn = gameNumber;
12106     ListGame *lg = NULL;
12107     int numPGNTags = 0;
12108     int err, pos = -1;
12109     GameMode oldGameMode;
12110     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12111
12112     if (appData.debugMode)
12113         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12114
12115     if (gameMode == Training )
12116         SetTrainingModeOff();
12117
12118     oldGameMode = gameMode;
12119     if (gameMode != BeginningOfGame) {
12120       Reset(FALSE, TRUE);
12121     }
12122
12123     gameFileFP = f;
12124     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12125         fclose(lastLoadGameFP);
12126     }
12127
12128     if (useList) {
12129         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12130
12131         if (lg) {
12132             fseek(f, lg->offset, 0);
12133             GameListHighlight(gameNumber);
12134             pos = lg->position;
12135             gn = 1;
12136         }
12137         else {
12138             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12139               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12140             else
12141             DisplayError(_("Game number out of range"), 0);
12142             return FALSE;
12143         }
12144     } else {
12145         GameListDestroy();
12146         if (fseek(f, 0, 0) == -1) {
12147             if (f == lastLoadGameFP ?
12148                 gameNumber == lastLoadGameNumber + 1 :
12149                 gameNumber == 1) {
12150                 gn = 1;
12151             } else {
12152                 DisplayError(_("Can't seek on game file"), 0);
12153                 return FALSE;
12154             }
12155         }
12156     }
12157     lastLoadGameFP = f;
12158     lastLoadGameNumber = gameNumber;
12159     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12160     lastLoadGameUseList = useList;
12161
12162     yynewfile(f);
12163
12164     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12165       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12166                 lg->gameInfo.black);
12167             DisplayTitle(buf);
12168     } else if (*title != NULLCHAR) {
12169         if (gameNumber > 1) {
12170           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12171             DisplayTitle(buf);
12172         } else {
12173             DisplayTitle(title);
12174         }
12175     }
12176
12177     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12178         gameMode = PlayFromGameFile;
12179         ModeHighlight();
12180     }
12181
12182     currentMove = forwardMostMove = backwardMostMove = 0;
12183     CopyBoard(boards[0], initialPosition);
12184     StopClocks();
12185
12186     /*
12187      * Skip the first gn-1 games in the file.
12188      * Also skip over anything that precedes an identifiable
12189      * start of game marker, to avoid being confused by
12190      * garbage at the start of the file.  Currently
12191      * recognized start of game markers are the move number "1",
12192      * the pattern "gnuchess .* game", the pattern
12193      * "^[#;%] [^ ]* game file", and a PGN tag block.
12194      * A game that starts with one of the latter two patterns
12195      * will also have a move number 1, possibly
12196      * following a position diagram.
12197      * 5-4-02: Let's try being more lenient and allowing a game to
12198      * start with an unnumbered move.  Does that break anything?
12199      */
12200     cm = lastLoadGameStart = EndOfFile;
12201     while (gn > 0) {
12202         yyboardindex = forwardMostMove;
12203         cm = (ChessMove) Myylex();
12204         switch (cm) {
12205           case EndOfFile:
12206             if (cmailMsgLoaded) {
12207                 nCmailGames = CMAIL_MAX_GAMES - gn;
12208             } else {
12209                 Reset(TRUE, TRUE);
12210                 DisplayError(_("Game not found in file"), 0);
12211             }
12212             return FALSE;
12213
12214           case GNUChessGame:
12215           case XBoardGame:
12216             gn--;
12217             lastLoadGameStart = cm;
12218             break;
12219
12220           case MoveNumberOne:
12221             switch (lastLoadGameStart) {
12222               case GNUChessGame:
12223               case XBoardGame:
12224               case PGNTag:
12225                 break;
12226               case MoveNumberOne:
12227               case EndOfFile:
12228                 gn--;           /* count this game */
12229                 lastLoadGameStart = cm;
12230                 break;
12231               default:
12232                 /* impossible */
12233                 break;
12234             }
12235             break;
12236
12237           case PGNTag:
12238             switch (lastLoadGameStart) {
12239               case GNUChessGame:
12240               case PGNTag:
12241               case MoveNumberOne:
12242               case EndOfFile:
12243                 gn--;           /* count this game */
12244                 lastLoadGameStart = cm;
12245                 break;
12246               case XBoardGame:
12247                 lastLoadGameStart = cm; /* game counted already */
12248                 break;
12249               default:
12250                 /* impossible */
12251                 break;
12252             }
12253             if (gn > 0) {
12254                 do {
12255                     yyboardindex = forwardMostMove;
12256                     cm = (ChessMove) Myylex();
12257                 } while (cm == PGNTag || cm == Comment);
12258             }
12259             break;
12260
12261           case WhiteWins:
12262           case BlackWins:
12263           case GameIsDrawn:
12264             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12265                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12266                     != CMAIL_OLD_RESULT) {
12267                     nCmailResults ++ ;
12268                     cmailResult[  CMAIL_MAX_GAMES
12269                                 - gn - 1] = CMAIL_OLD_RESULT;
12270                 }
12271             }
12272             break;
12273
12274           case NormalMove:
12275             /* Only a NormalMove can be at the start of a game
12276              * without a position diagram. */
12277             if (lastLoadGameStart == EndOfFile ) {
12278               gn--;
12279               lastLoadGameStart = MoveNumberOne;
12280             }
12281             break;
12282
12283           default:
12284             break;
12285         }
12286     }
12287
12288     if (appData.debugMode)
12289       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12290
12291     if (cm == XBoardGame) {
12292         /* Skip any header junk before position diagram and/or move 1 */
12293         for (;;) {
12294             yyboardindex = forwardMostMove;
12295             cm = (ChessMove) Myylex();
12296
12297             if (cm == EndOfFile ||
12298                 cm == GNUChessGame || cm == XBoardGame) {
12299                 /* Empty game; pretend end-of-file and handle later */
12300                 cm = EndOfFile;
12301                 break;
12302             }
12303
12304             if (cm == MoveNumberOne || cm == PositionDiagram ||
12305                 cm == PGNTag || cm == Comment)
12306               break;
12307         }
12308     } else if (cm == GNUChessGame) {
12309         if (gameInfo.event != NULL) {
12310             free(gameInfo.event);
12311         }
12312         gameInfo.event = StrSave(yy_text);
12313     }
12314
12315     startedFromSetupPosition = FALSE;
12316     while (cm == PGNTag) {
12317         if (appData.debugMode)
12318           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12319         err = ParsePGNTag(yy_text, &gameInfo);
12320         if (!err) numPGNTags++;
12321
12322         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12323         if(gameInfo.variant != oldVariant) {
12324             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12325             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12326             InitPosition(TRUE);
12327             oldVariant = gameInfo.variant;
12328             if (appData.debugMode)
12329               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12330         }
12331
12332
12333         if (gameInfo.fen != NULL) {
12334           Board initial_position;
12335           startedFromSetupPosition = TRUE;
12336           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12337             Reset(TRUE, TRUE);
12338             DisplayError(_("Bad FEN position in file"), 0);
12339             return FALSE;
12340           }
12341           CopyBoard(boards[0], initial_position);
12342           if (blackPlaysFirst) {
12343             currentMove = forwardMostMove = backwardMostMove = 1;
12344             CopyBoard(boards[1], initial_position);
12345             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12346             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12347             timeRemaining[0][1] = whiteTimeRemaining;
12348             timeRemaining[1][1] = blackTimeRemaining;
12349             if (commentList[0] != NULL) {
12350               commentList[1] = commentList[0];
12351               commentList[0] = NULL;
12352             }
12353           } else {
12354             currentMove = forwardMostMove = backwardMostMove = 0;
12355           }
12356           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12357           {   int i;
12358               initialRulePlies = FENrulePlies;
12359               for( i=0; i< nrCastlingRights; i++ )
12360                   initialRights[i] = initial_position[CASTLING][i];
12361           }
12362           yyboardindex = forwardMostMove;
12363           free(gameInfo.fen);
12364           gameInfo.fen = NULL;
12365         }
12366
12367         yyboardindex = forwardMostMove;
12368         cm = (ChessMove) Myylex();
12369
12370         /* Handle comments interspersed among the tags */
12371         while (cm == Comment) {
12372             char *p;
12373             if (appData.debugMode)
12374               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12375             p = yy_text;
12376             AppendComment(currentMove, p, FALSE);
12377             yyboardindex = forwardMostMove;
12378             cm = (ChessMove) Myylex();
12379         }
12380     }
12381
12382     /* don't rely on existence of Event tag since if game was
12383      * pasted from clipboard the Event tag may not exist
12384      */
12385     if (numPGNTags > 0){
12386         char *tags;
12387         if (gameInfo.variant == VariantNormal) {
12388           VariantClass v = StringToVariant(gameInfo.event);
12389           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12390           if(v < VariantShogi) gameInfo.variant = v;
12391         }
12392         if (!matchMode) {
12393           if( appData.autoDisplayTags ) {
12394             tags = PGNTags(&gameInfo);
12395             TagsPopUp(tags, CmailMsg());
12396             free(tags);
12397           }
12398         }
12399     } else {
12400         /* Make something up, but don't display it now */
12401         SetGameInfo();
12402         TagsPopDown();
12403     }
12404
12405     if (cm == PositionDiagram) {
12406         int i, j;
12407         char *p;
12408         Board initial_position;
12409
12410         if (appData.debugMode)
12411           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12412
12413         if (!startedFromSetupPosition) {
12414             p = yy_text;
12415             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12416               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12417                 switch (*p) {
12418                   case '{':
12419                   case '[':
12420                   case '-':
12421                   case ' ':
12422                   case '\t':
12423                   case '\n':
12424                   case '\r':
12425                     break;
12426                   default:
12427                     initial_position[i][j++] = CharToPiece(*p);
12428                     break;
12429                 }
12430             while (*p == ' ' || *p == '\t' ||
12431                    *p == '\n' || *p == '\r') p++;
12432
12433             if (strncmp(p, "black", strlen("black"))==0)
12434               blackPlaysFirst = TRUE;
12435             else
12436               blackPlaysFirst = FALSE;
12437             startedFromSetupPosition = TRUE;
12438
12439             CopyBoard(boards[0], initial_position);
12440             if (blackPlaysFirst) {
12441                 currentMove = forwardMostMove = backwardMostMove = 1;
12442                 CopyBoard(boards[1], initial_position);
12443                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12444                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12445                 timeRemaining[0][1] = whiteTimeRemaining;
12446                 timeRemaining[1][1] = blackTimeRemaining;
12447                 if (commentList[0] != NULL) {
12448                     commentList[1] = commentList[0];
12449                     commentList[0] = NULL;
12450                 }
12451             } else {
12452                 currentMove = forwardMostMove = backwardMostMove = 0;
12453             }
12454         }
12455         yyboardindex = forwardMostMove;
12456         cm = (ChessMove) Myylex();
12457     }
12458
12459   if(!creatingBook) {
12460     if (first.pr == NoProc) {
12461         StartChessProgram(&first);
12462     }
12463     InitChessProgram(&first, FALSE);
12464     SendToProgram("force\n", &first);
12465     if (startedFromSetupPosition) {
12466         SendBoard(&first, forwardMostMove);
12467     if (appData.debugMode) {
12468         fprintf(debugFP, "Load Game\n");
12469     }
12470         DisplayBothClocks();
12471     }
12472   }
12473
12474     /* [HGM] server: flag to write setup moves in broadcast file as one */
12475     loadFlag = appData.suppressLoadMoves;
12476
12477     while (cm == Comment) {
12478         char *p;
12479         if (appData.debugMode)
12480           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12481         p = yy_text;
12482         AppendComment(currentMove, p, FALSE);
12483         yyboardindex = forwardMostMove;
12484         cm = (ChessMove) Myylex();
12485     }
12486
12487     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12488         cm == WhiteWins || cm == BlackWins ||
12489         cm == GameIsDrawn || cm == GameUnfinished) {
12490         DisplayMessage("", _("No moves in game"));
12491         if (cmailMsgLoaded) {
12492             if (appData.debugMode)
12493               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12494             ClearHighlights();
12495             flipView = FALSE;
12496         }
12497         DrawPosition(FALSE, boards[currentMove]);
12498         DisplayBothClocks();
12499         gameMode = EditGame;
12500         ModeHighlight();
12501         gameFileFP = NULL;
12502         cmailOldMove = 0;
12503         return TRUE;
12504     }
12505
12506     // [HGM] PV info: routine tests if comment empty
12507     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12508         DisplayComment(currentMove - 1, commentList[currentMove]);
12509     }
12510     if (!matchMode && appData.timeDelay != 0)
12511       DrawPosition(FALSE, boards[currentMove]);
12512
12513     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12514       programStats.ok_to_send = 1;
12515     }
12516
12517     /* if the first token after the PGN tags is a move
12518      * and not move number 1, retrieve it from the parser
12519      */
12520     if (cm != MoveNumberOne)
12521         LoadGameOneMove(cm);
12522
12523     /* load the remaining moves from the file */
12524     while (LoadGameOneMove(EndOfFile)) {
12525       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12526       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12527     }
12528
12529     /* rewind to the start of the game */
12530     currentMove = backwardMostMove;
12531
12532     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12533
12534     if (oldGameMode == AnalyzeFile) {
12535       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12536       AnalyzeFileEvent();
12537     } else
12538     if (oldGameMode == AnalyzeMode) {
12539       AnalyzeFileEvent();
12540     }
12541
12542     if(creatingBook) return TRUE;
12543     if (!matchMode && pos > 0) {
12544         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12545     } else
12546     if (matchMode || appData.timeDelay == 0) {
12547       ToEndEvent();
12548     } else if (appData.timeDelay > 0) {
12549       AutoPlayGameLoop();
12550     }
12551
12552     if (appData.debugMode)
12553         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12554
12555     loadFlag = 0; /* [HGM] true game starts */
12556     return TRUE;
12557 }
12558
12559 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12560 int
12561 ReloadPosition (int offset)
12562 {
12563     int positionNumber = lastLoadPositionNumber + offset;
12564     if (lastLoadPositionFP == NULL) {
12565         DisplayError(_("No position has been loaded yet"), 0);
12566         return FALSE;
12567     }
12568     if (positionNumber <= 0) {
12569         DisplayError(_("Can't back up any further"), 0);
12570         return FALSE;
12571     }
12572     return LoadPosition(lastLoadPositionFP, positionNumber,
12573                         lastLoadPositionTitle);
12574 }
12575
12576 /* Load the nth position from the given file */
12577 int
12578 LoadPositionFromFile (char *filename, int n, char *title)
12579 {
12580     FILE *f;
12581     char buf[MSG_SIZ];
12582
12583     if (strcmp(filename, "-") == 0) {
12584         return LoadPosition(stdin, n, "stdin");
12585     } else {
12586         f = fopen(filename, "rb");
12587         if (f == NULL) {
12588             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12589             DisplayError(buf, errno);
12590             return FALSE;
12591         } else {
12592             return LoadPosition(f, n, title);
12593         }
12594     }
12595 }
12596
12597 /* Load the nth position from the given open file, and close it */
12598 int
12599 LoadPosition (FILE *f, int positionNumber, char *title)
12600 {
12601     char *p, line[MSG_SIZ];
12602     Board initial_position;
12603     int i, j, fenMode, pn;
12604
12605     if (gameMode == Training )
12606         SetTrainingModeOff();
12607
12608     if (gameMode != BeginningOfGame) {
12609         Reset(FALSE, TRUE);
12610     }
12611     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12612         fclose(lastLoadPositionFP);
12613     }
12614     if (positionNumber == 0) positionNumber = 1;
12615     lastLoadPositionFP = f;
12616     lastLoadPositionNumber = positionNumber;
12617     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12618     if (first.pr == NoProc && !appData.noChessProgram) {
12619       StartChessProgram(&first);
12620       InitChessProgram(&first, FALSE);
12621     }
12622     pn = positionNumber;
12623     if (positionNumber < 0) {
12624         /* Negative position number means to seek to that byte offset */
12625         if (fseek(f, -positionNumber, 0) == -1) {
12626             DisplayError(_("Can't seek on position file"), 0);
12627             return FALSE;
12628         };
12629         pn = 1;
12630     } else {
12631         if (fseek(f, 0, 0) == -1) {
12632             if (f == lastLoadPositionFP ?
12633                 positionNumber == lastLoadPositionNumber + 1 :
12634                 positionNumber == 1) {
12635                 pn = 1;
12636             } else {
12637                 DisplayError(_("Can't seek on position file"), 0);
12638                 return FALSE;
12639             }
12640         }
12641     }
12642     /* See if this file is FEN or old-style xboard */
12643     if (fgets(line, MSG_SIZ, f) == NULL) {
12644         DisplayError(_("Position not found in file"), 0);
12645         return FALSE;
12646     }
12647     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12648     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12649
12650     if (pn >= 2) {
12651         if (fenMode || line[0] == '#') pn--;
12652         while (pn > 0) {
12653             /* skip positions before number pn */
12654             if (fgets(line, MSG_SIZ, f) == NULL) {
12655                 Reset(TRUE, TRUE);
12656                 DisplayError(_("Position not found in file"), 0);
12657                 return FALSE;
12658             }
12659             if (fenMode || line[0] == '#') pn--;
12660         }
12661     }
12662
12663     if (fenMode) {
12664         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12665             DisplayError(_("Bad FEN position in file"), 0);
12666             return FALSE;
12667         }
12668     } else {
12669         (void) fgets(line, MSG_SIZ, f);
12670         (void) fgets(line, MSG_SIZ, f);
12671
12672         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12673             (void) fgets(line, MSG_SIZ, f);
12674             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12675                 if (*p == ' ')
12676                   continue;
12677                 initial_position[i][j++] = CharToPiece(*p);
12678             }
12679         }
12680
12681         blackPlaysFirst = FALSE;
12682         if (!feof(f)) {
12683             (void) fgets(line, MSG_SIZ, f);
12684             if (strncmp(line, "black", strlen("black"))==0)
12685               blackPlaysFirst = TRUE;
12686         }
12687     }
12688     startedFromSetupPosition = TRUE;
12689
12690     CopyBoard(boards[0], initial_position);
12691     if (blackPlaysFirst) {
12692         currentMove = forwardMostMove = backwardMostMove = 1;
12693         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12694         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12695         CopyBoard(boards[1], initial_position);
12696         DisplayMessage("", _("Black to play"));
12697     } else {
12698         currentMove = forwardMostMove = backwardMostMove = 0;
12699         DisplayMessage("", _("White to play"));
12700     }
12701     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12702     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12703         SendToProgram("force\n", &first);
12704         SendBoard(&first, forwardMostMove);
12705     }
12706     if (appData.debugMode) {
12707 int i, j;
12708   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12709   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12710         fprintf(debugFP, "Load Position\n");
12711     }
12712
12713     if (positionNumber > 1) {
12714       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12715         DisplayTitle(line);
12716     } else {
12717         DisplayTitle(title);
12718     }
12719     gameMode = EditGame;
12720     ModeHighlight();
12721     ResetClocks();
12722     timeRemaining[0][1] = whiteTimeRemaining;
12723     timeRemaining[1][1] = blackTimeRemaining;
12724     DrawPosition(FALSE, boards[currentMove]);
12725
12726     return TRUE;
12727 }
12728
12729
12730 void
12731 CopyPlayerNameIntoFileName (char **dest, char *src)
12732 {
12733     while (*src != NULLCHAR && *src != ',') {
12734         if (*src == ' ') {
12735             *(*dest)++ = '_';
12736             src++;
12737         } else {
12738             *(*dest)++ = *src++;
12739         }
12740     }
12741 }
12742
12743 char *
12744 DefaultFileName (char *ext)
12745 {
12746     static char def[MSG_SIZ];
12747     char *p;
12748
12749     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12750         p = def;
12751         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12752         *p++ = '-';
12753         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12754         *p++ = '.';
12755         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12756     } else {
12757         def[0] = NULLCHAR;
12758     }
12759     return def;
12760 }
12761
12762 /* Save the current game to the given file */
12763 int
12764 SaveGameToFile (char *filename, int append)
12765 {
12766     FILE *f;
12767     char buf[MSG_SIZ];
12768     int result, i, t,tot=0;
12769
12770     if (strcmp(filename, "-") == 0) {
12771         return SaveGame(stdout, 0, NULL);
12772     } else {
12773         for(i=0; i<10; i++) { // upto 10 tries
12774              f = fopen(filename, append ? "a" : "w");
12775              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12776              if(f || errno != 13) break;
12777              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12778              tot += t;
12779         }
12780         if (f == NULL) {
12781             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12782             DisplayError(buf, errno);
12783             return FALSE;
12784         } else {
12785             safeStrCpy(buf, lastMsg, MSG_SIZ);
12786             DisplayMessage(_("Waiting for access to save file"), "");
12787             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12788             DisplayMessage(_("Saving game"), "");
12789             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12790             result = SaveGame(f, 0, NULL);
12791             DisplayMessage(buf, "");
12792             return result;
12793         }
12794     }
12795 }
12796
12797 char *
12798 SavePart (char *str)
12799 {
12800     static char buf[MSG_SIZ];
12801     char *p;
12802
12803     p = strchr(str, ' ');
12804     if (p == NULL) return str;
12805     strncpy(buf, str, p - str);
12806     buf[p - str] = NULLCHAR;
12807     return buf;
12808 }
12809
12810 #define PGN_MAX_LINE 75
12811
12812 #define PGN_SIDE_WHITE  0
12813 #define PGN_SIDE_BLACK  1
12814
12815 static int
12816 FindFirstMoveOutOfBook (int side)
12817 {
12818     int result = -1;
12819
12820     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12821         int index = backwardMostMove;
12822         int has_book_hit = 0;
12823
12824         if( (index % 2) != side ) {
12825             index++;
12826         }
12827
12828         while( index < forwardMostMove ) {
12829             /* Check to see if engine is in book */
12830             int depth = pvInfoList[index].depth;
12831             int score = pvInfoList[index].score;
12832             int in_book = 0;
12833
12834             if( depth <= 2 ) {
12835                 in_book = 1;
12836             }
12837             else if( score == 0 && depth == 63 ) {
12838                 in_book = 1; /* Zappa */
12839             }
12840             else if( score == 2 && depth == 99 ) {
12841                 in_book = 1; /* Abrok */
12842             }
12843
12844             has_book_hit += in_book;
12845
12846             if( ! in_book ) {
12847                 result = index;
12848
12849                 break;
12850             }
12851
12852             index += 2;
12853         }
12854     }
12855
12856     return result;
12857 }
12858
12859 void
12860 GetOutOfBookInfo (char * buf)
12861 {
12862     int oob[2];
12863     int i;
12864     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12865
12866     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12867     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12868
12869     *buf = '\0';
12870
12871     if( oob[0] >= 0 || oob[1] >= 0 ) {
12872         for( i=0; i<2; i++ ) {
12873             int idx = oob[i];
12874
12875             if( idx >= 0 ) {
12876                 if( i > 0 && oob[0] >= 0 ) {
12877                     strcat( buf, "   " );
12878                 }
12879
12880                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12881                 sprintf( buf+strlen(buf), "%s%.2f",
12882                     pvInfoList[idx].score >= 0 ? "+" : "",
12883                     pvInfoList[idx].score / 100.0 );
12884             }
12885         }
12886     }
12887 }
12888
12889 /* Save game in PGN style and close the file */
12890 int
12891 SaveGamePGN (FILE *f)
12892 {
12893     int i, offset, linelen, newblock;
12894 //    char *movetext;
12895     char numtext[32];
12896     int movelen, numlen, blank;
12897     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12898
12899     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12900
12901     PrintPGNTags(f, &gameInfo);
12902
12903     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12904
12905     if (backwardMostMove > 0 || startedFromSetupPosition) {
12906         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12907         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12908         fprintf(f, "\n{--------------\n");
12909         PrintPosition(f, backwardMostMove);
12910         fprintf(f, "--------------}\n");
12911         free(fen);
12912     }
12913     else {
12914         /* [AS] Out of book annotation */
12915         if( appData.saveOutOfBookInfo ) {
12916             char buf[64];
12917
12918             GetOutOfBookInfo( buf );
12919
12920             if( buf[0] != '\0' ) {
12921                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12922             }
12923         }
12924
12925         fprintf(f, "\n");
12926     }
12927
12928     i = backwardMostMove;
12929     linelen = 0;
12930     newblock = TRUE;
12931
12932     while (i < forwardMostMove) {
12933         /* Print comments preceding this move */
12934         if (commentList[i] != NULL) {
12935             if (linelen > 0) fprintf(f, "\n");
12936             fprintf(f, "%s", commentList[i]);
12937             linelen = 0;
12938             newblock = TRUE;
12939         }
12940
12941         /* Format move number */
12942         if ((i % 2) == 0)
12943           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12944         else
12945           if (newblock)
12946             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12947           else
12948             numtext[0] = NULLCHAR;
12949
12950         numlen = strlen(numtext);
12951         newblock = FALSE;
12952
12953         /* Print move number */
12954         blank = linelen > 0 && numlen > 0;
12955         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12956             fprintf(f, "\n");
12957             linelen = 0;
12958             blank = 0;
12959         }
12960         if (blank) {
12961             fprintf(f, " ");
12962             linelen++;
12963         }
12964         fprintf(f, "%s", numtext);
12965         linelen += numlen;
12966
12967         /* Get move */
12968         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12969         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12970
12971         /* Print move */
12972         blank = linelen > 0 && movelen > 0;
12973         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12974             fprintf(f, "\n");
12975             linelen = 0;
12976             blank = 0;
12977         }
12978         if (blank) {
12979             fprintf(f, " ");
12980             linelen++;
12981         }
12982         fprintf(f, "%s", move_buffer);
12983         linelen += movelen;
12984
12985         /* [AS] Add PV info if present */
12986         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12987             /* [HGM] add time */
12988             char buf[MSG_SIZ]; int seconds;
12989
12990             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12991
12992             if( seconds <= 0)
12993               buf[0] = 0;
12994             else
12995               if( seconds < 30 )
12996                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12997               else
12998                 {
12999                   seconds = (seconds + 4)/10; // round to full seconds
13000                   if( seconds < 60 )
13001                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13002                   else
13003                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13004                 }
13005
13006             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13007                       pvInfoList[i].score >= 0 ? "+" : "",
13008                       pvInfoList[i].score / 100.0,
13009                       pvInfoList[i].depth,
13010                       buf );
13011
13012             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13013
13014             /* Print score/depth */
13015             blank = linelen > 0 && movelen > 0;
13016             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13017                 fprintf(f, "\n");
13018                 linelen = 0;
13019                 blank = 0;
13020             }
13021             if (blank) {
13022                 fprintf(f, " ");
13023                 linelen++;
13024             }
13025             fprintf(f, "%s", move_buffer);
13026             linelen += movelen;
13027         }
13028
13029         i++;
13030     }
13031
13032     /* Start a new line */
13033     if (linelen > 0) fprintf(f, "\n");
13034
13035     /* Print comments after last move */
13036     if (commentList[i] != NULL) {
13037         fprintf(f, "%s\n", commentList[i]);
13038     }
13039
13040     /* Print result */
13041     if (gameInfo.resultDetails != NULL &&
13042         gameInfo.resultDetails[0] != NULLCHAR) {
13043         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13044                 PGNResult(gameInfo.result));
13045     } else {
13046         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13047     }
13048
13049     fclose(f);
13050     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13051     return TRUE;
13052 }
13053
13054 /* Save game in old style and close the file */
13055 int
13056 SaveGameOldStyle (FILE *f)
13057 {
13058     int i, offset;
13059     time_t tm;
13060
13061     tm = time((time_t *) NULL);
13062
13063     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13064     PrintOpponents(f);
13065
13066     if (backwardMostMove > 0 || startedFromSetupPosition) {
13067         fprintf(f, "\n[--------------\n");
13068         PrintPosition(f, backwardMostMove);
13069         fprintf(f, "--------------]\n");
13070     } else {
13071         fprintf(f, "\n");
13072     }
13073
13074     i = backwardMostMove;
13075     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13076
13077     while (i < forwardMostMove) {
13078         if (commentList[i] != NULL) {
13079             fprintf(f, "[%s]\n", commentList[i]);
13080         }
13081
13082         if ((i % 2) == 1) {
13083             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13084             i++;
13085         } else {
13086             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13087             i++;
13088             if (commentList[i] != NULL) {
13089                 fprintf(f, "\n");
13090                 continue;
13091             }
13092             if (i >= forwardMostMove) {
13093                 fprintf(f, "\n");
13094                 break;
13095             }
13096             fprintf(f, "%s\n", parseList[i]);
13097             i++;
13098         }
13099     }
13100
13101     if (commentList[i] != NULL) {
13102         fprintf(f, "[%s]\n", commentList[i]);
13103     }
13104
13105     /* This isn't really the old style, but it's close enough */
13106     if (gameInfo.resultDetails != NULL &&
13107         gameInfo.resultDetails[0] != NULLCHAR) {
13108         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13109                 gameInfo.resultDetails);
13110     } else {
13111         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13112     }
13113
13114     fclose(f);
13115     return TRUE;
13116 }
13117
13118 /* Save the current game to open file f and close the file */
13119 int
13120 SaveGame (FILE *f, int dummy, char *dummy2)
13121 {
13122     if (gameMode == EditPosition) EditPositionDone(TRUE);
13123     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13124     if (appData.oldSaveStyle)
13125       return SaveGameOldStyle(f);
13126     else
13127       return SaveGamePGN(f);
13128 }
13129
13130 /* Save the current position to the given file */
13131 int
13132 SavePositionToFile (char *filename)
13133 {
13134     FILE *f;
13135     char buf[MSG_SIZ];
13136
13137     if (strcmp(filename, "-") == 0) {
13138         return SavePosition(stdout, 0, NULL);
13139     } else {
13140         f = fopen(filename, "a");
13141         if (f == NULL) {
13142             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13143             DisplayError(buf, errno);
13144             return FALSE;
13145         } else {
13146             safeStrCpy(buf, lastMsg, MSG_SIZ);
13147             DisplayMessage(_("Waiting for access to save file"), "");
13148             flock(fileno(f), LOCK_EX); // [HGM] lock
13149             DisplayMessage(_("Saving position"), "");
13150             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13151             SavePosition(f, 0, NULL);
13152             DisplayMessage(buf, "");
13153             return TRUE;
13154         }
13155     }
13156 }
13157
13158 /* Save the current position to the given open file and close the file */
13159 int
13160 SavePosition (FILE *f, int dummy, char *dummy2)
13161 {
13162     time_t tm;
13163     char *fen;
13164
13165     if (gameMode == EditPosition) EditPositionDone(TRUE);
13166     if (appData.oldSaveStyle) {
13167         tm = time((time_t *) NULL);
13168
13169         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13170         PrintOpponents(f);
13171         fprintf(f, "[--------------\n");
13172         PrintPosition(f, currentMove);
13173         fprintf(f, "--------------]\n");
13174     } else {
13175         fen = PositionToFEN(currentMove, NULL, 1);
13176         fprintf(f, "%s\n", fen);
13177         free(fen);
13178     }
13179     fclose(f);
13180     return TRUE;
13181 }
13182
13183 void
13184 ReloadCmailMsgEvent (int unregister)
13185 {
13186 #if !WIN32
13187     static char *inFilename = NULL;
13188     static char *outFilename;
13189     int i;
13190     struct stat inbuf, outbuf;
13191     int status;
13192
13193     /* Any registered moves are unregistered if unregister is set, */
13194     /* i.e. invoked by the signal handler */
13195     if (unregister) {
13196         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13197             cmailMoveRegistered[i] = FALSE;
13198             if (cmailCommentList[i] != NULL) {
13199                 free(cmailCommentList[i]);
13200                 cmailCommentList[i] = NULL;
13201             }
13202         }
13203         nCmailMovesRegistered = 0;
13204     }
13205
13206     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13207         cmailResult[i] = CMAIL_NOT_RESULT;
13208     }
13209     nCmailResults = 0;
13210
13211     if (inFilename == NULL) {
13212         /* Because the filenames are static they only get malloced once  */
13213         /* and they never get freed                                      */
13214         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13215         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13216
13217         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13218         sprintf(outFilename, "%s.out", appData.cmailGameName);
13219     }
13220
13221     status = stat(outFilename, &outbuf);
13222     if (status < 0) {
13223         cmailMailedMove = FALSE;
13224     } else {
13225         status = stat(inFilename, &inbuf);
13226         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13227     }
13228
13229     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13230        counts the games, notes how each one terminated, etc.
13231
13232        It would be nice to remove this kludge and instead gather all
13233        the information while building the game list.  (And to keep it
13234        in the game list nodes instead of having a bunch of fixed-size
13235        parallel arrays.)  Note this will require getting each game's
13236        termination from the PGN tags, as the game list builder does
13237        not process the game moves.  --mann
13238        */
13239     cmailMsgLoaded = TRUE;
13240     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13241
13242     /* Load first game in the file or popup game menu */
13243     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13244
13245 #endif /* !WIN32 */
13246     return;
13247 }
13248
13249 int
13250 RegisterMove ()
13251 {
13252     FILE *f;
13253     char string[MSG_SIZ];
13254
13255     if (   cmailMailedMove
13256         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13257         return TRUE;            /* Allow free viewing  */
13258     }
13259
13260     /* Unregister move to ensure that we don't leave RegisterMove        */
13261     /* with the move registered when the conditions for registering no   */
13262     /* longer hold                                                       */
13263     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13264         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13265         nCmailMovesRegistered --;
13266
13267         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13268           {
13269               free(cmailCommentList[lastLoadGameNumber - 1]);
13270               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13271           }
13272     }
13273
13274     if (cmailOldMove == -1) {
13275         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13276         return FALSE;
13277     }
13278
13279     if (currentMove > cmailOldMove + 1) {
13280         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13281         return FALSE;
13282     }
13283
13284     if (currentMove < cmailOldMove) {
13285         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13286         return FALSE;
13287     }
13288
13289     if (forwardMostMove > currentMove) {
13290         /* Silently truncate extra moves */
13291         TruncateGame();
13292     }
13293
13294     if (   (currentMove == cmailOldMove + 1)
13295         || (   (currentMove == cmailOldMove)
13296             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13297                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13298         if (gameInfo.result != GameUnfinished) {
13299             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13300         }
13301
13302         if (commentList[currentMove] != NULL) {
13303             cmailCommentList[lastLoadGameNumber - 1]
13304               = StrSave(commentList[currentMove]);
13305         }
13306         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13307
13308         if (appData.debugMode)
13309           fprintf(debugFP, "Saving %s for game %d\n",
13310                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13311
13312         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13313
13314         f = fopen(string, "w");
13315         if (appData.oldSaveStyle) {
13316             SaveGameOldStyle(f); /* also closes the file */
13317
13318             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13319             f = fopen(string, "w");
13320             SavePosition(f, 0, NULL); /* also closes the file */
13321         } else {
13322             fprintf(f, "{--------------\n");
13323             PrintPosition(f, currentMove);
13324             fprintf(f, "--------------}\n\n");
13325
13326             SaveGame(f, 0, NULL); /* also closes the file*/
13327         }
13328
13329         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13330         nCmailMovesRegistered ++;
13331     } else if (nCmailGames == 1) {
13332         DisplayError(_("You have not made a move yet"), 0);
13333         return FALSE;
13334     }
13335
13336     return TRUE;
13337 }
13338
13339 void
13340 MailMoveEvent ()
13341 {
13342 #if !WIN32
13343     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13344     FILE *commandOutput;
13345     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13346     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13347     int nBuffers;
13348     int i;
13349     int archived;
13350     char *arcDir;
13351
13352     if (! cmailMsgLoaded) {
13353         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13354         return;
13355     }
13356
13357     if (nCmailGames == nCmailResults) {
13358         DisplayError(_("No unfinished games"), 0);
13359         return;
13360     }
13361
13362 #if CMAIL_PROHIBIT_REMAIL
13363     if (cmailMailedMove) {
13364       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);
13365         DisplayError(msg, 0);
13366         return;
13367     }
13368 #endif
13369
13370     if (! (cmailMailedMove || RegisterMove())) return;
13371
13372     if (   cmailMailedMove
13373         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13374       snprintf(string, MSG_SIZ, partCommandString,
13375                appData.debugMode ? " -v" : "", appData.cmailGameName);
13376         commandOutput = popen(string, "r");
13377
13378         if (commandOutput == NULL) {
13379             DisplayError(_("Failed to invoke cmail"), 0);
13380         } else {
13381             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13382                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13383             }
13384             if (nBuffers > 1) {
13385                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13386                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13387                 nBytes = MSG_SIZ - 1;
13388             } else {
13389                 (void) memcpy(msg, buffer, nBytes);
13390             }
13391             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13392
13393             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13394                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13395
13396                 archived = TRUE;
13397                 for (i = 0; i < nCmailGames; i ++) {
13398                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13399                         archived = FALSE;
13400                     }
13401                 }
13402                 if (   archived
13403                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13404                         != NULL)) {
13405                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13406                            arcDir,
13407                            appData.cmailGameName,
13408                            gameInfo.date);
13409                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13410                     cmailMsgLoaded = FALSE;
13411                 }
13412             }
13413
13414             DisplayInformation(msg);
13415             pclose(commandOutput);
13416         }
13417     } else {
13418         if ((*cmailMsg) != '\0') {
13419             DisplayInformation(cmailMsg);
13420         }
13421     }
13422
13423     return;
13424 #endif /* !WIN32 */
13425 }
13426
13427 char *
13428 CmailMsg ()
13429 {
13430 #if WIN32
13431     return NULL;
13432 #else
13433     int  prependComma = 0;
13434     char number[5];
13435     char string[MSG_SIZ];       /* Space for game-list */
13436     int  i;
13437
13438     if (!cmailMsgLoaded) return "";
13439
13440     if (cmailMailedMove) {
13441       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13442     } else {
13443         /* Create a list of games left */
13444       snprintf(string, MSG_SIZ, "[");
13445         for (i = 0; i < nCmailGames; i ++) {
13446             if (! (   cmailMoveRegistered[i]
13447                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13448                 if (prependComma) {
13449                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13450                 } else {
13451                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13452                     prependComma = 1;
13453                 }
13454
13455                 strcat(string, number);
13456             }
13457         }
13458         strcat(string, "]");
13459
13460         if (nCmailMovesRegistered + nCmailResults == 0) {
13461             switch (nCmailGames) {
13462               case 1:
13463                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13464                 break;
13465
13466               case 2:
13467                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13468                 break;
13469
13470               default:
13471                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13472                          nCmailGames);
13473                 break;
13474             }
13475         } else {
13476             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13477               case 1:
13478                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13479                          string);
13480                 break;
13481
13482               case 0:
13483                 if (nCmailResults == nCmailGames) {
13484                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13485                 } else {
13486                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13487                 }
13488                 break;
13489
13490               default:
13491                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13492                          string);
13493             }
13494         }
13495     }
13496     return cmailMsg;
13497 #endif /* WIN32 */
13498 }
13499
13500 void
13501 ResetGameEvent ()
13502 {
13503     if (gameMode == Training)
13504       SetTrainingModeOff();
13505
13506     Reset(TRUE, TRUE);
13507     cmailMsgLoaded = FALSE;
13508     if (appData.icsActive) {
13509       SendToICS(ics_prefix);
13510       SendToICS("refresh\n");
13511     }
13512 }
13513
13514 void
13515 ExitEvent (int status)
13516 {
13517     exiting++;
13518     if (exiting > 2) {
13519       /* Give up on clean exit */
13520       exit(status);
13521     }
13522     if (exiting > 1) {
13523       /* Keep trying for clean exit */
13524       return;
13525     }
13526
13527     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13528
13529     if (telnetISR != NULL) {
13530       RemoveInputSource(telnetISR);
13531     }
13532     if (icsPR != NoProc) {
13533       DestroyChildProcess(icsPR, TRUE);
13534     }
13535
13536     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13537     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13538
13539     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13540     /* make sure this other one finishes before killing it!                  */
13541     if(endingGame) { int count = 0;
13542         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13543         while(endingGame && count++ < 10) DoSleep(1);
13544         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13545     }
13546
13547     /* Kill off chess programs */
13548     if (first.pr != NoProc) {
13549         ExitAnalyzeMode();
13550
13551         DoSleep( appData.delayBeforeQuit );
13552         SendToProgram("quit\n", &first);
13553         DoSleep( appData.delayAfterQuit );
13554         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13555     }
13556     if (second.pr != NoProc) {
13557         DoSleep( appData.delayBeforeQuit );
13558         SendToProgram("quit\n", &second);
13559         DoSleep( appData.delayAfterQuit );
13560         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13561     }
13562     if (first.isr != NULL) {
13563         RemoveInputSource(first.isr);
13564     }
13565     if (second.isr != NULL) {
13566         RemoveInputSource(second.isr);
13567     }
13568
13569     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13570     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13571
13572     ShutDownFrontEnd();
13573     exit(status);
13574 }
13575
13576 void
13577 PauseEngine (ChessProgramState *cps)
13578 {
13579     SendToProgram("pause\n", cps);
13580     cps->pause = 2;
13581 }
13582
13583 void
13584 UnPauseEngine (ChessProgramState *cps)
13585 {
13586     SendToProgram("resume\n", cps);
13587     cps->pause = 1;
13588 }
13589
13590 void
13591 PauseEvent ()
13592 {
13593     if (appData.debugMode)
13594         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13595     if (pausing) {
13596         pausing = FALSE;
13597         ModeHighlight();
13598         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13599             StartClocks();
13600             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13601                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13602                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13603             }
13604             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13605             HandleMachineMove(stashedInputMove, stalledEngine);
13606             stalledEngine = NULL;
13607             return;
13608         }
13609         if (gameMode == MachinePlaysWhite ||
13610             gameMode == TwoMachinesPlay   ||
13611             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13612             if(first.pause)  UnPauseEngine(&first);
13613             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13614             if(second.pause) UnPauseEngine(&second);
13615             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13616             StartClocks();
13617         } else {
13618             DisplayBothClocks();
13619         }
13620         if (gameMode == PlayFromGameFile) {
13621             if (appData.timeDelay >= 0)
13622                 AutoPlayGameLoop();
13623         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13624             Reset(FALSE, TRUE);
13625             SendToICS(ics_prefix);
13626             SendToICS("refresh\n");
13627         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13628             ForwardInner(forwardMostMove);
13629         }
13630         pauseExamInvalid = FALSE;
13631     } else {
13632         switch (gameMode) {
13633           default:
13634             return;
13635           case IcsExamining:
13636             pauseExamForwardMostMove = forwardMostMove;
13637             pauseExamInvalid = FALSE;
13638             /* fall through */
13639           case IcsObserving:
13640           case IcsPlayingWhite:
13641           case IcsPlayingBlack:
13642             pausing = TRUE;
13643             ModeHighlight();
13644             return;
13645           case PlayFromGameFile:
13646             (void) StopLoadGameTimer();
13647             pausing = TRUE;
13648             ModeHighlight();
13649             break;
13650           case BeginningOfGame:
13651             if (appData.icsActive) return;
13652             /* else fall through */
13653           case MachinePlaysWhite:
13654           case MachinePlaysBlack:
13655           case TwoMachinesPlay:
13656             if (forwardMostMove == 0)
13657               return;           /* don't pause if no one has moved */
13658             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13659                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13660                 if(onMove->pause) {           // thinking engine can be paused
13661                     PauseEngine(onMove);      // do it
13662                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13663                         PauseEngine(onMove->other);
13664                     else
13665                         SendToProgram("easy\n", onMove->other);
13666                     StopClocks();
13667                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13668             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13669                 if(first.pause) {
13670                     PauseEngine(&first);
13671                     StopClocks();
13672                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13673             } else { // human on move, pause pondering by either method
13674                 if(first.pause)
13675                     PauseEngine(&first);
13676                 else if(appData.ponderNextMove)
13677                     SendToProgram("easy\n", &first);
13678                 StopClocks();
13679             }
13680             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13681           case AnalyzeMode:
13682             pausing = TRUE;
13683             ModeHighlight();
13684             break;
13685         }
13686     }
13687 }
13688
13689 void
13690 EditCommentEvent ()
13691 {
13692     char title[MSG_SIZ];
13693
13694     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13695       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13696     } else {
13697       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13698                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13699                parseList[currentMove - 1]);
13700     }
13701
13702     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13703 }
13704
13705
13706 void
13707 EditTagsEvent ()
13708 {
13709     char *tags = PGNTags(&gameInfo);
13710     bookUp = FALSE;
13711     EditTagsPopUp(tags, NULL);
13712     free(tags);
13713 }
13714
13715 void
13716 ToggleSecond ()
13717 {
13718   if(second.analyzing) {
13719     SendToProgram("exit\n", &second);
13720     second.analyzing = FALSE;
13721   } else {
13722     if (second.pr == NoProc) StartChessProgram(&second);
13723     InitChessProgram(&second, FALSE);
13724     FeedMovesToProgram(&second, currentMove);
13725
13726     SendToProgram("analyze\n", &second);
13727     second.analyzing = TRUE;
13728   }
13729 }
13730
13731 /* Toggle ShowThinking */
13732 void
13733 ToggleShowThinking()
13734 {
13735   appData.showThinking = !appData.showThinking;
13736   ShowThinkingEvent();
13737 }
13738
13739 int
13740 AnalyzeModeEvent ()
13741 {
13742     char buf[MSG_SIZ];
13743
13744     if (!first.analysisSupport) {
13745       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13746       DisplayError(buf, 0);
13747       return 0;
13748     }
13749     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13750     if (appData.icsActive) {
13751         if (gameMode != IcsObserving) {
13752           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13753             DisplayError(buf, 0);
13754             /* secure check */
13755             if (appData.icsEngineAnalyze) {
13756                 if (appData.debugMode)
13757                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13758                 ExitAnalyzeMode();
13759                 ModeHighlight();
13760             }
13761             return 0;
13762         }
13763         /* if enable, user wants to disable icsEngineAnalyze */
13764         if (appData.icsEngineAnalyze) {
13765                 ExitAnalyzeMode();
13766                 ModeHighlight();
13767                 return 0;
13768         }
13769         appData.icsEngineAnalyze = TRUE;
13770         if (appData.debugMode)
13771             fprintf(debugFP, "ICS engine analyze starting... \n");
13772     }
13773
13774     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13775     if (appData.noChessProgram || gameMode == AnalyzeMode)
13776       return 0;
13777
13778     if (gameMode != AnalyzeFile) {
13779         if (!appData.icsEngineAnalyze) {
13780                EditGameEvent();
13781                if (gameMode != EditGame) return 0;
13782         }
13783         if (!appData.showThinking) ToggleShowThinking();
13784         ResurrectChessProgram();
13785         SendToProgram("analyze\n", &first);
13786         first.analyzing = TRUE;
13787         /*first.maybeThinking = TRUE;*/
13788         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13789         EngineOutputPopUp();
13790     }
13791     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13792     pausing = FALSE;
13793     ModeHighlight();
13794     SetGameInfo();
13795
13796     StartAnalysisClock();
13797     GetTimeMark(&lastNodeCountTime);
13798     lastNodeCount = 0;
13799     return 1;
13800 }
13801
13802 void
13803 AnalyzeFileEvent ()
13804 {
13805     if (appData.noChessProgram || gameMode == AnalyzeFile)
13806       return;
13807
13808     if (!first.analysisSupport) {
13809       char buf[MSG_SIZ];
13810       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13811       DisplayError(buf, 0);
13812       return;
13813     }
13814
13815     if (gameMode != AnalyzeMode) {
13816         keepInfo = 1; // mere annotating should not alter PGN tags
13817         EditGameEvent();
13818         keepInfo = 0;
13819         if (gameMode != EditGame) return;
13820         if (!appData.showThinking) ToggleShowThinking();
13821         ResurrectChessProgram();
13822         SendToProgram("analyze\n", &first);
13823         first.analyzing = TRUE;
13824         /*first.maybeThinking = TRUE;*/
13825         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13826         EngineOutputPopUp();
13827     }
13828     gameMode = AnalyzeFile;
13829     pausing = FALSE;
13830     ModeHighlight();
13831
13832     StartAnalysisClock();
13833     GetTimeMark(&lastNodeCountTime);
13834     lastNodeCount = 0;
13835     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13836     AnalysisPeriodicEvent(1);
13837 }
13838
13839 void
13840 MachineWhiteEvent ()
13841 {
13842     char buf[MSG_SIZ];
13843     char *bookHit = NULL;
13844
13845     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13846       return;
13847
13848
13849     if (gameMode == PlayFromGameFile ||
13850         gameMode == TwoMachinesPlay  ||
13851         gameMode == Training         ||
13852         gameMode == AnalyzeMode      ||
13853         gameMode == EndOfGame)
13854         EditGameEvent();
13855
13856     if (gameMode == EditPosition)
13857         EditPositionDone(TRUE);
13858
13859     if (!WhiteOnMove(currentMove)) {
13860         DisplayError(_("It is not White's turn"), 0);
13861         return;
13862     }
13863
13864     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13865       ExitAnalyzeMode();
13866
13867     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13868         gameMode == AnalyzeFile)
13869         TruncateGame();
13870
13871     ResurrectChessProgram();    /* in case it isn't running */
13872     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13873         gameMode = MachinePlaysWhite;
13874         ResetClocks();
13875     } else
13876     gameMode = MachinePlaysWhite;
13877     pausing = FALSE;
13878     ModeHighlight();
13879     SetGameInfo();
13880     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13881     DisplayTitle(buf);
13882     if (first.sendName) {
13883       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13884       SendToProgram(buf, &first);
13885     }
13886     if (first.sendTime) {
13887       if (first.useColors) {
13888         SendToProgram("black\n", &first); /*gnu kludge*/
13889       }
13890       SendTimeRemaining(&first, TRUE);
13891     }
13892     if (first.useColors) {
13893       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13894     }
13895     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13896     SetMachineThinkingEnables();
13897     first.maybeThinking = TRUE;
13898     StartClocks();
13899     firstMove = FALSE;
13900
13901     if (appData.autoFlipView && !flipView) {
13902       flipView = !flipView;
13903       DrawPosition(FALSE, NULL);
13904       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13905     }
13906
13907     if(bookHit) { // [HGM] book: simulate book reply
13908         static char bookMove[MSG_SIZ]; // a bit generous?
13909
13910         programStats.nodes = programStats.depth = programStats.time =
13911         programStats.score = programStats.got_only_move = 0;
13912         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13913
13914         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13915         strcat(bookMove, bookHit);
13916         HandleMachineMove(bookMove, &first);
13917     }
13918 }
13919
13920 void
13921 MachineBlackEvent ()
13922 {
13923   char buf[MSG_SIZ];
13924   char *bookHit = NULL;
13925
13926     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13927         return;
13928
13929
13930     if (gameMode == PlayFromGameFile ||
13931         gameMode == TwoMachinesPlay  ||
13932         gameMode == Training         ||
13933         gameMode == AnalyzeMode      ||
13934         gameMode == EndOfGame)
13935         EditGameEvent();
13936
13937     if (gameMode == EditPosition)
13938         EditPositionDone(TRUE);
13939
13940     if (WhiteOnMove(currentMove)) {
13941         DisplayError(_("It is not Black's turn"), 0);
13942         return;
13943     }
13944
13945     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13946       ExitAnalyzeMode();
13947
13948     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13949         gameMode == AnalyzeFile)
13950         TruncateGame();
13951
13952     ResurrectChessProgram();    /* in case it isn't running */
13953     gameMode = MachinePlaysBlack;
13954     pausing = FALSE;
13955     ModeHighlight();
13956     SetGameInfo();
13957     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13958     DisplayTitle(buf);
13959     if (first.sendName) {
13960       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13961       SendToProgram(buf, &first);
13962     }
13963     if (first.sendTime) {
13964       if (first.useColors) {
13965         SendToProgram("white\n", &first); /*gnu kludge*/
13966       }
13967       SendTimeRemaining(&first, FALSE);
13968     }
13969     if (first.useColors) {
13970       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13971     }
13972     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13973     SetMachineThinkingEnables();
13974     first.maybeThinking = TRUE;
13975     StartClocks();
13976
13977     if (appData.autoFlipView && flipView) {
13978       flipView = !flipView;
13979       DrawPosition(FALSE, NULL);
13980       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13981     }
13982     if(bookHit) { // [HGM] book: simulate book reply
13983         static char bookMove[MSG_SIZ]; // a bit generous?
13984
13985         programStats.nodes = programStats.depth = programStats.time =
13986         programStats.score = programStats.got_only_move = 0;
13987         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13988
13989         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13990         strcat(bookMove, bookHit);
13991         HandleMachineMove(bookMove, &first);
13992     }
13993 }
13994
13995
13996 void
13997 DisplayTwoMachinesTitle ()
13998 {
13999     char buf[MSG_SIZ];
14000     if (appData.matchGames > 0) {
14001         if(appData.tourneyFile[0]) {
14002           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14003                    gameInfo.white, _("vs."), gameInfo.black,
14004                    nextGame+1, appData.matchGames+1,
14005                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14006         } else
14007         if (first.twoMachinesColor[0] == 'w') {
14008           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14009                    gameInfo.white, _("vs."),  gameInfo.black,
14010                    first.matchWins, second.matchWins,
14011                    matchGame - 1 - (first.matchWins + second.matchWins));
14012         } else {
14013           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14014                    gameInfo.white, _("vs."), gameInfo.black,
14015                    second.matchWins, first.matchWins,
14016                    matchGame - 1 - (first.matchWins + second.matchWins));
14017         }
14018     } else {
14019       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14020     }
14021     DisplayTitle(buf);
14022 }
14023
14024 void
14025 SettingsMenuIfReady ()
14026 {
14027   if (second.lastPing != second.lastPong) {
14028     DisplayMessage("", _("Waiting for second chess program"));
14029     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14030     return;
14031   }
14032   ThawUI();
14033   DisplayMessage("", "");
14034   SettingsPopUp(&second);
14035 }
14036
14037 int
14038 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14039 {
14040     char buf[MSG_SIZ];
14041     if (cps->pr == NoProc) {
14042         StartChessProgram(cps);
14043         if (cps->protocolVersion == 1) {
14044           retry();
14045           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14046         } else {
14047           /* kludge: allow timeout for initial "feature" command */
14048           if(retry != TwoMachinesEventIfReady) FreezeUI();
14049           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14050           DisplayMessage("", buf);
14051           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14052         }
14053         return 1;
14054     }
14055     return 0;
14056 }
14057
14058 void
14059 TwoMachinesEvent P((void))
14060 {
14061     int i;
14062     char buf[MSG_SIZ];
14063     ChessProgramState *onmove;
14064     char *bookHit = NULL;
14065     static int stalling = 0;
14066     TimeMark now;
14067     long wait;
14068
14069     if (appData.noChessProgram) return;
14070
14071     switch (gameMode) {
14072       case TwoMachinesPlay:
14073         return;
14074       case MachinePlaysWhite:
14075       case MachinePlaysBlack:
14076         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14077             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14078             return;
14079         }
14080         /* fall through */
14081       case BeginningOfGame:
14082       case PlayFromGameFile:
14083       case EndOfGame:
14084         EditGameEvent();
14085         if (gameMode != EditGame) return;
14086         break;
14087       case EditPosition:
14088         EditPositionDone(TRUE);
14089         break;
14090       case AnalyzeMode:
14091       case AnalyzeFile:
14092         ExitAnalyzeMode();
14093         break;
14094       case EditGame:
14095       default:
14096         break;
14097     }
14098
14099 //    forwardMostMove = currentMove;
14100     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14101     startingEngine = TRUE;
14102
14103     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14104
14105     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14106     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14107       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14108       return;
14109     }
14110     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14111
14112     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14113         startingEngine = FALSE;
14114         DisplayError("second engine does not play this", 0);
14115         return;
14116     }
14117
14118     if(!stalling) {
14119       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14120       SendToProgram("force\n", &second);
14121       stalling = 1;
14122       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14123       return;
14124     }
14125     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14126     if(appData.matchPause>10000 || appData.matchPause<10)
14127                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14128     wait = SubtractTimeMarks(&now, &pauseStart);
14129     if(wait < appData.matchPause) {
14130         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14131         return;
14132     }
14133     // we are now committed to starting the game
14134     stalling = 0;
14135     DisplayMessage("", "");
14136     if (startedFromSetupPosition) {
14137         SendBoard(&second, backwardMostMove);
14138     if (appData.debugMode) {
14139         fprintf(debugFP, "Two Machines\n");
14140     }
14141     }
14142     for (i = backwardMostMove; i < forwardMostMove; i++) {
14143         SendMoveToProgram(i, &second);
14144     }
14145
14146     gameMode = TwoMachinesPlay;
14147     pausing = startingEngine = FALSE;
14148     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14149     SetGameInfo();
14150     DisplayTwoMachinesTitle();
14151     firstMove = TRUE;
14152     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14153         onmove = &first;
14154     } else {
14155         onmove = &second;
14156     }
14157     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14158     SendToProgram(first.computerString, &first);
14159     if (first.sendName) {
14160       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14161       SendToProgram(buf, &first);
14162     }
14163     SendToProgram(second.computerString, &second);
14164     if (second.sendName) {
14165       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14166       SendToProgram(buf, &second);
14167     }
14168
14169     ResetClocks();
14170     if (!first.sendTime || !second.sendTime) {
14171         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14172         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14173     }
14174     if (onmove->sendTime) {
14175       if (onmove->useColors) {
14176         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14177       }
14178       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14179     }
14180     if (onmove->useColors) {
14181       SendToProgram(onmove->twoMachinesColor, onmove);
14182     }
14183     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14184 //    SendToProgram("go\n", onmove);
14185     onmove->maybeThinking = TRUE;
14186     SetMachineThinkingEnables();
14187
14188     StartClocks();
14189
14190     if(bookHit) { // [HGM] book: simulate book reply
14191         static char bookMove[MSG_SIZ]; // a bit generous?
14192
14193         programStats.nodes = programStats.depth = programStats.time =
14194         programStats.score = programStats.got_only_move = 0;
14195         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14196
14197         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14198         strcat(bookMove, bookHit);
14199         savedMessage = bookMove; // args for deferred call
14200         savedState = onmove;
14201         ScheduleDelayedEvent(DeferredBookMove, 1);
14202     }
14203 }
14204
14205 void
14206 TrainingEvent ()
14207 {
14208     if (gameMode == Training) {
14209       SetTrainingModeOff();
14210       gameMode = PlayFromGameFile;
14211       DisplayMessage("", _("Training mode off"));
14212     } else {
14213       gameMode = Training;
14214       animateTraining = appData.animate;
14215
14216       /* make sure we are not already at the end of the game */
14217       if (currentMove < forwardMostMove) {
14218         SetTrainingModeOn();
14219         DisplayMessage("", _("Training mode on"));
14220       } else {
14221         gameMode = PlayFromGameFile;
14222         DisplayError(_("Already at end of game"), 0);
14223       }
14224     }
14225     ModeHighlight();
14226 }
14227
14228 void
14229 IcsClientEvent ()
14230 {
14231     if (!appData.icsActive) return;
14232     switch (gameMode) {
14233       case IcsPlayingWhite:
14234       case IcsPlayingBlack:
14235       case IcsObserving:
14236       case IcsIdle:
14237       case BeginningOfGame:
14238       case IcsExamining:
14239         return;
14240
14241       case EditGame:
14242         break;
14243
14244       case EditPosition:
14245         EditPositionDone(TRUE);
14246         break;
14247
14248       case AnalyzeMode:
14249       case AnalyzeFile:
14250         ExitAnalyzeMode();
14251         break;
14252
14253       default:
14254         EditGameEvent();
14255         break;
14256     }
14257
14258     gameMode = IcsIdle;
14259     ModeHighlight();
14260     return;
14261 }
14262
14263 void
14264 EditGameEvent ()
14265 {
14266     int i;
14267
14268     switch (gameMode) {
14269       case Training:
14270         SetTrainingModeOff();
14271         break;
14272       case MachinePlaysWhite:
14273       case MachinePlaysBlack:
14274       case BeginningOfGame:
14275         SendToProgram("force\n", &first);
14276         SetUserThinkingEnables();
14277         break;
14278       case PlayFromGameFile:
14279         (void) StopLoadGameTimer();
14280         if (gameFileFP != NULL) {
14281             gameFileFP = NULL;
14282         }
14283         break;
14284       case EditPosition:
14285         EditPositionDone(TRUE);
14286         break;
14287       case AnalyzeMode:
14288       case AnalyzeFile:
14289         ExitAnalyzeMode();
14290         SendToProgram("force\n", &first);
14291         break;
14292       case TwoMachinesPlay:
14293         GameEnds(EndOfFile, NULL, GE_PLAYER);
14294         ResurrectChessProgram();
14295         SetUserThinkingEnables();
14296         break;
14297       case EndOfGame:
14298         ResurrectChessProgram();
14299         break;
14300       case IcsPlayingBlack:
14301       case IcsPlayingWhite:
14302         DisplayError(_("Warning: You are still playing a game"), 0);
14303         break;
14304       case IcsObserving:
14305         DisplayError(_("Warning: You are still observing a game"), 0);
14306         break;
14307       case IcsExamining:
14308         DisplayError(_("Warning: You are still examining a game"), 0);
14309         break;
14310       case IcsIdle:
14311         break;
14312       case EditGame:
14313       default:
14314         return;
14315     }
14316
14317     pausing = FALSE;
14318     StopClocks();
14319     first.offeredDraw = second.offeredDraw = 0;
14320
14321     if (gameMode == PlayFromGameFile) {
14322         whiteTimeRemaining = timeRemaining[0][currentMove];
14323         blackTimeRemaining = timeRemaining[1][currentMove];
14324         DisplayTitle("");
14325     }
14326
14327     if (gameMode == MachinePlaysWhite ||
14328         gameMode == MachinePlaysBlack ||
14329         gameMode == TwoMachinesPlay ||
14330         gameMode == EndOfGame) {
14331         i = forwardMostMove;
14332         while (i > currentMove) {
14333             SendToProgram("undo\n", &first);
14334             i--;
14335         }
14336         if(!adjustedClock) {
14337         whiteTimeRemaining = timeRemaining[0][currentMove];
14338         blackTimeRemaining = timeRemaining[1][currentMove];
14339         DisplayBothClocks();
14340         }
14341         if (whiteFlag || blackFlag) {
14342             whiteFlag = blackFlag = 0;
14343         }
14344         DisplayTitle("");
14345     }
14346
14347     gameMode = EditGame;
14348     ModeHighlight();
14349     SetGameInfo();
14350 }
14351
14352
14353 void
14354 EditPositionEvent ()
14355 {
14356     if (gameMode == EditPosition) {
14357         EditGameEvent();
14358         return;
14359     }
14360
14361     EditGameEvent();
14362     if (gameMode != EditGame) return;
14363
14364     gameMode = EditPosition;
14365     ModeHighlight();
14366     SetGameInfo();
14367     if (currentMove > 0)
14368       CopyBoard(boards[0], boards[currentMove]);
14369
14370     blackPlaysFirst = !WhiteOnMove(currentMove);
14371     ResetClocks();
14372     currentMove = forwardMostMove = backwardMostMove = 0;
14373     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14374     DisplayMove(-1);
14375     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14376 }
14377
14378 void
14379 ExitAnalyzeMode ()
14380 {
14381     /* [DM] icsEngineAnalyze - possible call from other functions */
14382     if (appData.icsEngineAnalyze) {
14383         appData.icsEngineAnalyze = FALSE;
14384
14385         DisplayMessage("",_("Close ICS engine analyze..."));
14386     }
14387     if (first.analysisSupport && first.analyzing) {
14388       SendToBoth("exit\n");
14389       first.analyzing = second.analyzing = FALSE;
14390     }
14391     thinkOutput[0] = NULLCHAR;
14392 }
14393
14394 void
14395 EditPositionDone (Boolean fakeRights)
14396 {
14397     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14398
14399     startedFromSetupPosition = TRUE;
14400     InitChessProgram(&first, FALSE);
14401     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14402       boards[0][EP_STATUS] = EP_NONE;
14403       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14404       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14405         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14406         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14407       } else boards[0][CASTLING][2] = NoRights;
14408       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14409         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14410         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14411       } else boards[0][CASTLING][5] = NoRights;
14412       if(gameInfo.variant == VariantSChess) {
14413         int i;
14414         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14415           boards[0][VIRGIN][i] = 0;
14416           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14417           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14418         }
14419       }
14420     }
14421     SendToProgram("force\n", &first);
14422     if (blackPlaysFirst) {
14423         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14424         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14425         currentMove = forwardMostMove = backwardMostMove = 1;
14426         CopyBoard(boards[1], boards[0]);
14427     } else {
14428         currentMove = forwardMostMove = backwardMostMove = 0;
14429     }
14430     SendBoard(&first, forwardMostMove);
14431     if (appData.debugMode) {
14432         fprintf(debugFP, "EditPosDone\n");
14433     }
14434     DisplayTitle("");
14435     DisplayMessage("", "");
14436     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14437     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14438     gameMode = EditGame;
14439     ModeHighlight();
14440     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14441     ClearHighlights(); /* [AS] */
14442 }
14443
14444 /* Pause for `ms' milliseconds */
14445 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14446 void
14447 TimeDelay (long ms)
14448 {
14449     TimeMark m1, m2;
14450
14451     GetTimeMark(&m1);
14452     do {
14453         GetTimeMark(&m2);
14454     } while (SubtractTimeMarks(&m2, &m1) < ms);
14455 }
14456
14457 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14458 void
14459 SendMultiLineToICS (char *buf)
14460 {
14461     char temp[MSG_SIZ+1], *p;
14462     int len;
14463
14464     len = strlen(buf);
14465     if (len > MSG_SIZ)
14466       len = MSG_SIZ;
14467
14468     strncpy(temp, buf, len);
14469     temp[len] = 0;
14470
14471     p = temp;
14472     while (*p) {
14473         if (*p == '\n' || *p == '\r')
14474           *p = ' ';
14475         ++p;
14476     }
14477
14478     strcat(temp, "\n");
14479     SendToICS(temp);
14480     SendToPlayer(temp, strlen(temp));
14481 }
14482
14483 void
14484 SetWhiteToPlayEvent ()
14485 {
14486     if (gameMode == EditPosition) {
14487         blackPlaysFirst = FALSE;
14488         DisplayBothClocks();    /* works because currentMove is 0 */
14489     } else if (gameMode == IcsExamining) {
14490         SendToICS(ics_prefix);
14491         SendToICS("tomove white\n");
14492     }
14493 }
14494
14495 void
14496 SetBlackToPlayEvent ()
14497 {
14498     if (gameMode == EditPosition) {
14499         blackPlaysFirst = TRUE;
14500         currentMove = 1;        /* kludge */
14501         DisplayBothClocks();
14502         currentMove = 0;
14503     } else if (gameMode == IcsExamining) {
14504         SendToICS(ics_prefix);
14505         SendToICS("tomove black\n");
14506     }
14507 }
14508
14509 void
14510 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14511 {
14512     char buf[MSG_SIZ];
14513     ChessSquare piece = boards[0][y][x];
14514
14515     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14516
14517     switch (selection) {
14518       case ClearBoard:
14519         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14520             SendToICS(ics_prefix);
14521             SendToICS("bsetup clear\n");
14522         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14523             SendToICS(ics_prefix);
14524             SendToICS("clearboard\n");
14525         } else {
14526             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14527                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14528                 for (y = 0; y < BOARD_HEIGHT; y++) {
14529                     if (gameMode == IcsExamining) {
14530                         if (boards[currentMove][y][x] != EmptySquare) {
14531                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14532                                     AAA + x, ONE + y);
14533                             SendToICS(buf);
14534                         }
14535                     } else {
14536                         boards[0][y][x] = p;
14537                     }
14538                 }
14539             }
14540         }
14541         if (gameMode == EditPosition) {
14542             DrawPosition(FALSE, boards[0]);
14543         }
14544         break;
14545
14546       case WhitePlay:
14547         SetWhiteToPlayEvent();
14548         break;
14549
14550       case BlackPlay:
14551         SetBlackToPlayEvent();
14552         break;
14553
14554       case EmptySquare:
14555         if (gameMode == IcsExamining) {
14556             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14557             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14558             SendToICS(buf);
14559         } else {
14560             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14561                 if(x == BOARD_LEFT-2) {
14562                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14563                     boards[0][y][1] = 0;
14564                 } else
14565                 if(x == BOARD_RGHT+1) {
14566                     if(y >= gameInfo.holdingsSize) break;
14567                     boards[0][y][BOARD_WIDTH-2] = 0;
14568                 } else break;
14569             }
14570             boards[0][y][x] = EmptySquare;
14571             DrawPosition(FALSE, boards[0]);
14572         }
14573         break;
14574
14575       case PromotePiece:
14576         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14577            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14578             selection = (ChessSquare) (PROMOTED piece);
14579         } else if(piece == EmptySquare) selection = WhiteSilver;
14580         else selection = (ChessSquare)((int)piece - 1);
14581         goto defaultlabel;
14582
14583       case DemotePiece:
14584         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14585            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14586             selection = (ChessSquare) (DEMOTED piece);
14587         } else if(piece == EmptySquare) selection = BlackSilver;
14588         else selection = (ChessSquare)((int)piece + 1);
14589         goto defaultlabel;
14590
14591       case WhiteQueen:
14592       case BlackQueen:
14593         if(gameInfo.variant == VariantShatranj ||
14594            gameInfo.variant == VariantXiangqi  ||
14595            gameInfo.variant == VariantCourier  ||
14596            gameInfo.variant == VariantASEAN    ||
14597            gameInfo.variant == VariantMakruk     )
14598             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14599         goto defaultlabel;
14600
14601       case WhiteKing:
14602       case BlackKing:
14603         if(gameInfo.variant == VariantXiangqi)
14604             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14605         if(gameInfo.variant == VariantKnightmate)
14606             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14607       default:
14608         defaultlabel:
14609         if (gameMode == IcsExamining) {
14610             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14611             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14612                      PieceToChar(selection), AAA + x, ONE + y);
14613             SendToICS(buf);
14614         } else {
14615             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14616                 int n;
14617                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14618                     n = PieceToNumber(selection - BlackPawn);
14619                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14620                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14621                     boards[0][BOARD_HEIGHT-1-n][1]++;
14622                 } else
14623                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14624                     n = PieceToNumber(selection);
14625                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14626                     boards[0][n][BOARD_WIDTH-1] = selection;
14627                     boards[0][n][BOARD_WIDTH-2]++;
14628                 }
14629             } else
14630             boards[0][y][x] = selection;
14631             DrawPosition(TRUE, boards[0]);
14632             ClearHighlights();
14633             fromX = fromY = -1;
14634         }
14635         break;
14636     }
14637 }
14638
14639
14640 void
14641 DropMenuEvent (ChessSquare selection, int x, int y)
14642 {
14643     ChessMove moveType;
14644
14645     switch (gameMode) {
14646       case IcsPlayingWhite:
14647       case MachinePlaysBlack:
14648         if (!WhiteOnMove(currentMove)) {
14649             DisplayMoveError(_("It is Black's turn"));
14650             return;
14651         }
14652         moveType = WhiteDrop;
14653         break;
14654       case IcsPlayingBlack:
14655       case MachinePlaysWhite:
14656         if (WhiteOnMove(currentMove)) {
14657             DisplayMoveError(_("It is White's turn"));
14658             return;
14659         }
14660         moveType = BlackDrop;
14661         break;
14662       case EditGame:
14663         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14664         break;
14665       default:
14666         return;
14667     }
14668
14669     if (moveType == BlackDrop && selection < BlackPawn) {
14670       selection = (ChessSquare) ((int) selection
14671                                  + (int) BlackPawn - (int) WhitePawn);
14672     }
14673     if (boards[currentMove][y][x] != EmptySquare) {
14674         DisplayMoveError(_("That square is occupied"));
14675         return;
14676     }
14677
14678     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14679 }
14680
14681 void
14682 AcceptEvent ()
14683 {
14684     /* Accept a pending offer of any kind from opponent */
14685
14686     if (appData.icsActive) {
14687         SendToICS(ics_prefix);
14688         SendToICS("accept\n");
14689     } else if (cmailMsgLoaded) {
14690         if (currentMove == cmailOldMove &&
14691             commentList[cmailOldMove] != NULL &&
14692             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14693                    "Black offers a draw" : "White offers a draw")) {
14694             TruncateGame();
14695             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14696             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14697         } else {
14698             DisplayError(_("There is no pending offer on this move"), 0);
14699             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14700         }
14701     } else {
14702         /* Not used for offers from chess program */
14703     }
14704 }
14705
14706 void
14707 DeclineEvent ()
14708 {
14709     /* Decline a pending offer of any kind from opponent */
14710
14711     if (appData.icsActive) {
14712         SendToICS(ics_prefix);
14713         SendToICS("decline\n");
14714     } else if (cmailMsgLoaded) {
14715         if (currentMove == cmailOldMove &&
14716             commentList[cmailOldMove] != NULL &&
14717             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14718                    "Black offers a draw" : "White offers a draw")) {
14719 #ifdef NOTDEF
14720             AppendComment(cmailOldMove, "Draw declined", TRUE);
14721             DisplayComment(cmailOldMove - 1, "Draw declined");
14722 #endif /*NOTDEF*/
14723         } else {
14724             DisplayError(_("There is no pending offer on this move"), 0);
14725         }
14726     } else {
14727         /* Not used for offers from chess program */
14728     }
14729 }
14730
14731 void
14732 RematchEvent ()
14733 {
14734     /* Issue ICS rematch command */
14735     if (appData.icsActive) {
14736         SendToICS(ics_prefix);
14737         SendToICS("rematch\n");
14738     }
14739 }
14740
14741 void
14742 CallFlagEvent ()
14743 {
14744     /* Call your opponent's flag (claim a win on time) */
14745     if (appData.icsActive) {
14746         SendToICS(ics_prefix);
14747         SendToICS("flag\n");
14748     } else {
14749         switch (gameMode) {
14750           default:
14751             return;
14752           case MachinePlaysWhite:
14753             if (whiteFlag) {
14754                 if (blackFlag)
14755                   GameEnds(GameIsDrawn, "Both players ran out of time",
14756                            GE_PLAYER);
14757                 else
14758                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14759             } else {
14760                 DisplayError(_("Your opponent is not out of time"), 0);
14761             }
14762             break;
14763           case MachinePlaysBlack:
14764             if (blackFlag) {
14765                 if (whiteFlag)
14766                   GameEnds(GameIsDrawn, "Both players ran out of time",
14767                            GE_PLAYER);
14768                 else
14769                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14770             } else {
14771                 DisplayError(_("Your opponent is not out of time"), 0);
14772             }
14773             break;
14774         }
14775     }
14776 }
14777
14778 void
14779 ClockClick (int which)
14780 {       // [HGM] code moved to back-end from winboard.c
14781         if(which) { // black clock
14782           if (gameMode == EditPosition || gameMode == IcsExamining) {
14783             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14784             SetBlackToPlayEvent();
14785           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14786           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14787           } else if (shiftKey) {
14788             AdjustClock(which, -1);
14789           } else if (gameMode == IcsPlayingWhite ||
14790                      gameMode == MachinePlaysBlack) {
14791             CallFlagEvent();
14792           }
14793         } else { // white clock
14794           if (gameMode == EditPosition || gameMode == IcsExamining) {
14795             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14796             SetWhiteToPlayEvent();
14797           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14798           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14799           } else if (shiftKey) {
14800             AdjustClock(which, -1);
14801           } else if (gameMode == IcsPlayingBlack ||
14802                    gameMode == MachinePlaysWhite) {
14803             CallFlagEvent();
14804           }
14805         }
14806 }
14807
14808 void
14809 DrawEvent ()
14810 {
14811     /* Offer draw or accept pending draw offer from opponent */
14812
14813     if (appData.icsActive) {
14814         /* Note: tournament rules require draw offers to be
14815            made after you make your move but before you punch
14816            your clock.  Currently ICS doesn't let you do that;
14817            instead, you immediately punch your clock after making
14818            a move, but you can offer a draw at any time. */
14819
14820         SendToICS(ics_prefix);
14821         SendToICS("draw\n");
14822         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14823     } else if (cmailMsgLoaded) {
14824         if (currentMove == cmailOldMove &&
14825             commentList[cmailOldMove] != NULL &&
14826             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14827                    "Black offers a draw" : "White offers a draw")) {
14828             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14829             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14830         } else if (currentMove == cmailOldMove + 1) {
14831             char *offer = WhiteOnMove(cmailOldMove) ?
14832               "White offers a draw" : "Black offers a draw";
14833             AppendComment(currentMove, offer, TRUE);
14834             DisplayComment(currentMove - 1, offer);
14835             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14836         } else {
14837             DisplayError(_("You must make your move before offering a draw"), 0);
14838             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14839         }
14840     } else if (first.offeredDraw) {
14841         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14842     } else {
14843         if (first.sendDrawOffers) {
14844             SendToProgram("draw\n", &first);
14845             userOfferedDraw = TRUE;
14846         }
14847     }
14848 }
14849
14850 void
14851 AdjournEvent ()
14852 {
14853     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14854
14855     if (appData.icsActive) {
14856         SendToICS(ics_prefix);
14857         SendToICS("adjourn\n");
14858     } else {
14859         /* Currently GNU Chess doesn't offer or accept Adjourns */
14860     }
14861 }
14862
14863
14864 void
14865 AbortEvent ()
14866 {
14867     /* Offer Abort or accept pending Abort offer from opponent */
14868
14869     if (appData.icsActive) {
14870         SendToICS(ics_prefix);
14871         SendToICS("abort\n");
14872     } else {
14873         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14874     }
14875 }
14876
14877 void
14878 ResignEvent ()
14879 {
14880     /* Resign.  You can do this even if it's not your turn. */
14881
14882     if (appData.icsActive) {
14883         SendToICS(ics_prefix);
14884         SendToICS("resign\n");
14885     } else {
14886         switch (gameMode) {
14887           case MachinePlaysWhite:
14888             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14889             break;
14890           case MachinePlaysBlack:
14891             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14892             break;
14893           case EditGame:
14894             if (cmailMsgLoaded) {
14895                 TruncateGame();
14896                 if (WhiteOnMove(cmailOldMove)) {
14897                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14898                 } else {
14899                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14900                 }
14901                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14902             }
14903             break;
14904           default:
14905             break;
14906         }
14907     }
14908 }
14909
14910
14911 void
14912 StopObservingEvent ()
14913 {
14914     /* Stop observing current games */
14915     SendToICS(ics_prefix);
14916     SendToICS("unobserve\n");
14917 }
14918
14919 void
14920 StopExaminingEvent ()
14921 {
14922     /* Stop observing current game */
14923     SendToICS(ics_prefix);
14924     SendToICS("unexamine\n");
14925 }
14926
14927 void
14928 ForwardInner (int target)
14929 {
14930     int limit; int oldSeekGraphUp = seekGraphUp;
14931
14932     if (appData.debugMode)
14933         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14934                 target, currentMove, forwardMostMove);
14935
14936     if (gameMode == EditPosition)
14937       return;
14938
14939     seekGraphUp = FALSE;
14940     MarkTargetSquares(1);
14941
14942     if (gameMode == PlayFromGameFile && !pausing)
14943       PauseEvent();
14944
14945     if (gameMode == IcsExamining && pausing)
14946       limit = pauseExamForwardMostMove;
14947     else
14948       limit = forwardMostMove;
14949
14950     if (target > limit) target = limit;
14951
14952     if (target > 0 && moveList[target - 1][0]) {
14953         int fromX, fromY, toX, toY;
14954         toX = moveList[target - 1][2] - AAA;
14955         toY = moveList[target - 1][3] - ONE;
14956         if (moveList[target - 1][1] == '@') {
14957             if (appData.highlightLastMove) {
14958                 SetHighlights(-1, -1, toX, toY);
14959             }
14960         } else {
14961             fromX = moveList[target - 1][0] - AAA;
14962             fromY = moveList[target - 1][1] - ONE;
14963             if (target == currentMove + 1) {
14964                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14965             }
14966             if (appData.highlightLastMove) {
14967                 SetHighlights(fromX, fromY, toX, toY);
14968             }
14969         }
14970     }
14971     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14972         gameMode == Training || gameMode == PlayFromGameFile ||
14973         gameMode == AnalyzeFile) {
14974         while (currentMove < target) {
14975             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14976             SendMoveToProgram(currentMove++, &first);
14977         }
14978     } else {
14979         currentMove = target;
14980     }
14981
14982     if (gameMode == EditGame || gameMode == EndOfGame) {
14983         whiteTimeRemaining = timeRemaining[0][currentMove];
14984         blackTimeRemaining = timeRemaining[1][currentMove];
14985     }
14986     DisplayBothClocks();
14987     DisplayMove(currentMove - 1);
14988     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14989     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14990     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14991         DisplayComment(currentMove - 1, commentList[currentMove]);
14992     }
14993     ClearMap(); // [HGM] exclude: invalidate map
14994 }
14995
14996
14997 void
14998 ForwardEvent ()
14999 {
15000     if (gameMode == IcsExamining && !pausing) {
15001         SendToICS(ics_prefix);
15002         SendToICS("forward\n");
15003     } else {
15004         ForwardInner(currentMove + 1);
15005     }
15006 }
15007
15008 void
15009 ToEndEvent ()
15010 {
15011     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15012         /* to optimze, we temporarily turn off analysis mode while we feed
15013          * the remaining moves to the engine. Otherwise we get analysis output
15014          * after each move.
15015          */
15016         if (first.analysisSupport) {
15017           SendToProgram("exit\nforce\n", &first);
15018           first.analyzing = FALSE;
15019         }
15020     }
15021
15022     if (gameMode == IcsExamining && !pausing) {
15023         SendToICS(ics_prefix);
15024         SendToICS("forward 999999\n");
15025     } else {
15026         ForwardInner(forwardMostMove);
15027     }
15028
15029     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15030         /* we have fed all the moves, so reactivate analysis mode */
15031         SendToProgram("analyze\n", &first);
15032         first.analyzing = TRUE;
15033         /*first.maybeThinking = TRUE;*/
15034         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15035     }
15036 }
15037
15038 void
15039 BackwardInner (int target)
15040 {
15041     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15042
15043     if (appData.debugMode)
15044         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15045                 target, currentMove, forwardMostMove);
15046
15047     if (gameMode == EditPosition) return;
15048     seekGraphUp = FALSE;
15049     MarkTargetSquares(1);
15050     if (currentMove <= backwardMostMove) {
15051         ClearHighlights();
15052         DrawPosition(full_redraw, boards[currentMove]);
15053         return;
15054     }
15055     if (gameMode == PlayFromGameFile && !pausing)
15056       PauseEvent();
15057
15058     if (moveList[target][0]) {
15059         int fromX, fromY, toX, toY;
15060         toX = moveList[target][2] - AAA;
15061         toY = moveList[target][3] - ONE;
15062         if (moveList[target][1] == '@') {
15063             if (appData.highlightLastMove) {
15064                 SetHighlights(-1, -1, toX, toY);
15065             }
15066         } else {
15067             fromX = moveList[target][0] - AAA;
15068             fromY = moveList[target][1] - ONE;
15069             if (target == currentMove - 1) {
15070                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15071             }
15072             if (appData.highlightLastMove) {
15073                 SetHighlights(fromX, fromY, toX, toY);
15074             }
15075         }
15076     }
15077     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15078         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15079         while (currentMove > target) {
15080             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15081                 // null move cannot be undone. Reload program with move history before it.
15082                 int i;
15083                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15084                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15085                 }
15086                 SendBoard(&first, i);
15087               if(second.analyzing) SendBoard(&second, i);
15088                 for(currentMove=i; currentMove<target; currentMove++) {
15089                     SendMoveToProgram(currentMove, &first);
15090                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15091                 }
15092                 break;
15093             }
15094             SendToBoth("undo\n");
15095             currentMove--;
15096         }
15097     } else {
15098         currentMove = target;
15099     }
15100
15101     if (gameMode == EditGame || gameMode == EndOfGame) {
15102         whiteTimeRemaining = timeRemaining[0][currentMove];
15103         blackTimeRemaining = timeRemaining[1][currentMove];
15104     }
15105     DisplayBothClocks();
15106     DisplayMove(currentMove - 1);
15107     DrawPosition(full_redraw, boards[currentMove]);
15108     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15109     // [HGM] PV info: routine tests if comment empty
15110     DisplayComment(currentMove - 1, commentList[currentMove]);
15111     ClearMap(); // [HGM] exclude: invalidate map
15112 }
15113
15114 void
15115 BackwardEvent ()
15116 {
15117     if (gameMode == IcsExamining && !pausing) {
15118         SendToICS(ics_prefix);
15119         SendToICS("backward\n");
15120     } else {
15121         BackwardInner(currentMove - 1);
15122     }
15123 }
15124
15125 void
15126 ToStartEvent ()
15127 {
15128     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15129         /* to optimize, we temporarily turn off analysis mode while we undo
15130          * all the moves. Otherwise we get analysis output after each undo.
15131          */
15132         if (first.analysisSupport) {
15133           SendToProgram("exit\nforce\n", &first);
15134           first.analyzing = FALSE;
15135         }
15136     }
15137
15138     if (gameMode == IcsExamining && !pausing) {
15139         SendToICS(ics_prefix);
15140         SendToICS("backward 999999\n");
15141     } else {
15142         BackwardInner(backwardMostMove);
15143     }
15144
15145     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15146         /* we have fed all the moves, so reactivate analysis mode */
15147         SendToProgram("analyze\n", &first);
15148         first.analyzing = TRUE;
15149         /*first.maybeThinking = TRUE;*/
15150         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15151     }
15152 }
15153
15154 void
15155 ToNrEvent (int to)
15156 {
15157   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15158   if (to >= forwardMostMove) to = forwardMostMove;
15159   if (to <= backwardMostMove) to = backwardMostMove;
15160   if (to < currentMove) {
15161     BackwardInner(to);
15162   } else {
15163     ForwardInner(to);
15164   }
15165 }
15166
15167 void
15168 RevertEvent (Boolean annotate)
15169 {
15170     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15171         return;
15172     }
15173     if (gameMode != IcsExamining) {
15174         DisplayError(_("You are not examining a game"), 0);
15175         return;
15176     }
15177     if (pausing) {
15178         DisplayError(_("You can't revert while pausing"), 0);
15179         return;
15180     }
15181     SendToICS(ics_prefix);
15182     SendToICS("revert\n");
15183 }
15184
15185 void
15186 RetractMoveEvent ()
15187 {
15188     switch (gameMode) {
15189       case MachinePlaysWhite:
15190       case MachinePlaysBlack:
15191         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15192             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15193             return;
15194         }
15195         if (forwardMostMove < 2) return;
15196         currentMove = forwardMostMove = forwardMostMove - 2;
15197         whiteTimeRemaining = timeRemaining[0][currentMove];
15198         blackTimeRemaining = timeRemaining[1][currentMove];
15199         DisplayBothClocks();
15200         DisplayMove(currentMove - 1);
15201         ClearHighlights();/*!! could figure this out*/
15202         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15203         SendToProgram("remove\n", &first);
15204         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15205         break;
15206
15207       case BeginningOfGame:
15208       default:
15209         break;
15210
15211       case IcsPlayingWhite:
15212       case IcsPlayingBlack:
15213         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15214             SendToICS(ics_prefix);
15215             SendToICS("takeback 2\n");
15216         } else {
15217             SendToICS(ics_prefix);
15218             SendToICS("takeback 1\n");
15219         }
15220         break;
15221     }
15222 }
15223
15224 void
15225 MoveNowEvent ()
15226 {
15227     ChessProgramState *cps;
15228
15229     switch (gameMode) {
15230       case MachinePlaysWhite:
15231         if (!WhiteOnMove(forwardMostMove)) {
15232             DisplayError(_("It is your turn"), 0);
15233             return;
15234         }
15235         cps = &first;
15236         break;
15237       case MachinePlaysBlack:
15238         if (WhiteOnMove(forwardMostMove)) {
15239             DisplayError(_("It is your turn"), 0);
15240             return;
15241         }
15242         cps = &first;
15243         break;
15244       case TwoMachinesPlay:
15245         if (WhiteOnMove(forwardMostMove) ==
15246             (first.twoMachinesColor[0] == 'w')) {
15247             cps = &first;
15248         } else {
15249             cps = &second;
15250         }
15251         break;
15252       case BeginningOfGame:
15253       default:
15254         return;
15255     }
15256     SendToProgram("?\n", cps);
15257 }
15258
15259 void
15260 TruncateGameEvent ()
15261 {
15262     EditGameEvent();
15263     if (gameMode != EditGame) return;
15264     TruncateGame();
15265 }
15266
15267 void
15268 TruncateGame ()
15269 {
15270     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15271     if (forwardMostMove > currentMove) {
15272         if (gameInfo.resultDetails != NULL) {
15273             free(gameInfo.resultDetails);
15274             gameInfo.resultDetails = NULL;
15275             gameInfo.result = GameUnfinished;
15276         }
15277         forwardMostMove = currentMove;
15278         HistorySet(parseList, backwardMostMove, forwardMostMove,
15279                    currentMove-1);
15280     }
15281 }
15282
15283 void
15284 HintEvent ()
15285 {
15286     if (appData.noChessProgram) return;
15287     switch (gameMode) {
15288       case MachinePlaysWhite:
15289         if (WhiteOnMove(forwardMostMove)) {
15290             DisplayError(_("Wait until your turn"), 0);
15291             return;
15292         }
15293         break;
15294       case BeginningOfGame:
15295       case MachinePlaysBlack:
15296         if (!WhiteOnMove(forwardMostMove)) {
15297             DisplayError(_("Wait until your turn"), 0);
15298             return;
15299         }
15300         break;
15301       default:
15302         DisplayError(_("No hint available"), 0);
15303         return;
15304     }
15305     SendToProgram("hint\n", &first);
15306     hintRequested = TRUE;
15307 }
15308
15309 void
15310 CreateBookEvent ()
15311 {
15312     ListGame * lg = (ListGame *) gameList.head;
15313     FILE *f, *g;
15314     int nItem;
15315     static int secondTime = FALSE;
15316
15317     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15318         DisplayError(_("Game list not loaded or empty"), 0);
15319         return;
15320     }
15321
15322     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15323         fclose(g);
15324         secondTime++;
15325         DisplayNote(_("Book file exists! Try again for overwrite."));
15326         return;
15327     }
15328
15329     creatingBook = TRUE;
15330     secondTime = FALSE;
15331
15332     /* Get list size */
15333     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15334         LoadGame(f, nItem, "", TRUE);
15335         AddGameToBook(TRUE);
15336         lg = (ListGame *) lg->node.succ;
15337     }
15338
15339     creatingBook = FALSE;
15340     FlushBook();
15341 }
15342
15343 void
15344 BookEvent ()
15345 {
15346     if (appData.noChessProgram) return;
15347     switch (gameMode) {
15348       case MachinePlaysWhite:
15349         if (WhiteOnMove(forwardMostMove)) {
15350             DisplayError(_("Wait until your turn"), 0);
15351             return;
15352         }
15353         break;
15354       case BeginningOfGame:
15355       case MachinePlaysBlack:
15356         if (!WhiteOnMove(forwardMostMove)) {
15357             DisplayError(_("Wait until your turn"), 0);
15358             return;
15359         }
15360         break;
15361       case EditPosition:
15362         EditPositionDone(TRUE);
15363         break;
15364       case TwoMachinesPlay:
15365         return;
15366       default:
15367         break;
15368     }
15369     SendToProgram("bk\n", &first);
15370     bookOutput[0] = NULLCHAR;
15371     bookRequested = TRUE;
15372 }
15373
15374 void
15375 AboutGameEvent ()
15376 {
15377     char *tags = PGNTags(&gameInfo);
15378     TagsPopUp(tags, CmailMsg());
15379     free(tags);
15380 }
15381
15382 /* end button procedures */
15383
15384 void
15385 PrintPosition (FILE *fp, int move)
15386 {
15387     int i, j;
15388
15389     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15390         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15391             char c = PieceToChar(boards[move][i][j]);
15392             fputc(c == 'x' ? '.' : c, fp);
15393             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15394         }
15395     }
15396     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15397       fprintf(fp, "white to play\n");
15398     else
15399       fprintf(fp, "black to play\n");
15400 }
15401
15402 void
15403 PrintOpponents (FILE *fp)
15404 {
15405     if (gameInfo.white != NULL) {
15406         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15407     } else {
15408         fprintf(fp, "\n");
15409     }
15410 }
15411
15412 /* Find last component of program's own name, using some heuristics */
15413 void
15414 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15415 {
15416     char *p, *q, c;
15417     int local = (strcmp(host, "localhost") == 0);
15418     while (!local && (p = strchr(prog, ';')) != NULL) {
15419         p++;
15420         while (*p == ' ') p++;
15421         prog = p;
15422     }
15423     if (*prog == '"' || *prog == '\'') {
15424         q = strchr(prog + 1, *prog);
15425     } else {
15426         q = strchr(prog, ' ');
15427     }
15428     if (q == NULL) q = prog + strlen(prog);
15429     p = q;
15430     while (p >= prog && *p != '/' && *p != '\\') p--;
15431     p++;
15432     if(p == prog && *p == '"') p++;
15433     c = *q; *q = 0;
15434     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15435     memcpy(buf, p, q - p);
15436     buf[q - p] = NULLCHAR;
15437     if (!local) {
15438         strcat(buf, "@");
15439         strcat(buf, host);
15440     }
15441 }
15442
15443 char *
15444 TimeControlTagValue ()
15445 {
15446     char buf[MSG_SIZ];
15447     if (!appData.clockMode) {
15448       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15449     } else if (movesPerSession > 0) {
15450       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15451     } else if (timeIncrement == 0) {
15452       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15453     } else {
15454       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15455     }
15456     return StrSave(buf);
15457 }
15458
15459 void
15460 SetGameInfo ()
15461 {
15462     /* This routine is used only for certain modes */
15463     VariantClass v = gameInfo.variant;
15464     ChessMove r = GameUnfinished;
15465     char *p = NULL;
15466
15467     if(keepInfo) return;
15468
15469     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15470         r = gameInfo.result;
15471         p = gameInfo.resultDetails;
15472         gameInfo.resultDetails = NULL;
15473     }
15474     ClearGameInfo(&gameInfo);
15475     gameInfo.variant = v;
15476
15477     switch (gameMode) {
15478       case MachinePlaysWhite:
15479         gameInfo.event = StrSave( appData.pgnEventHeader );
15480         gameInfo.site = StrSave(HostName());
15481         gameInfo.date = PGNDate();
15482         gameInfo.round = StrSave("-");
15483         gameInfo.white = StrSave(first.tidy);
15484         gameInfo.black = StrSave(UserName());
15485         gameInfo.timeControl = TimeControlTagValue();
15486         break;
15487
15488       case MachinePlaysBlack:
15489         gameInfo.event = StrSave( appData.pgnEventHeader );
15490         gameInfo.site = StrSave(HostName());
15491         gameInfo.date = PGNDate();
15492         gameInfo.round = StrSave("-");
15493         gameInfo.white = StrSave(UserName());
15494         gameInfo.black = StrSave(first.tidy);
15495         gameInfo.timeControl = TimeControlTagValue();
15496         break;
15497
15498       case TwoMachinesPlay:
15499         gameInfo.event = StrSave( appData.pgnEventHeader );
15500         gameInfo.site = StrSave(HostName());
15501         gameInfo.date = PGNDate();
15502         if (roundNr > 0) {
15503             char buf[MSG_SIZ];
15504             snprintf(buf, MSG_SIZ, "%d", roundNr);
15505             gameInfo.round = StrSave(buf);
15506         } else {
15507             gameInfo.round = StrSave("-");
15508         }
15509         if (first.twoMachinesColor[0] == 'w') {
15510             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15511             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15512         } else {
15513             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15514             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15515         }
15516         gameInfo.timeControl = TimeControlTagValue();
15517         break;
15518
15519       case EditGame:
15520         gameInfo.event = StrSave("Edited game");
15521         gameInfo.site = StrSave(HostName());
15522         gameInfo.date = PGNDate();
15523         gameInfo.round = StrSave("-");
15524         gameInfo.white = StrSave("-");
15525         gameInfo.black = StrSave("-");
15526         gameInfo.result = r;
15527         gameInfo.resultDetails = p;
15528         break;
15529
15530       case EditPosition:
15531         gameInfo.event = StrSave("Edited position");
15532         gameInfo.site = StrSave(HostName());
15533         gameInfo.date = PGNDate();
15534         gameInfo.round = StrSave("-");
15535         gameInfo.white = StrSave("-");
15536         gameInfo.black = StrSave("-");
15537         break;
15538
15539       case IcsPlayingWhite:
15540       case IcsPlayingBlack:
15541       case IcsObserving:
15542       case IcsExamining:
15543         break;
15544
15545       case PlayFromGameFile:
15546         gameInfo.event = StrSave("Game from non-PGN file");
15547         gameInfo.site = StrSave(HostName());
15548         gameInfo.date = PGNDate();
15549         gameInfo.round = StrSave("-");
15550         gameInfo.white = StrSave("?");
15551         gameInfo.black = StrSave("?");
15552         break;
15553
15554       default:
15555         break;
15556     }
15557 }
15558
15559 void
15560 ReplaceComment (int index, char *text)
15561 {
15562     int len;
15563     char *p;
15564     float score;
15565
15566     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15567        pvInfoList[index-1].depth == len &&
15568        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15569        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15570     while (*text == '\n') text++;
15571     len = strlen(text);
15572     while (len > 0 && text[len - 1] == '\n') len--;
15573
15574     if (commentList[index] != NULL)
15575       free(commentList[index]);
15576
15577     if (len == 0) {
15578         commentList[index] = NULL;
15579         return;
15580     }
15581   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15582       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15583       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15584     commentList[index] = (char *) malloc(len + 2);
15585     strncpy(commentList[index], text, len);
15586     commentList[index][len] = '\n';
15587     commentList[index][len + 1] = NULLCHAR;
15588   } else {
15589     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15590     char *p;
15591     commentList[index] = (char *) malloc(len + 7);
15592     safeStrCpy(commentList[index], "{\n", 3);
15593     safeStrCpy(commentList[index]+2, text, len+1);
15594     commentList[index][len+2] = NULLCHAR;
15595     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15596     strcat(commentList[index], "\n}\n");
15597   }
15598 }
15599
15600 void
15601 CrushCRs (char *text)
15602 {
15603   char *p = text;
15604   char *q = text;
15605   char ch;
15606
15607   do {
15608     ch = *p++;
15609     if (ch == '\r') continue;
15610     *q++ = ch;
15611   } while (ch != '\0');
15612 }
15613
15614 void
15615 AppendComment (int index, char *text, Boolean addBraces)
15616 /* addBraces  tells if we should add {} */
15617 {
15618     int oldlen, len;
15619     char *old;
15620
15621 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15622     if(addBraces == 3) addBraces = 0; else // force appending literally
15623     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15624
15625     CrushCRs(text);
15626     while (*text == '\n') text++;
15627     len = strlen(text);
15628     while (len > 0 && text[len - 1] == '\n') len--;
15629     text[len] = NULLCHAR;
15630
15631     if (len == 0) return;
15632
15633     if (commentList[index] != NULL) {
15634       Boolean addClosingBrace = addBraces;
15635         old = commentList[index];
15636         oldlen = strlen(old);
15637         while(commentList[index][oldlen-1] ==  '\n')
15638           commentList[index][--oldlen] = NULLCHAR;
15639         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15640         safeStrCpy(commentList[index], old, oldlen + len + 6);
15641         free(old);
15642         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15643         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15644           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15645           while (*text == '\n') { text++; len--; }
15646           commentList[index][--oldlen] = NULLCHAR;
15647       }
15648         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15649         else          strcat(commentList[index], "\n");
15650         strcat(commentList[index], text);
15651         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15652         else          strcat(commentList[index], "\n");
15653     } else {
15654         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15655         if(addBraces)
15656           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15657         else commentList[index][0] = NULLCHAR;
15658         strcat(commentList[index], text);
15659         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15660         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15661     }
15662 }
15663
15664 static char *
15665 FindStr (char * text, char * sub_text)
15666 {
15667     char * result = strstr( text, sub_text );
15668
15669     if( result != NULL ) {
15670         result += strlen( sub_text );
15671     }
15672
15673     return result;
15674 }
15675
15676 /* [AS] Try to extract PV info from PGN comment */
15677 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15678 char *
15679 GetInfoFromComment (int index, char * text)
15680 {
15681     char * sep = text, *p;
15682
15683     if( text != NULL && index > 0 ) {
15684         int score = 0;
15685         int depth = 0;
15686         int time = -1, sec = 0, deci;
15687         char * s_eval = FindStr( text, "[%eval " );
15688         char * s_emt = FindStr( text, "[%emt " );
15689 #if 0
15690         if( s_eval != NULL || s_emt != NULL ) {
15691 #else
15692         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15693 #endif
15694             /* New style */
15695             char delim;
15696
15697             if( s_eval != NULL ) {
15698                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15699                     return text;
15700                 }
15701
15702                 if( delim != ']' ) {
15703                     return text;
15704                 }
15705             }
15706
15707             if( s_emt != NULL ) {
15708             }
15709                 return text;
15710         }
15711         else {
15712             /* We expect something like: [+|-]nnn.nn/dd */
15713             int score_lo = 0;
15714
15715             if(*text != '{') return text; // [HGM] braces: must be normal comment
15716
15717             sep = strchr( text, '/' );
15718             if( sep == NULL || sep < (text+4) ) {
15719                 return text;
15720             }
15721
15722             p = text;
15723             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15724             if(p[1] == '(') { // comment starts with PV
15725                p = strchr(p, ')'); // locate end of PV
15726                if(p == NULL || sep < p+5) return text;
15727                // at this point we have something like "{(.*) +0.23/6 ..."
15728                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15729                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15730                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15731             }
15732             time = -1; sec = -1; deci = -1;
15733             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15734                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15735                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15736                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15737                 return text;
15738             }
15739
15740             if( score_lo < 0 || score_lo >= 100 ) {
15741                 return text;
15742             }
15743
15744             if(sec >= 0) time = 600*time + 10*sec; else
15745             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15746
15747             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15748
15749             /* [HGM] PV time: now locate end of PV info */
15750             while( *++sep >= '0' && *sep <= '9'); // strip depth
15751             if(time >= 0)
15752             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15753             if(sec >= 0)
15754             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15755             if(deci >= 0)
15756             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15757             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15758         }
15759
15760         if( depth <= 0 ) {
15761             return text;
15762         }
15763
15764         if( time < 0 ) {
15765             time = -1;
15766         }
15767
15768         pvInfoList[index-1].depth = depth;
15769         pvInfoList[index-1].score = score;
15770         pvInfoList[index-1].time  = 10*time; // centi-sec
15771         if(*sep == '}') *sep = 0; else *--sep = '{';
15772         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15773     }
15774     return sep;
15775 }
15776
15777 void
15778 SendToProgram (char *message, ChessProgramState *cps)
15779 {
15780     int count, outCount, error;
15781     char buf[MSG_SIZ];
15782
15783     if (cps->pr == NoProc) return;
15784     Attention(cps);
15785
15786     if (appData.debugMode) {
15787         TimeMark now;
15788         GetTimeMark(&now);
15789         fprintf(debugFP, "%ld >%-6s: %s",
15790                 SubtractTimeMarks(&now, &programStartTime),
15791                 cps->which, message);
15792         if(serverFP)
15793             fprintf(serverFP, "%ld >%-6s: %s",
15794                 SubtractTimeMarks(&now, &programStartTime),
15795                 cps->which, message), fflush(serverFP);
15796     }
15797
15798     count = strlen(message);
15799     outCount = OutputToProcess(cps->pr, message, count, &error);
15800     if (outCount < count && !exiting
15801                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15802       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15803       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15804         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15805             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15806                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15807                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15808                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15809             } else {
15810                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15811                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15812                 gameInfo.result = res;
15813             }
15814             gameInfo.resultDetails = StrSave(buf);
15815         }
15816         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15817         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15818     }
15819 }
15820
15821 void
15822 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15823 {
15824     char *end_str;
15825     char buf[MSG_SIZ];
15826     ChessProgramState *cps = (ChessProgramState *)closure;
15827
15828     if (isr != cps->isr) return; /* Killed intentionally */
15829     if (count <= 0) {
15830         if (count == 0) {
15831             RemoveInputSource(cps->isr);
15832             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15833                     _(cps->which), cps->program);
15834             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15835             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15836                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15837                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15838                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15839                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15840                 } else {
15841                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15842                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15843                     gameInfo.result = res;
15844                 }
15845                 gameInfo.resultDetails = StrSave(buf);
15846             }
15847             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15848             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15849         } else {
15850             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15851                     _(cps->which), cps->program);
15852             RemoveInputSource(cps->isr);
15853
15854             /* [AS] Program is misbehaving badly... kill it */
15855             if( count == -2 ) {
15856                 DestroyChildProcess( cps->pr, 9 );
15857                 cps->pr = NoProc;
15858             }
15859
15860             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15861         }
15862         return;
15863     }
15864
15865     if ((end_str = strchr(message, '\r')) != NULL)
15866       *end_str = NULLCHAR;
15867     if ((end_str = strchr(message, '\n')) != NULL)
15868       *end_str = NULLCHAR;
15869
15870     if (appData.debugMode) {
15871         TimeMark now; int print = 1;
15872         char *quote = ""; char c; int i;
15873
15874         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15875                 char start = message[0];
15876                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15877                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15878                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15879                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15880                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15881                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15882                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15883                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15884                    sscanf(message, "hint: %c", &c)!=1 &&
15885                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15886                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15887                     print = (appData.engineComments >= 2);
15888                 }
15889                 message[0] = start; // restore original message
15890         }
15891         if(print) {
15892                 GetTimeMark(&now);
15893                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15894                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15895                         quote,
15896                         message);
15897                 if(serverFP)
15898                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15899                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15900                         quote,
15901                         message), fflush(serverFP);
15902         }
15903     }
15904
15905     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15906     if (appData.icsEngineAnalyze) {
15907         if (strstr(message, "whisper") != NULL ||
15908              strstr(message, "kibitz") != NULL ||
15909             strstr(message, "tellics") != NULL) return;
15910     }
15911
15912     HandleMachineMove(message, cps);
15913 }
15914
15915
15916 void
15917 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15918 {
15919     char buf[MSG_SIZ];
15920     int seconds;
15921
15922     if( timeControl_2 > 0 ) {
15923         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15924             tc = timeControl_2;
15925         }
15926     }
15927     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15928     inc /= cps->timeOdds;
15929     st  /= cps->timeOdds;
15930
15931     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15932
15933     if (st > 0) {
15934       /* Set exact time per move, normally using st command */
15935       if (cps->stKludge) {
15936         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15937         seconds = st % 60;
15938         if (seconds == 0) {
15939           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15940         } else {
15941           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15942         }
15943       } else {
15944         snprintf(buf, MSG_SIZ, "st %d\n", st);
15945       }
15946     } else {
15947       /* Set conventional or incremental time control, using level command */
15948       if (seconds == 0) {
15949         /* Note old gnuchess bug -- minutes:seconds used to not work.
15950            Fixed in later versions, but still avoid :seconds
15951            when seconds is 0. */
15952         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15953       } else {
15954         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15955                  seconds, inc/1000.);
15956       }
15957     }
15958     SendToProgram(buf, cps);
15959
15960     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15961     /* Orthogonally, limit search to given depth */
15962     if (sd > 0) {
15963       if (cps->sdKludge) {
15964         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15965       } else {
15966         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15967       }
15968       SendToProgram(buf, cps);
15969     }
15970
15971     if(cps->nps >= 0) { /* [HGM] nps */
15972         if(cps->supportsNPS == FALSE)
15973           cps->nps = -1; // don't use if engine explicitly says not supported!
15974         else {
15975           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15976           SendToProgram(buf, cps);
15977         }
15978     }
15979 }
15980
15981 ChessProgramState *
15982 WhitePlayer ()
15983 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15984 {
15985     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15986        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15987         return &second;
15988     return &first;
15989 }
15990
15991 void
15992 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15993 {
15994     char message[MSG_SIZ];
15995     long time, otime;
15996
15997     /* Note: this routine must be called when the clocks are stopped
15998        or when they have *just* been set or switched; otherwise
15999        it will be off by the time since the current tick started.
16000     */
16001     if (machineWhite) {
16002         time = whiteTimeRemaining / 10;
16003         otime = blackTimeRemaining / 10;
16004     } else {
16005         time = blackTimeRemaining / 10;
16006         otime = whiteTimeRemaining / 10;
16007     }
16008     /* [HGM] translate opponent's time by time-odds factor */
16009     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16010
16011     if (time <= 0) time = 1;
16012     if (otime <= 0) otime = 1;
16013
16014     snprintf(message, MSG_SIZ, "time %ld\n", time);
16015     SendToProgram(message, cps);
16016
16017     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16018     SendToProgram(message, cps);
16019 }
16020
16021 int
16022 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16023 {
16024   char buf[MSG_SIZ];
16025   int len = strlen(name);
16026   int val;
16027
16028   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16029     (*p) += len + 1;
16030     sscanf(*p, "%d", &val);
16031     *loc = (val != 0);
16032     while (**p && **p != ' ')
16033       (*p)++;
16034     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16035     SendToProgram(buf, cps);
16036     return TRUE;
16037   }
16038   return FALSE;
16039 }
16040
16041 int
16042 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16043 {
16044   char buf[MSG_SIZ];
16045   int len = strlen(name);
16046   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16047     (*p) += len + 1;
16048     sscanf(*p, "%d", loc);
16049     while (**p && **p != ' ') (*p)++;
16050     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16051     SendToProgram(buf, cps);
16052     return TRUE;
16053   }
16054   return FALSE;
16055 }
16056
16057 int
16058 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16059 {
16060   char buf[MSG_SIZ];
16061   int len = strlen(name);
16062   if (strncmp((*p), name, len) == 0
16063       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16064     (*p) += len + 2;
16065     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16066     sscanf(*p, "%[^\"]", *loc);
16067     while (**p && **p != '\"') (*p)++;
16068     if (**p == '\"') (*p)++;
16069     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16070     SendToProgram(buf, cps);
16071     return TRUE;
16072   }
16073   return FALSE;
16074 }
16075
16076 int
16077 ParseOption (Option *opt, ChessProgramState *cps)
16078 // [HGM] options: process the string that defines an engine option, and determine
16079 // name, type, default value, and allowed value range
16080 {
16081         char *p, *q, buf[MSG_SIZ];
16082         int n, min = (-1)<<31, max = 1<<31, def;
16083
16084         if(p = strstr(opt->name, " -spin ")) {
16085             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16086             if(max < min) max = min; // enforce consistency
16087             if(def < min) def = min;
16088             if(def > max) def = max;
16089             opt->value = def;
16090             opt->min = min;
16091             opt->max = max;
16092             opt->type = Spin;
16093         } else if((p = strstr(opt->name, " -slider "))) {
16094             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16095             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16096             if(max < min) max = min; // enforce consistency
16097             if(def < min) def = min;
16098             if(def > max) def = max;
16099             opt->value = def;
16100             opt->min = min;
16101             opt->max = max;
16102             opt->type = Spin; // Slider;
16103         } else if((p = strstr(opt->name, " -string "))) {
16104             opt->textValue = p+9;
16105             opt->type = TextBox;
16106         } else if((p = strstr(opt->name, " -file "))) {
16107             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16108             opt->textValue = p+7;
16109             opt->type = FileName; // FileName;
16110         } else if((p = strstr(opt->name, " -path "))) {
16111             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16112             opt->textValue = p+7;
16113             opt->type = PathName; // PathName;
16114         } else if(p = strstr(opt->name, " -check ")) {
16115             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16116             opt->value = (def != 0);
16117             opt->type = CheckBox;
16118         } else if(p = strstr(opt->name, " -combo ")) {
16119             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16120             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16121             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16122             opt->value = n = 0;
16123             while(q = StrStr(q, " /// ")) {
16124                 n++; *q = 0;    // count choices, and null-terminate each of them
16125                 q += 5;
16126                 if(*q == '*') { // remember default, which is marked with * prefix
16127                     q++;
16128                     opt->value = n;
16129                 }
16130                 cps->comboList[cps->comboCnt++] = q;
16131             }
16132             cps->comboList[cps->comboCnt++] = NULL;
16133             opt->max = n + 1;
16134             opt->type = ComboBox;
16135         } else if(p = strstr(opt->name, " -button")) {
16136             opt->type = Button;
16137         } else if(p = strstr(opt->name, " -save")) {
16138             opt->type = SaveButton;
16139         } else return FALSE;
16140         *p = 0; // terminate option name
16141         // now look if the command-line options define a setting for this engine option.
16142         if(cps->optionSettings && cps->optionSettings[0])
16143             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16144         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16145           snprintf(buf, MSG_SIZ, "option %s", p);
16146                 if(p = strstr(buf, ",")) *p = 0;
16147                 if(q = strchr(buf, '=')) switch(opt->type) {
16148                     case ComboBox:
16149                         for(n=0; n<opt->max; n++)
16150                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16151                         break;
16152                     case TextBox:
16153                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16154                         break;
16155                     case Spin:
16156                     case CheckBox:
16157                         opt->value = atoi(q+1);
16158                     default:
16159                         break;
16160                 }
16161                 strcat(buf, "\n");
16162                 SendToProgram(buf, cps);
16163         }
16164         return TRUE;
16165 }
16166
16167 void
16168 FeatureDone (ChessProgramState *cps, int val)
16169 {
16170   DelayedEventCallback cb = GetDelayedEvent();
16171   if ((cb == InitBackEnd3 && cps == &first) ||
16172       (cb == SettingsMenuIfReady && cps == &second) ||
16173       (cb == LoadEngine) ||
16174       (cb == TwoMachinesEventIfReady)) {
16175     CancelDelayedEvent();
16176     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16177   }
16178   cps->initDone = val;
16179   if(val) cps->reload = FALSE;
16180 }
16181
16182 /* Parse feature command from engine */
16183 void
16184 ParseFeatures (char *args, ChessProgramState *cps)
16185 {
16186   char *p = args;
16187   char *q = NULL;
16188   int val;
16189   char buf[MSG_SIZ];
16190
16191   for (;;) {
16192     while (*p == ' ') p++;
16193     if (*p == NULLCHAR) return;
16194
16195     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16196     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16197     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16198     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16199     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16200     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16201     if (BoolFeature(&p, "reuse", &val, cps)) {
16202       /* Engine can disable reuse, but can't enable it if user said no */
16203       if (!val) cps->reuse = FALSE;
16204       continue;
16205     }
16206     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16207     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16208       if (gameMode == TwoMachinesPlay) {
16209         DisplayTwoMachinesTitle();
16210       } else {
16211         DisplayTitle("");
16212       }
16213       continue;
16214     }
16215     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16216     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16217     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16218     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16219     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16220     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16221     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16222     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16223     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16224     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16225     if (IntFeature(&p, "done", &val, cps)) {
16226       FeatureDone(cps, val);
16227       continue;
16228     }
16229     /* Added by Tord: */
16230     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16231     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16232     /* End of additions by Tord */
16233
16234     /* [HGM] added features: */
16235     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16236     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16237     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16238     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16239     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16240     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16241     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16242     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16243         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16244         FREE(cps->option[cps->nrOptions].name);
16245         cps->option[cps->nrOptions].name = q; q = NULL;
16246         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16247           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16248             SendToProgram(buf, cps);
16249             continue;
16250         }
16251         if(cps->nrOptions >= MAX_OPTIONS) {
16252             cps->nrOptions--;
16253             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16254             DisplayError(buf, 0);
16255         }
16256         continue;
16257     }
16258     /* End of additions by HGM */
16259
16260     /* unknown feature: complain and skip */
16261     q = p;
16262     while (*q && *q != '=') q++;
16263     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16264     SendToProgram(buf, cps);
16265     p = q;
16266     if (*p == '=') {
16267       p++;
16268       if (*p == '\"') {
16269         p++;
16270         while (*p && *p != '\"') p++;
16271         if (*p == '\"') p++;
16272       } else {
16273         while (*p && *p != ' ') p++;
16274       }
16275     }
16276   }
16277
16278 }
16279
16280 void
16281 PeriodicUpdatesEvent (int newState)
16282 {
16283     if (newState == appData.periodicUpdates)
16284       return;
16285
16286     appData.periodicUpdates=newState;
16287
16288     /* Display type changes, so update it now */
16289 //    DisplayAnalysis();
16290
16291     /* Get the ball rolling again... */
16292     if (newState) {
16293         AnalysisPeriodicEvent(1);
16294         StartAnalysisClock();
16295     }
16296 }
16297
16298 void
16299 PonderNextMoveEvent (int newState)
16300 {
16301     if (newState == appData.ponderNextMove) return;
16302     if (gameMode == EditPosition) EditPositionDone(TRUE);
16303     if (newState) {
16304         SendToProgram("hard\n", &first);
16305         if (gameMode == TwoMachinesPlay) {
16306             SendToProgram("hard\n", &second);
16307         }
16308     } else {
16309         SendToProgram("easy\n", &first);
16310         thinkOutput[0] = NULLCHAR;
16311         if (gameMode == TwoMachinesPlay) {
16312             SendToProgram("easy\n", &second);
16313         }
16314     }
16315     appData.ponderNextMove = newState;
16316 }
16317
16318 void
16319 NewSettingEvent (int option, int *feature, char *command, int value)
16320 {
16321     char buf[MSG_SIZ];
16322
16323     if (gameMode == EditPosition) EditPositionDone(TRUE);
16324     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16325     if(feature == NULL || *feature) SendToProgram(buf, &first);
16326     if (gameMode == TwoMachinesPlay) {
16327         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16328     }
16329 }
16330
16331 void
16332 ShowThinkingEvent ()
16333 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16334 {
16335     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16336     int newState = appData.showThinking
16337         // [HGM] thinking: other features now need thinking output as well
16338         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16339
16340     if (oldState == newState) return;
16341     oldState = newState;
16342     if (gameMode == EditPosition) EditPositionDone(TRUE);
16343     if (oldState) {
16344         SendToProgram("post\n", &first);
16345         if (gameMode == TwoMachinesPlay) {
16346             SendToProgram("post\n", &second);
16347         }
16348     } else {
16349         SendToProgram("nopost\n", &first);
16350         thinkOutput[0] = NULLCHAR;
16351         if (gameMode == TwoMachinesPlay) {
16352             SendToProgram("nopost\n", &second);
16353         }
16354     }
16355 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16356 }
16357
16358 void
16359 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16360 {
16361   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16362   if (pr == NoProc) return;
16363   AskQuestion(title, question, replyPrefix, pr);
16364 }
16365
16366 void
16367 TypeInEvent (char firstChar)
16368 {
16369     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16370         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16371         gameMode == AnalyzeMode || gameMode == EditGame ||
16372         gameMode == EditPosition || gameMode == IcsExamining ||
16373         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16374         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16375                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16376                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16377         gameMode == Training) PopUpMoveDialog(firstChar);
16378 }
16379
16380 void
16381 TypeInDoneEvent (char *move)
16382 {
16383         Board board;
16384         int n, fromX, fromY, toX, toY;
16385         char promoChar;
16386         ChessMove moveType;
16387
16388         // [HGM] FENedit
16389         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16390                 EditPositionPasteFEN(move);
16391                 return;
16392         }
16393         // [HGM] movenum: allow move number to be typed in any mode
16394         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16395           ToNrEvent(2*n-1);
16396           return;
16397         }
16398         // undocumented kludge: allow command-line option to be typed in!
16399         // (potentially fatal, and does not implement the effect of the option.)
16400         // should only be used for options that are values on which future decisions will be made,
16401         // and definitely not on options that would be used during initialization.
16402         if(strstr(move, "!!! -") == move) {
16403             ParseArgsFromString(move+4);
16404             return;
16405         }
16406
16407       if (gameMode != EditGame && currentMove != forwardMostMove &&
16408         gameMode != Training) {
16409         DisplayMoveError(_("Displayed move is not current"));
16410       } else {
16411         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16412           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16413         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16414         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16415           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16416           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16417         } else {
16418           DisplayMoveError(_("Could not parse move"));
16419         }
16420       }
16421 }
16422
16423 void
16424 DisplayMove (int moveNumber)
16425 {
16426     char message[MSG_SIZ];
16427     char res[MSG_SIZ];
16428     char cpThinkOutput[MSG_SIZ];
16429
16430     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16431
16432     if (moveNumber == forwardMostMove - 1 ||
16433         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16434
16435         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16436
16437         if (strchr(cpThinkOutput, '\n')) {
16438             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16439         }
16440     } else {
16441         *cpThinkOutput = NULLCHAR;
16442     }
16443
16444     /* [AS] Hide thinking from human user */
16445     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16446         *cpThinkOutput = NULLCHAR;
16447         if( thinkOutput[0] != NULLCHAR ) {
16448             int i;
16449
16450             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16451                 cpThinkOutput[i] = '.';
16452             }
16453             cpThinkOutput[i] = NULLCHAR;
16454             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16455         }
16456     }
16457
16458     if (moveNumber == forwardMostMove - 1 &&
16459         gameInfo.resultDetails != NULL) {
16460         if (gameInfo.resultDetails[0] == NULLCHAR) {
16461           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16462         } else {
16463           snprintf(res, MSG_SIZ, " {%s} %s",
16464                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16465         }
16466     } else {
16467         res[0] = NULLCHAR;
16468     }
16469
16470     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16471         DisplayMessage(res, cpThinkOutput);
16472     } else {
16473       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16474                 WhiteOnMove(moveNumber) ? " " : ".. ",
16475                 parseList[moveNumber], res);
16476         DisplayMessage(message, cpThinkOutput);
16477     }
16478 }
16479
16480 void
16481 DisplayComment (int moveNumber, char *text)
16482 {
16483     char title[MSG_SIZ];
16484
16485     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16486       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16487     } else {
16488       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16489               WhiteOnMove(moveNumber) ? " " : ".. ",
16490               parseList[moveNumber]);
16491     }
16492     if (text != NULL && (appData.autoDisplayComment || commentUp))
16493         CommentPopUp(title, text);
16494 }
16495
16496 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16497  * might be busy thinking or pondering.  It can be omitted if your
16498  * gnuchess is configured to stop thinking immediately on any user
16499  * input.  However, that gnuchess feature depends on the FIONREAD
16500  * ioctl, which does not work properly on some flavors of Unix.
16501  */
16502 void
16503 Attention (ChessProgramState *cps)
16504 {
16505 #if ATTENTION
16506     if (!cps->useSigint) return;
16507     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16508     switch (gameMode) {
16509       case MachinePlaysWhite:
16510       case MachinePlaysBlack:
16511       case TwoMachinesPlay:
16512       case IcsPlayingWhite:
16513       case IcsPlayingBlack:
16514       case AnalyzeMode:
16515       case AnalyzeFile:
16516         /* Skip if we know it isn't thinking */
16517         if (!cps->maybeThinking) return;
16518         if (appData.debugMode)
16519           fprintf(debugFP, "Interrupting %s\n", cps->which);
16520         InterruptChildProcess(cps->pr);
16521         cps->maybeThinking = FALSE;
16522         break;
16523       default:
16524         break;
16525     }
16526 #endif /*ATTENTION*/
16527 }
16528
16529 int
16530 CheckFlags ()
16531 {
16532     if (whiteTimeRemaining <= 0) {
16533         if (!whiteFlag) {
16534             whiteFlag = TRUE;
16535             if (appData.icsActive) {
16536                 if (appData.autoCallFlag &&
16537                     gameMode == IcsPlayingBlack && !blackFlag) {
16538                   SendToICS(ics_prefix);
16539                   SendToICS("flag\n");
16540                 }
16541             } else {
16542                 if (blackFlag) {
16543                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16544                 } else {
16545                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16546                     if (appData.autoCallFlag) {
16547                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16548                         return TRUE;
16549                     }
16550                 }
16551             }
16552         }
16553     }
16554     if (blackTimeRemaining <= 0) {
16555         if (!blackFlag) {
16556             blackFlag = TRUE;
16557             if (appData.icsActive) {
16558                 if (appData.autoCallFlag &&
16559                     gameMode == IcsPlayingWhite && !whiteFlag) {
16560                   SendToICS(ics_prefix);
16561                   SendToICS("flag\n");
16562                 }
16563             } else {
16564                 if (whiteFlag) {
16565                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16566                 } else {
16567                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16568                     if (appData.autoCallFlag) {
16569                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16570                         return TRUE;
16571                     }
16572                 }
16573             }
16574         }
16575     }
16576     return FALSE;
16577 }
16578
16579 void
16580 CheckTimeControl ()
16581 {
16582     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16583         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16584
16585     /*
16586      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16587      */
16588     if ( !WhiteOnMove(forwardMostMove) ) {
16589         /* White made time control */
16590         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16591         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16592         /* [HGM] time odds: correct new time quota for time odds! */
16593                                             / WhitePlayer()->timeOdds;
16594         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16595     } else {
16596         lastBlack -= blackTimeRemaining;
16597         /* Black made time control */
16598         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16599                                             / WhitePlayer()->other->timeOdds;
16600         lastWhite = whiteTimeRemaining;
16601     }
16602 }
16603
16604 void
16605 DisplayBothClocks ()
16606 {
16607     int wom = gameMode == EditPosition ?
16608       !blackPlaysFirst : WhiteOnMove(currentMove);
16609     DisplayWhiteClock(whiteTimeRemaining, wom);
16610     DisplayBlackClock(blackTimeRemaining, !wom);
16611 }
16612
16613
16614 /* Timekeeping seems to be a portability nightmare.  I think everyone
16615    has ftime(), but I'm really not sure, so I'm including some ifdefs
16616    to use other calls if you don't.  Clocks will be less accurate if
16617    you have neither ftime nor gettimeofday.
16618 */
16619
16620 /* VS 2008 requires the #include outside of the function */
16621 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16622 #include <sys/timeb.h>
16623 #endif
16624
16625 /* Get the current time as a TimeMark */
16626 void
16627 GetTimeMark (TimeMark *tm)
16628 {
16629 #if HAVE_GETTIMEOFDAY
16630
16631     struct timeval timeVal;
16632     struct timezone timeZone;
16633
16634     gettimeofday(&timeVal, &timeZone);
16635     tm->sec = (long) timeVal.tv_sec;
16636     tm->ms = (int) (timeVal.tv_usec / 1000L);
16637
16638 #else /*!HAVE_GETTIMEOFDAY*/
16639 #if HAVE_FTIME
16640
16641 // include <sys/timeb.h> / moved to just above start of function
16642     struct timeb timeB;
16643
16644     ftime(&timeB);
16645     tm->sec = (long) timeB.time;
16646     tm->ms = (int) timeB.millitm;
16647
16648 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16649     tm->sec = (long) time(NULL);
16650     tm->ms = 0;
16651 #endif
16652 #endif
16653 }
16654
16655 /* Return the difference in milliseconds between two
16656    time marks.  We assume the difference will fit in a long!
16657 */
16658 long
16659 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16660 {
16661     return 1000L*(tm2->sec - tm1->sec) +
16662            (long) (tm2->ms - tm1->ms);
16663 }
16664
16665
16666 /*
16667  * Code to manage the game clocks.
16668  *
16669  * In tournament play, black starts the clock and then white makes a move.
16670  * We give the human user a slight advantage if he is playing white---the
16671  * clocks don't run until he makes his first move, so it takes zero time.
16672  * Also, we don't account for network lag, so we could get out of sync
16673  * with GNU Chess's clock -- but then, referees are always right.
16674  */
16675
16676 static TimeMark tickStartTM;
16677 static long intendedTickLength;
16678
16679 long
16680 NextTickLength (long timeRemaining)
16681 {
16682     long nominalTickLength, nextTickLength;
16683
16684     if (timeRemaining > 0L && timeRemaining <= 10000L)
16685       nominalTickLength = 100L;
16686     else
16687       nominalTickLength = 1000L;
16688     nextTickLength = timeRemaining % nominalTickLength;
16689     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16690
16691     return nextTickLength;
16692 }
16693
16694 /* Adjust clock one minute up or down */
16695 void
16696 AdjustClock (Boolean which, int dir)
16697 {
16698     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16699     if(which) blackTimeRemaining += 60000*dir;
16700     else      whiteTimeRemaining += 60000*dir;
16701     DisplayBothClocks();
16702     adjustedClock = TRUE;
16703 }
16704
16705 /* Stop clocks and reset to a fresh time control */
16706 void
16707 ResetClocks ()
16708 {
16709     (void) StopClockTimer();
16710     if (appData.icsActive) {
16711         whiteTimeRemaining = blackTimeRemaining = 0;
16712     } else if (searchTime) {
16713         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16714         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16715     } else { /* [HGM] correct new time quote for time odds */
16716         whiteTC = blackTC = fullTimeControlString;
16717         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16718         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16719     }
16720     if (whiteFlag || blackFlag) {
16721         DisplayTitle("");
16722         whiteFlag = blackFlag = FALSE;
16723     }
16724     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16725     DisplayBothClocks();
16726     adjustedClock = FALSE;
16727 }
16728
16729 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16730
16731 /* Decrement running clock by amount of time that has passed */
16732 void
16733 DecrementClocks ()
16734 {
16735     long timeRemaining;
16736     long lastTickLength, fudge;
16737     TimeMark now;
16738
16739     if (!appData.clockMode) return;
16740     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16741
16742     GetTimeMark(&now);
16743
16744     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16745
16746     /* Fudge if we woke up a little too soon */
16747     fudge = intendedTickLength - lastTickLength;
16748     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16749
16750     if (WhiteOnMove(forwardMostMove)) {
16751         if(whiteNPS >= 0) lastTickLength = 0;
16752         timeRemaining = whiteTimeRemaining -= lastTickLength;
16753         if(timeRemaining < 0 && !appData.icsActive) {
16754             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16755             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16756                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16757                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16758             }
16759         }
16760         DisplayWhiteClock(whiteTimeRemaining - fudge,
16761                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16762     } else {
16763         if(blackNPS >= 0) lastTickLength = 0;
16764         timeRemaining = blackTimeRemaining -= lastTickLength;
16765         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16766             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16767             if(suddenDeath) {
16768                 blackStartMove = forwardMostMove;
16769                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16770             }
16771         }
16772         DisplayBlackClock(blackTimeRemaining - fudge,
16773                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16774     }
16775     if (CheckFlags()) return;
16776
16777     if(twoBoards) { // count down secondary board's clocks as well
16778         activePartnerTime -= lastTickLength;
16779         partnerUp = 1;
16780         if(activePartner == 'W')
16781             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16782         else
16783             DisplayBlackClock(activePartnerTime, TRUE);
16784         partnerUp = 0;
16785     }
16786
16787     tickStartTM = now;
16788     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16789     StartClockTimer(intendedTickLength);
16790
16791     /* if the time remaining has fallen below the alarm threshold, sound the
16792      * alarm. if the alarm has sounded and (due to a takeback or time control
16793      * with increment) the time remaining has increased to a level above the
16794      * threshold, reset the alarm so it can sound again.
16795      */
16796
16797     if (appData.icsActive && appData.icsAlarm) {
16798
16799         /* make sure we are dealing with the user's clock */
16800         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16801                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16802            )) return;
16803
16804         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16805             alarmSounded = FALSE;
16806         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16807             PlayAlarmSound();
16808             alarmSounded = TRUE;
16809         }
16810     }
16811 }
16812
16813
16814 /* A player has just moved, so stop the previously running
16815    clock and (if in clock mode) start the other one.
16816    We redisplay both clocks in case we're in ICS mode, because
16817    ICS gives us an update to both clocks after every move.
16818    Note that this routine is called *after* forwardMostMove
16819    is updated, so the last fractional tick must be subtracted
16820    from the color that is *not* on move now.
16821 */
16822 void
16823 SwitchClocks (int newMoveNr)
16824 {
16825     long lastTickLength;
16826     TimeMark now;
16827     int flagged = FALSE;
16828
16829     GetTimeMark(&now);
16830
16831     if (StopClockTimer() && appData.clockMode) {
16832         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16833         if (!WhiteOnMove(forwardMostMove)) {
16834             if(blackNPS >= 0) lastTickLength = 0;
16835             blackTimeRemaining -= lastTickLength;
16836            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16837 //         if(pvInfoList[forwardMostMove].time == -1)
16838                  pvInfoList[forwardMostMove].time =               // use GUI time
16839                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16840         } else {
16841            if(whiteNPS >= 0) lastTickLength = 0;
16842            whiteTimeRemaining -= lastTickLength;
16843            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16844 //         if(pvInfoList[forwardMostMove].time == -1)
16845                  pvInfoList[forwardMostMove].time =
16846                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16847         }
16848         flagged = CheckFlags();
16849     }
16850     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16851     CheckTimeControl();
16852
16853     if (flagged || !appData.clockMode) return;
16854
16855     switch (gameMode) {
16856       case MachinePlaysBlack:
16857       case MachinePlaysWhite:
16858       case BeginningOfGame:
16859         if (pausing) return;
16860         break;
16861
16862       case EditGame:
16863       case PlayFromGameFile:
16864       case IcsExamining:
16865         return;
16866
16867       default:
16868         break;
16869     }
16870
16871     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16872         if(WhiteOnMove(forwardMostMove))
16873              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16874         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16875     }
16876
16877     tickStartTM = now;
16878     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16879       whiteTimeRemaining : blackTimeRemaining);
16880     StartClockTimer(intendedTickLength);
16881 }
16882
16883
16884 /* Stop both clocks */
16885 void
16886 StopClocks ()
16887 {
16888     long lastTickLength;
16889     TimeMark now;
16890
16891     if (!StopClockTimer()) return;
16892     if (!appData.clockMode) return;
16893
16894     GetTimeMark(&now);
16895
16896     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16897     if (WhiteOnMove(forwardMostMove)) {
16898         if(whiteNPS >= 0) lastTickLength = 0;
16899         whiteTimeRemaining -= lastTickLength;
16900         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16901     } else {
16902         if(blackNPS >= 0) lastTickLength = 0;
16903         blackTimeRemaining -= lastTickLength;
16904         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16905     }
16906     CheckFlags();
16907 }
16908
16909 /* Start clock of player on move.  Time may have been reset, so
16910    if clock is already running, stop and restart it. */
16911 void
16912 StartClocks ()
16913 {
16914     (void) StopClockTimer(); /* in case it was running already */
16915     DisplayBothClocks();
16916     if (CheckFlags()) return;
16917
16918     if (!appData.clockMode) return;
16919     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16920
16921     GetTimeMark(&tickStartTM);
16922     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16923       whiteTimeRemaining : blackTimeRemaining);
16924
16925    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16926     whiteNPS = blackNPS = -1;
16927     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16928        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16929         whiteNPS = first.nps;
16930     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16931        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16932         blackNPS = first.nps;
16933     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16934         whiteNPS = second.nps;
16935     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16936         blackNPS = second.nps;
16937     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16938
16939     StartClockTimer(intendedTickLength);
16940 }
16941
16942 char *
16943 TimeString (long ms)
16944 {
16945     long second, minute, hour, day;
16946     char *sign = "";
16947     static char buf[32];
16948
16949     if (ms > 0 && ms <= 9900) {
16950       /* convert milliseconds to tenths, rounding up */
16951       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16952
16953       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16954       return buf;
16955     }
16956
16957     /* convert milliseconds to seconds, rounding up */
16958     /* use floating point to avoid strangeness of integer division
16959        with negative dividends on many machines */
16960     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16961
16962     if (second < 0) {
16963         sign = "-";
16964         second = -second;
16965     }
16966
16967     day = second / (60 * 60 * 24);
16968     second = second % (60 * 60 * 24);
16969     hour = second / (60 * 60);
16970     second = second % (60 * 60);
16971     minute = second / 60;
16972     second = second % 60;
16973
16974     if (day > 0)
16975       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16976               sign, day, hour, minute, second);
16977     else if (hour > 0)
16978       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16979     else
16980       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16981
16982     return buf;
16983 }
16984
16985
16986 /*
16987  * This is necessary because some C libraries aren't ANSI C compliant yet.
16988  */
16989 char *
16990 StrStr (char *string, char *match)
16991 {
16992     int i, length;
16993
16994     length = strlen(match);
16995
16996     for (i = strlen(string) - length; i >= 0; i--, string++)
16997       if (!strncmp(match, string, length))
16998         return string;
16999
17000     return NULL;
17001 }
17002
17003 char *
17004 StrCaseStr (char *string, char *match)
17005 {
17006     int i, j, length;
17007
17008     length = strlen(match);
17009
17010     for (i = strlen(string) - length; i >= 0; i--, string++) {
17011         for (j = 0; j < length; j++) {
17012             if (ToLower(match[j]) != ToLower(string[j]))
17013               break;
17014         }
17015         if (j == length) return string;
17016     }
17017
17018     return NULL;
17019 }
17020
17021 #ifndef _amigados
17022 int
17023 StrCaseCmp (char *s1, char *s2)
17024 {
17025     char c1, c2;
17026
17027     for (;;) {
17028         c1 = ToLower(*s1++);
17029         c2 = ToLower(*s2++);
17030         if (c1 > c2) return 1;
17031         if (c1 < c2) return -1;
17032         if (c1 == NULLCHAR) return 0;
17033     }
17034 }
17035
17036
17037 int
17038 ToLower (int c)
17039 {
17040     return isupper(c) ? tolower(c) : c;
17041 }
17042
17043
17044 int
17045 ToUpper (int c)
17046 {
17047     return islower(c) ? toupper(c) : c;
17048 }
17049 #endif /* !_amigados    */
17050
17051 char *
17052 StrSave (char *s)
17053 {
17054   char *ret;
17055
17056   if ((ret = (char *) malloc(strlen(s) + 1)))
17057     {
17058       safeStrCpy(ret, s, strlen(s)+1);
17059     }
17060   return ret;
17061 }
17062
17063 char *
17064 StrSavePtr (char *s, char **savePtr)
17065 {
17066     if (*savePtr) {
17067         free(*savePtr);
17068     }
17069     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17070       safeStrCpy(*savePtr, s, strlen(s)+1);
17071     }
17072     return(*savePtr);
17073 }
17074
17075 char *
17076 PGNDate ()
17077 {
17078     time_t clock;
17079     struct tm *tm;
17080     char buf[MSG_SIZ];
17081
17082     clock = time((time_t *)NULL);
17083     tm = localtime(&clock);
17084     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17085             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17086     return StrSave(buf);
17087 }
17088
17089
17090 char *
17091 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17092 {
17093     int i, j, fromX, fromY, toX, toY;
17094     int whiteToPlay;
17095     char buf[MSG_SIZ];
17096     char *p, *q;
17097     int emptycount;
17098     ChessSquare piece;
17099
17100     whiteToPlay = (gameMode == EditPosition) ?
17101       !blackPlaysFirst : (move % 2 == 0);
17102     p = buf;
17103
17104     /* Piece placement data */
17105     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17106         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17107         emptycount = 0;
17108         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17109             if (boards[move][i][j] == EmptySquare) {
17110                 emptycount++;
17111             } else { ChessSquare piece = boards[move][i][j];
17112                 if (emptycount > 0) {
17113                     if(emptycount<10) /* [HGM] can be >= 10 */
17114                         *p++ = '0' + emptycount;
17115                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17116                     emptycount = 0;
17117                 }
17118                 if(PieceToChar(piece) == '+') {
17119                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17120                     *p++ = '+';
17121                     piece = (ChessSquare)(DEMOTED piece);
17122                 }
17123                 *p++ = PieceToChar(piece);
17124                 if(p[-1] == '~') {
17125                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17126                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17127                     *p++ = '~';
17128                 }
17129             }
17130         }
17131         if (emptycount > 0) {
17132             if(emptycount<10) /* [HGM] can be >= 10 */
17133                 *p++ = '0' + emptycount;
17134             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17135             emptycount = 0;
17136         }
17137         *p++ = '/';
17138     }
17139     *(p - 1) = ' ';
17140
17141     /* [HGM] print Crazyhouse or Shogi holdings */
17142     if( gameInfo.holdingsWidth ) {
17143         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17144         q = p;
17145         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17146             piece = boards[move][i][BOARD_WIDTH-1];
17147             if( piece != EmptySquare )
17148               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17149                   *p++ = PieceToChar(piece);
17150         }
17151         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17152             piece = boards[move][BOARD_HEIGHT-i-1][0];
17153             if( piece != EmptySquare )
17154               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17155                   *p++ = PieceToChar(piece);
17156         }
17157
17158         if( q == p ) *p++ = '-';
17159         *p++ = ']';
17160         *p++ = ' ';
17161     }
17162
17163     /* Active color */
17164     *p++ = whiteToPlay ? 'w' : 'b';
17165     *p++ = ' ';
17166
17167   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17168     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17169   } else {
17170   if(nrCastlingRights) {
17171      q = p;
17172      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17173        /* [HGM] write directly from rights */
17174            if(boards[move][CASTLING][2] != NoRights &&
17175               boards[move][CASTLING][0] != NoRights   )
17176                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17177            if(boards[move][CASTLING][2] != NoRights &&
17178               boards[move][CASTLING][1] != NoRights   )
17179                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17180            if(boards[move][CASTLING][5] != NoRights &&
17181               boards[move][CASTLING][3] != NoRights   )
17182                 *p++ = boards[move][CASTLING][3] + AAA;
17183            if(boards[move][CASTLING][5] != NoRights &&
17184               boards[move][CASTLING][4] != NoRights   )
17185                 *p++ = boards[move][CASTLING][4] + AAA;
17186      } else {
17187
17188         /* [HGM] write true castling rights */
17189         if( nrCastlingRights == 6 ) {
17190             int q, k=0;
17191             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17192                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17193             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17194                  boards[move][CASTLING][2] != NoRights  );
17195             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17196                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17197                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17198                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17199                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17200             }
17201             if(q) *p++ = 'Q';
17202             k = 0;
17203             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17204                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17205             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17206                  boards[move][CASTLING][5] != NoRights  );
17207             if(gameInfo.variant == VariantSChess) {
17208                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17209                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17210                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17211                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17212             }
17213             if(q) *p++ = 'q';
17214         }
17215      }
17216      if (q == p) *p++ = '-'; /* No castling rights */
17217      *p++ = ' ';
17218   }
17219
17220   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17221      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17222      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17223     /* En passant target square */
17224     if (move > backwardMostMove) {
17225         fromX = moveList[move - 1][0] - AAA;
17226         fromY = moveList[move - 1][1] - ONE;
17227         toX = moveList[move - 1][2] - AAA;
17228         toY = moveList[move - 1][3] - ONE;
17229         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17230             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17231             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17232             fromX == toX) {
17233             /* 2-square pawn move just happened */
17234             *p++ = toX + AAA;
17235             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17236         } else {
17237             *p++ = '-';
17238         }
17239     } else if(move == backwardMostMove) {
17240         // [HGM] perhaps we should always do it like this, and forget the above?
17241         if((signed char)boards[move][EP_STATUS] >= 0) {
17242             *p++ = boards[move][EP_STATUS] + AAA;
17243             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17244         } else {
17245             *p++ = '-';
17246         }
17247     } else {
17248         *p++ = '-';
17249     }
17250     *p++ = ' ';
17251   }
17252   }
17253
17254     if(moveCounts)
17255     {   int i = 0, j=move;
17256
17257         /* [HGM] find reversible plies */
17258         if (appData.debugMode) { int k;
17259             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17260             for(k=backwardMostMove; k<=forwardMostMove; k++)
17261                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17262
17263         }
17264
17265         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17266         if( j == backwardMostMove ) i += initialRulePlies;
17267         sprintf(p, "%d ", i);
17268         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17269
17270         /* Fullmove number */
17271         sprintf(p, "%d", (move / 2) + 1);
17272     } else *--p = NULLCHAR;
17273
17274     return StrSave(buf);
17275 }
17276
17277 Boolean
17278 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17279 {
17280     int i, j;
17281     char *p, c;
17282     int emptycount, virgin[BOARD_FILES];
17283     ChessSquare piece;
17284
17285     p = fen;
17286
17287     /* [HGM] by default clear Crazyhouse holdings, if present */
17288     if(gameInfo.holdingsWidth) {
17289        for(i=0; i<BOARD_HEIGHT; i++) {
17290            board[i][0]             = EmptySquare; /* black holdings */
17291            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17292            board[i][1]             = (ChessSquare) 0; /* black counts */
17293            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17294        }
17295     }
17296
17297     /* Piece placement data */
17298     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17299         j = 0;
17300         for (;;) {
17301             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17302                 if (*p == '/') p++;
17303                 emptycount = gameInfo.boardWidth - j;
17304                 while (emptycount--)
17305                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17306                 break;
17307 #if(BOARD_FILES >= 10)
17308             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17309                 p++; emptycount=10;
17310                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17311                 while (emptycount--)
17312                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17313 #endif
17314             } else if (isdigit(*p)) {
17315                 emptycount = *p++ - '0';
17316                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17317                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17318                 while (emptycount--)
17319                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17320             } else if (*p == '+' || isalpha(*p)) {
17321                 if (j >= gameInfo.boardWidth) return FALSE;
17322                 if(*p=='+') {
17323                     piece = CharToPiece(*++p);
17324                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17325                     piece = (ChessSquare) (PROMOTED piece ); p++;
17326                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17327                 } else piece = CharToPiece(*p++);
17328
17329                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17330                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17331                     piece = (ChessSquare) (PROMOTED piece);
17332                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17333                     p++;
17334                 }
17335                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17336             } else {
17337                 return FALSE;
17338             }
17339         }
17340     }
17341     while (*p == '/' || *p == ' ') p++;
17342
17343     /* [HGM] look for Crazyhouse holdings here */
17344     while(*p==' ') p++;
17345     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17346         if(*p == '[') p++;
17347         if(*p == '-' ) p++; /* empty holdings */ else {
17348             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17349             /* if we would allow FEN reading to set board size, we would   */
17350             /* have to add holdings and shift the board read so far here   */
17351             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17352                 p++;
17353                 if((int) piece >= (int) BlackPawn ) {
17354                     i = (int)piece - (int)BlackPawn;
17355                     i = PieceToNumber((ChessSquare)i);
17356                     if( i >= gameInfo.holdingsSize ) return FALSE;
17357                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17358                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17359                 } else {
17360                     i = (int)piece - (int)WhitePawn;
17361                     i = PieceToNumber((ChessSquare)i);
17362                     if( i >= gameInfo.holdingsSize ) return FALSE;
17363                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17364                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17365                 }
17366             }
17367         }
17368         if(*p == ']') p++;
17369     }
17370
17371     while(*p == ' ') p++;
17372
17373     /* Active color */
17374     c = *p++;
17375     if(appData.colorNickNames) {
17376       if( c == appData.colorNickNames[0] ) c = 'w'; else
17377       if( c == appData.colorNickNames[1] ) c = 'b';
17378     }
17379     switch (c) {
17380       case 'w':
17381         *blackPlaysFirst = FALSE;
17382         break;
17383       case 'b':
17384         *blackPlaysFirst = TRUE;
17385         break;
17386       default:
17387         return FALSE;
17388     }
17389
17390     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17391     /* return the extra info in global variiables             */
17392
17393     /* set defaults in case FEN is incomplete */
17394     board[EP_STATUS] = EP_UNKNOWN;
17395     for(i=0; i<nrCastlingRights; i++ ) {
17396         board[CASTLING][i] =
17397             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17398     }   /* assume possible unless obviously impossible */
17399     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17400     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17401     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17402                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17403     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17404     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17405     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17406                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17407     FENrulePlies = 0;
17408
17409     while(*p==' ') p++;
17410     if(nrCastlingRights) {
17411       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17412       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17413           /* castling indicator present, so default becomes no castlings */
17414           for(i=0; i<nrCastlingRights; i++ ) {
17415                  board[CASTLING][i] = NoRights;
17416           }
17417       }
17418       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17419              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17420              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17421              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17422         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17423
17424         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17425             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17426             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17427         }
17428         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17429             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17430         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17431                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17432         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17433                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17434         switch(c) {
17435           case'K':
17436               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17437               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17438               board[CASTLING][2] = whiteKingFile;
17439               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17440               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17441               break;
17442           case'Q':
17443               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17444               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17445               board[CASTLING][2] = whiteKingFile;
17446               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17447               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17448               break;
17449           case'k':
17450               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17451               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17452               board[CASTLING][5] = blackKingFile;
17453               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17454               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17455               break;
17456           case'q':
17457               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17458               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17459               board[CASTLING][5] = blackKingFile;
17460               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17461               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17462           case '-':
17463               break;
17464           default: /* FRC castlings */
17465               if(c >= 'a') { /* black rights */
17466                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17467                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17468                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17469                   if(i == BOARD_RGHT) break;
17470                   board[CASTLING][5] = i;
17471                   c -= AAA;
17472                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17473                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17474                   if(c > i)
17475                       board[CASTLING][3] = c;
17476                   else
17477                       board[CASTLING][4] = c;
17478               } else { /* white rights */
17479                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17480                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17481                     if(board[0][i] == WhiteKing) break;
17482                   if(i == BOARD_RGHT) break;
17483                   board[CASTLING][2] = i;
17484                   c -= AAA - 'a' + 'A';
17485                   if(board[0][c] >= WhiteKing) break;
17486                   if(c > i)
17487                       board[CASTLING][0] = c;
17488                   else
17489                       board[CASTLING][1] = c;
17490               }
17491         }
17492       }
17493       for(i=0; i<nrCastlingRights; i++)
17494         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17495       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17496     if (appData.debugMode) {
17497         fprintf(debugFP, "FEN castling rights:");
17498         for(i=0; i<nrCastlingRights; i++)
17499         fprintf(debugFP, " %d", board[CASTLING][i]);
17500         fprintf(debugFP, "\n");
17501     }
17502
17503       while(*p==' ') p++;
17504     }
17505
17506     /* read e.p. field in games that know e.p. capture */
17507     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17508        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17509        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17510       if(*p=='-') {
17511         p++; board[EP_STATUS] = EP_NONE;
17512       } else {
17513          char c = *p++ - AAA;
17514
17515          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17516          if(*p >= '0' && *p <='9') p++;
17517          board[EP_STATUS] = c;
17518       }
17519     }
17520
17521
17522     if(sscanf(p, "%d", &i) == 1) {
17523         FENrulePlies = i; /* 50-move ply counter */
17524         /* (The move number is still ignored)    */
17525     }
17526
17527     return TRUE;
17528 }
17529
17530 void
17531 EditPositionPasteFEN (char *fen)
17532 {
17533   if (fen != NULL) {
17534     Board initial_position;
17535
17536     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17537       DisplayError(_("Bad FEN position in clipboard"), 0);
17538       return ;
17539     } else {
17540       int savedBlackPlaysFirst = blackPlaysFirst;
17541       EditPositionEvent();
17542       blackPlaysFirst = savedBlackPlaysFirst;
17543       CopyBoard(boards[0], initial_position);
17544       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17545       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17546       DisplayBothClocks();
17547       DrawPosition(FALSE, boards[currentMove]);
17548     }
17549   }
17550 }
17551
17552 static char cseq[12] = "\\   ";
17553
17554 Boolean
17555 set_cont_sequence (char *new_seq)
17556 {
17557     int len;
17558     Boolean ret;
17559
17560     // handle bad attempts to set the sequence
17561         if (!new_seq)
17562                 return 0; // acceptable error - no debug
17563
17564     len = strlen(new_seq);
17565     ret = (len > 0) && (len < sizeof(cseq));
17566     if (ret)
17567       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17568     else if (appData.debugMode)
17569       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17570     return ret;
17571 }
17572
17573 /*
17574     reformat a source message so words don't cross the width boundary.  internal
17575     newlines are not removed.  returns the wrapped size (no null character unless
17576     included in source message).  If dest is NULL, only calculate the size required
17577     for the dest buffer.  lp argument indicats line position upon entry, and it's
17578     passed back upon exit.
17579 */
17580 int
17581 wrap (char *dest, char *src, int count, int width, int *lp)
17582 {
17583     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17584
17585     cseq_len = strlen(cseq);
17586     old_line = line = *lp;
17587     ansi = len = clen = 0;
17588
17589     for (i=0; i < count; i++)
17590     {
17591         if (src[i] == '\033')
17592             ansi = 1;
17593
17594         // if we hit the width, back up
17595         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17596         {
17597             // store i & len in case the word is too long
17598             old_i = i, old_len = len;
17599
17600             // find the end of the last word
17601             while (i && src[i] != ' ' && src[i] != '\n')
17602             {
17603                 i--;
17604                 len--;
17605             }
17606
17607             // word too long?  restore i & len before splitting it
17608             if ((old_i-i+clen) >= width)
17609             {
17610                 i = old_i;
17611                 len = old_len;
17612             }
17613
17614             // extra space?
17615             if (i && src[i-1] == ' ')
17616                 len--;
17617
17618             if (src[i] != ' ' && src[i] != '\n')
17619             {
17620                 i--;
17621                 if (len)
17622                     len--;
17623             }
17624
17625             // now append the newline and continuation sequence
17626             if (dest)
17627                 dest[len] = '\n';
17628             len++;
17629             if (dest)
17630                 strncpy(dest+len, cseq, cseq_len);
17631             len += cseq_len;
17632             line = cseq_len;
17633             clen = cseq_len;
17634             continue;
17635         }
17636
17637         if (dest)
17638             dest[len] = src[i];
17639         len++;
17640         if (!ansi)
17641             line++;
17642         if (src[i] == '\n')
17643             line = 0;
17644         if (src[i] == 'm')
17645             ansi = 0;
17646     }
17647     if (dest && appData.debugMode)
17648     {
17649         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17650             count, width, line, len, *lp);
17651         show_bytes(debugFP, src, count);
17652         fprintf(debugFP, "\ndest: ");
17653         show_bytes(debugFP, dest, len);
17654         fprintf(debugFP, "\n");
17655     }
17656     *lp = dest ? line : old_line;
17657
17658     return len;
17659 }
17660
17661 // [HGM] vari: routines for shelving variations
17662 Boolean modeRestore = FALSE;
17663
17664 void
17665 PushInner (int firstMove, int lastMove)
17666 {
17667         int i, j, nrMoves = lastMove - firstMove;
17668
17669         // push current tail of game on stack
17670         savedResult[storedGames] = gameInfo.result;
17671         savedDetails[storedGames] = gameInfo.resultDetails;
17672         gameInfo.resultDetails = NULL;
17673         savedFirst[storedGames] = firstMove;
17674         savedLast [storedGames] = lastMove;
17675         savedFramePtr[storedGames] = framePtr;
17676         framePtr -= nrMoves; // reserve space for the boards
17677         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17678             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17679             for(j=0; j<MOVE_LEN; j++)
17680                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17681             for(j=0; j<2*MOVE_LEN; j++)
17682                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17683             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17684             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17685             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17686             pvInfoList[firstMove+i-1].depth = 0;
17687             commentList[framePtr+i] = commentList[firstMove+i];
17688             commentList[firstMove+i] = NULL;
17689         }
17690
17691         storedGames++;
17692         forwardMostMove = firstMove; // truncate game so we can start variation
17693 }
17694
17695 void
17696 PushTail (int firstMove, int lastMove)
17697 {
17698         if(appData.icsActive) { // only in local mode
17699                 forwardMostMove = currentMove; // mimic old ICS behavior
17700                 return;
17701         }
17702         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17703
17704         PushInner(firstMove, lastMove);
17705         if(storedGames == 1) GreyRevert(FALSE);
17706         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17707 }
17708
17709 void
17710 PopInner (Boolean annotate)
17711 {
17712         int i, j, nrMoves;
17713         char buf[8000], moveBuf[20];
17714
17715         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17716         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17717         nrMoves = savedLast[storedGames] - currentMove;
17718         if(annotate) {
17719                 int cnt = 10;
17720                 if(!WhiteOnMove(currentMove))
17721                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17722                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17723                 for(i=currentMove; i<forwardMostMove; i++) {
17724                         if(WhiteOnMove(i))
17725                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17726                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17727                         strcat(buf, moveBuf);
17728                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17729                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17730                 }
17731                 strcat(buf, ")");
17732         }
17733         for(i=1; i<=nrMoves; i++) { // copy last variation back
17734             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17735             for(j=0; j<MOVE_LEN; j++)
17736                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17737             for(j=0; j<2*MOVE_LEN; j++)
17738                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17739             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17740             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17741             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17742             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17743             commentList[currentMove+i] = commentList[framePtr+i];
17744             commentList[framePtr+i] = NULL;
17745         }
17746         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17747         framePtr = savedFramePtr[storedGames];
17748         gameInfo.result = savedResult[storedGames];
17749         if(gameInfo.resultDetails != NULL) {
17750             free(gameInfo.resultDetails);
17751       }
17752         gameInfo.resultDetails = savedDetails[storedGames];
17753         forwardMostMove = currentMove + nrMoves;
17754 }
17755
17756 Boolean
17757 PopTail (Boolean annotate)
17758 {
17759         if(appData.icsActive) return FALSE; // only in local mode
17760         if(!storedGames) return FALSE; // sanity
17761         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17762
17763         PopInner(annotate);
17764         if(currentMove < forwardMostMove) ForwardEvent(); else
17765         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17766
17767         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17768         return TRUE;
17769 }
17770
17771 void
17772 CleanupTail ()
17773 {       // remove all shelved variations
17774         int i;
17775         for(i=0; i<storedGames; i++) {
17776             if(savedDetails[i])
17777                 free(savedDetails[i]);
17778             savedDetails[i] = NULL;
17779         }
17780         for(i=framePtr; i<MAX_MOVES; i++) {
17781                 if(commentList[i]) free(commentList[i]);
17782                 commentList[i] = NULL;
17783         }
17784         framePtr = MAX_MOVES-1;
17785         storedGames = 0;
17786 }
17787
17788 void
17789 LoadVariation (int index, char *text)
17790 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17791         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17792         int level = 0, move;
17793
17794         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17795         // first find outermost bracketing variation
17796         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17797             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17798                 if(*p == '{') wait = '}'; else
17799                 if(*p == '[') wait = ']'; else
17800                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17801                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17802             }
17803             if(*p == wait) wait = NULLCHAR; // closing ]} found
17804             p++;
17805         }
17806         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17807         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17808         end[1] = NULLCHAR; // clip off comment beyond variation
17809         ToNrEvent(currentMove-1);
17810         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17811         // kludge: use ParsePV() to append variation to game
17812         move = currentMove;
17813         ParsePV(start, TRUE, TRUE);
17814         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17815         ClearPremoveHighlights();
17816         CommentPopDown();
17817         ToNrEvent(currentMove+1);
17818 }
17819
17820 void
17821 LoadTheme ()
17822 {
17823     char *p, *q, buf[MSG_SIZ];
17824     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17825         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17826         ParseArgsFromString(buf);
17827         ActivateTheme(TRUE); // also redo colors
17828         return;
17829     }
17830     p = nickName;
17831     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17832     {
17833         int len;
17834         q = appData.themeNames;
17835         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17836       if(appData.useBitmaps) {
17837         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17838                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17839                 appData.liteBackTextureMode,
17840                 appData.darkBackTextureMode );
17841       } else {
17842         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17843                 Col2Text(2),   // lightSquareColor
17844                 Col2Text(3) ); // darkSquareColor
17845       }
17846       if(appData.useBorder) {
17847         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17848                 appData.border);
17849       } else {
17850         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17851       }
17852       if(appData.useFont) {
17853         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17854                 appData.renderPiecesWithFont,
17855                 appData.fontToPieceTable,
17856                 Col2Text(9),    // appData.fontBackColorWhite
17857                 Col2Text(10) ); // appData.fontForeColorBlack
17858       } else {
17859         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17860                 appData.pieceDirectory);
17861         if(!appData.pieceDirectory[0])
17862           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17863                 Col2Text(0),   // whitePieceColor
17864                 Col2Text(1) ); // blackPieceColor
17865       }
17866       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17867                 Col2Text(4),   // highlightSquareColor
17868                 Col2Text(5) ); // premoveHighlightColor
17869         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17870         if(insert != q) insert[-1] = NULLCHAR;
17871         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17872         if(q)   free(q);
17873     }
17874     ActivateTheme(FALSE);
17875 }