Expand number of marker colors to 8
[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++) 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 == 'G') marker[r][f++] = 3; else
7034             if(*fen == 'B') marker[r][f++] = 4; else
7035             if(*fen == 'C') marker[r][f++] = 5; else
7036             if(*fen == 'M') marker[r][f++] = 6; else
7037             if(*fen == 'W') marker[r][f++] = 7; else
7038             if(*fen == 'D') marker[r][f++] = 8; else
7039             if(*fen == 'R') marker[r][f++] = 2; else {
7040                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7041               f += s; fen -= s>0;
7042             }
7043             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7044             if(r < 0) break;
7045             fen++;
7046         }
7047         DrawPosition(TRUE, NULL);
7048 }
7049
7050 void
7051 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7052 {
7053     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7054     Markers *m = (Markers *) closure;
7055     if(rf == fromY && ff == fromX)
7056         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7057                          || kind == WhiteCapturesEnPassant
7058                          || kind == BlackCapturesEnPassant);
7059     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7060 }
7061
7062 void
7063 MarkTargetSquares (int clear)
7064 {
7065   int x, y, sum=0;
7066   if(clear) { // no reason to ever suppress clearing
7067     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7068     if(!sum) return; // nothing was cleared,no redraw needed
7069   } else {
7070     int capt = 0;
7071     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7072        !appData.testLegality || gameMode == EditPosition) return;
7073     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7074     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7075       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7076       if(capt)
7077       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7078     }
7079   }
7080   DrawPosition(FALSE, NULL);
7081 }
7082
7083 int
7084 Explode (Board board, int fromX, int fromY, int toX, int toY)
7085 {
7086     if(gameInfo.variant == VariantAtomic &&
7087        (board[toY][toX] != EmptySquare ||                     // capture?
7088         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7089                          board[fromY][fromX] == BlackPawn   )
7090       )) {
7091         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7092         return TRUE;
7093     }
7094     return FALSE;
7095 }
7096
7097 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7098
7099 int
7100 CanPromote (ChessSquare piece, int y)
7101 {
7102         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7103         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7104         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7105            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7106            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7107          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7108         return (piece == BlackPawn && y == 1 ||
7109                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7110                 piece == BlackLance && y == 1 ||
7111                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7112 }
7113
7114 void ReportClick(char *action, int x, int y)
7115 {
7116         char buf[MSG_SIZ]; // Inform engine of what user does
7117         int r, f;
7118         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7119           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7120         if(!first.highlight || gameMode == EditPosition) return;
7121         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7122         SendToProgram(buf, &first);
7123 }
7124
7125 void
7126 LeftClick (ClickType clickType, int xPix, int yPix)
7127 {
7128     int x, y;
7129     Boolean saveAnimate;
7130     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7131     char promoChoice = NULLCHAR;
7132     ChessSquare piece;
7133     static TimeMark lastClickTime, prevClickTime;
7134
7135     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7136
7137     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7138
7139     if (clickType == Press) ErrorPopDown();
7140
7141     x = EventToSquare(xPix, BOARD_WIDTH);
7142     y = EventToSquare(yPix, BOARD_HEIGHT);
7143     if (!flipView && y >= 0) {
7144         y = BOARD_HEIGHT - 1 - y;
7145     }
7146     if (flipView && x >= 0) {
7147         x = BOARD_WIDTH - 1 - x;
7148     }
7149
7150     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7151         defaultPromoChoice = promoSweep;
7152         promoSweep = EmptySquare;   // terminate sweep
7153         promoDefaultAltered = TRUE;
7154         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7155     }
7156
7157     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7158         if(clickType == Release) return; // ignore upclick of click-click destination
7159         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7160         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7161         if(gameInfo.holdingsWidth &&
7162                 (WhiteOnMove(currentMove)
7163                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7164                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7165             // click in right holdings, for determining promotion piece
7166             ChessSquare p = boards[currentMove][y][x];
7167             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7168             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7169             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7170                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7171                 fromX = fromY = -1;
7172                 return;
7173             }
7174         }
7175         DrawPosition(FALSE, boards[currentMove]);
7176         return;
7177     }
7178
7179     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7180     if(clickType == Press
7181             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7182               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7183               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7184         return;
7185
7186     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7187         // could be static click on premove from-square: abort premove
7188         gotPremove = 0;
7189         ClearPremoveHighlights();
7190     }
7191
7192     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7193         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7194
7195     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7196         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7197                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7198         defaultPromoChoice = DefaultPromoChoice(side);
7199     }
7200
7201     autoQueen = appData.alwaysPromoteToQueen;
7202
7203     if (fromX == -1) {
7204       int originalY = y;
7205       gatingPiece = EmptySquare;
7206       if (clickType != Press) {
7207         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7208             DragPieceEnd(xPix, yPix); dragging = 0;
7209             DrawPosition(FALSE, NULL);
7210         }
7211         return;
7212       }
7213       doubleClick = FALSE;
7214       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7215         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7216       }
7217       fromX = x; fromY = y; toX = toY = -1;
7218       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7219          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7220          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7221             /* First square */
7222             if (OKToStartUserMove(fromX, fromY)) {
7223                 second = 0;
7224                 ReportClick("lift", x, y);
7225                 MarkTargetSquares(0);
7226                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7227                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7228                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7229                     promoSweep = defaultPromoChoice;
7230                     selectFlag = 0; lastX = xPix; lastY = yPix;
7231                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7232                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7233                 }
7234                 if (appData.highlightDragging) {
7235                     SetHighlights(fromX, fromY, -1, -1);
7236                 } else {
7237                     ClearHighlights();
7238                 }
7239             } else fromX = fromY = -1;
7240             return;
7241         }
7242     }
7243
7244     /* fromX != -1 */
7245     if (clickType == Press && gameMode != EditPosition) {
7246         ChessSquare fromP;
7247         ChessSquare toP;
7248         int frc;
7249
7250         // ignore off-board to clicks
7251         if(y < 0 || x < 0) return;
7252
7253         /* Check if clicking again on the same color piece */
7254         fromP = boards[currentMove][fromY][fromX];
7255         toP = boards[currentMove][y][x];
7256         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7257         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7258              WhitePawn <= toP && toP <= WhiteKing &&
7259              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7260              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7261             (BlackPawn <= fromP && fromP <= BlackKing &&
7262              BlackPawn <= toP && toP <= BlackKing &&
7263              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7264              !(fromP == BlackKing && toP == BlackRook && frc))) {
7265             /* Clicked again on same color piece -- changed his mind */
7266             second = (x == fromX && y == fromY);
7267             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7268                 second = FALSE; // first double-click rather than scond click
7269                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7270             }
7271             promoDefaultAltered = FALSE;
7272             MarkTargetSquares(1);
7273            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7274             if (appData.highlightDragging) {
7275                 SetHighlights(x, y, -1, -1);
7276             } else {
7277                 ClearHighlights();
7278             }
7279             if (OKToStartUserMove(x, y)) {
7280                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7281                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7282                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7283                  gatingPiece = boards[currentMove][fromY][fromX];
7284                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7285                 fromX = x;
7286                 fromY = y; dragging = 1;
7287                 ReportClick("lift", x, y);
7288                 MarkTargetSquares(0);
7289                 DragPieceBegin(xPix, yPix, FALSE);
7290                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7291                     promoSweep = defaultPromoChoice;
7292                     selectFlag = 0; lastX = xPix; lastY = yPix;
7293                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7294                 }
7295             }
7296            }
7297            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7298            second = FALSE;
7299         }
7300         // ignore clicks on holdings
7301         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7302     }
7303
7304     if (clickType == Release && x == fromX && y == fromY) {
7305         DragPieceEnd(xPix, yPix); dragging = 0;
7306         if(clearFlag) {
7307             // a deferred attempt to click-click move an empty square on top of a piece
7308             boards[currentMove][y][x] = EmptySquare;
7309             ClearHighlights();
7310             DrawPosition(FALSE, boards[currentMove]);
7311             fromX = fromY = -1; clearFlag = 0;
7312             return;
7313         }
7314         if (appData.animateDragging) {
7315             /* Undo animation damage if any */
7316             DrawPosition(FALSE, NULL);
7317         }
7318         if (second || sweepSelecting) {
7319             /* Second up/down in same square; just abort move */
7320             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7321             second = sweepSelecting = 0;
7322             fromX = fromY = -1;
7323             gatingPiece = EmptySquare;
7324             MarkTargetSquares(1);
7325             ClearHighlights();
7326             gotPremove = 0;
7327             ClearPremoveHighlights();
7328         } else {
7329             /* First upclick in same square; start click-click mode */
7330             SetHighlights(x, y, -1, -1);
7331         }
7332         return;
7333     }
7334
7335     clearFlag = 0;
7336
7337     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7338         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7339         DisplayMessage(_("only marked squares are legal"),"");
7340         DrawPosition(TRUE, NULL);
7341         return; // ignore to-click
7342     }
7343
7344     /* we now have a different from- and (possibly off-board) to-square */
7345     /* Completed move */
7346     if(!sweepSelecting) {
7347         toX = x;
7348         toY = y;
7349     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7350
7351     saveAnimate = appData.animate;
7352     if (clickType == Press) {
7353         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7354             // must be Edit Position mode with empty-square selected
7355             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7356             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7357             return;
7358         }
7359         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7360           if(appData.sweepSelect) {
7361             ChessSquare piece = boards[currentMove][fromY][fromX];
7362             promoSweep = defaultPromoChoice;
7363             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7364             selectFlag = 0; lastX = xPix; lastY = yPix;
7365             Sweep(0); // Pawn that is going to promote: preview promotion piece
7366             sweepSelecting = 1;
7367             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7368             MarkTargetSquares(1);
7369           }
7370           return; // promo popup appears on up-click
7371         }
7372         /* Finish clickclick move */
7373         if (appData.animate || appData.highlightLastMove) {
7374             SetHighlights(fromX, fromY, toX, toY);
7375         } else {
7376             ClearHighlights();
7377         }
7378     } else {
7379 #if 0
7380 // [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
7381         /* Finish drag move */
7382         if (appData.highlightLastMove) {
7383             SetHighlights(fromX, fromY, toX, toY);
7384         } else {
7385             ClearHighlights();
7386         }
7387 #endif
7388         DragPieceEnd(xPix, yPix); dragging = 0;
7389         /* Don't animate move and drag both */
7390         appData.animate = FALSE;
7391     }
7392
7393     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7394     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7395         ChessSquare piece = boards[currentMove][fromY][fromX];
7396         if(gameMode == EditPosition && piece != EmptySquare &&
7397            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7398             int n;
7399
7400             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7401                 n = PieceToNumber(piece - (int)BlackPawn);
7402                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7403                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7404                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7405             } else
7406             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7407                 n = PieceToNumber(piece);
7408                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7409                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7410                 boards[currentMove][n][BOARD_WIDTH-2]++;
7411             }
7412             boards[currentMove][fromY][fromX] = EmptySquare;
7413         }
7414         ClearHighlights();
7415         fromX = fromY = -1;
7416         MarkTargetSquares(1);
7417         DrawPosition(TRUE, boards[currentMove]);
7418         return;
7419     }
7420
7421     // off-board moves should not be highlighted
7422     if(x < 0 || y < 0) ClearHighlights();
7423     else ReportClick("put", x, y);
7424
7425     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7426
7427     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7428         SetHighlights(fromX, fromY, toX, toY);
7429         MarkTargetSquares(1);
7430         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7431             // [HGM] super: promotion to captured piece selected from holdings
7432             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7433             promotionChoice = TRUE;
7434             // kludge follows to temporarily execute move on display, without promoting yet
7435             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7436             boards[currentMove][toY][toX] = p;
7437             DrawPosition(FALSE, boards[currentMove]);
7438             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7439             boards[currentMove][toY][toX] = q;
7440             DisplayMessage("Click in holdings to choose piece", "");
7441             return;
7442         }
7443         PromotionPopUp();
7444     } else {
7445         int oldMove = currentMove;
7446         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7447         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7448         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7449         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7450            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7451             DrawPosition(TRUE, boards[currentMove]);
7452         MarkTargetSquares(1);
7453         fromX = fromY = -1;
7454     }
7455     appData.animate = saveAnimate;
7456     if (appData.animate || appData.animateDragging) {
7457         /* Undo animation damage if needed */
7458         DrawPosition(FALSE, NULL);
7459     }
7460 }
7461
7462 int
7463 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7464 {   // front-end-free part taken out of PieceMenuPopup
7465     int whichMenu; int xSqr, ySqr;
7466
7467     if(seekGraphUp) { // [HGM] seekgraph
7468         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7469         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7470         return -2;
7471     }
7472
7473     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7474          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7475         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7476         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7477         if(action == Press)   {
7478             originalFlip = flipView;
7479             flipView = !flipView; // temporarily flip board to see game from partners perspective
7480             DrawPosition(TRUE, partnerBoard);
7481             DisplayMessage(partnerStatus, "");
7482             partnerUp = TRUE;
7483         } else if(action == Release) {
7484             flipView = originalFlip;
7485             DrawPosition(TRUE, boards[currentMove]);
7486             partnerUp = FALSE;
7487         }
7488         return -2;
7489     }
7490
7491     xSqr = EventToSquare(x, BOARD_WIDTH);
7492     ySqr = EventToSquare(y, BOARD_HEIGHT);
7493     if (action == Release) {
7494         if(pieceSweep != EmptySquare) {
7495             EditPositionMenuEvent(pieceSweep, toX, toY);
7496             pieceSweep = EmptySquare;
7497         } else UnLoadPV(); // [HGM] pv
7498     }
7499     if (action != Press) return -2; // return code to be ignored
7500     switch (gameMode) {
7501       case IcsExamining:
7502         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7503       case EditPosition:
7504         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7505         if (xSqr < 0 || ySqr < 0) return -1;
7506         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7507         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7508         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7509         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7510         NextPiece(0);
7511         return 2; // grab
7512       case IcsObserving:
7513         if(!appData.icsEngineAnalyze) return -1;
7514       case IcsPlayingWhite:
7515       case IcsPlayingBlack:
7516         if(!appData.zippyPlay) goto noZip;
7517       case AnalyzeMode:
7518       case AnalyzeFile:
7519       case MachinePlaysWhite:
7520       case MachinePlaysBlack:
7521       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7522         if (!appData.dropMenu) {
7523           LoadPV(x, y);
7524           return 2; // flag front-end to grab mouse events
7525         }
7526         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7527            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7528       case EditGame:
7529       noZip:
7530         if (xSqr < 0 || ySqr < 0) return -1;
7531         if (!appData.dropMenu || appData.testLegality &&
7532             gameInfo.variant != VariantBughouse &&
7533             gameInfo.variant != VariantCrazyhouse) return -1;
7534         whichMenu = 1; // drop menu
7535         break;
7536       default:
7537         return -1;
7538     }
7539
7540     if (((*fromX = xSqr) < 0) ||
7541         ((*fromY = ySqr) < 0)) {
7542         *fromX = *fromY = -1;
7543         return -1;
7544     }
7545     if (flipView)
7546       *fromX = BOARD_WIDTH - 1 - *fromX;
7547     else
7548       *fromY = BOARD_HEIGHT - 1 - *fromY;
7549
7550     return whichMenu;
7551 }
7552
7553 void
7554 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7555 {
7556 //    char * hint = lastHint;
7557     FrontEndProgramStats stats;
7558
7559     stats.which = cps == &first ? 0 : 1;
7560     stats.depth = cpstats->depth;
7561     stats.nodes = cpstats->nodes;
7562     stats.score = cpstats->score;
7563     stats.time = cpstats->time;
7564     stats.pv = cpstats->movelist;
7565     stats.hint = lastHint;
7566     stats.an_move_index = 0;
7567     stats.an_move_count = 0;
7568
7569     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7570         stats.hint = cpstats->move_name;
7571         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7572         stats.an_move_count = cpstats->nr_moves;
7573     }
7574
7575     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
7576
7577     SetProgramStats( &stats );
7578 }
7579
7580 void
7581 ClearEngineOutputPane (int which)
7582 {
7583     static FrontEndProgramStats dummyStats;
7584     dummyStats.which = which;
7585     dummyStats.pv = "#";
7586     SetProgramStats( &dummyStats );
7587 }
7588
7589 #define MAXPLAYERS 500
7590
7591 char *
7592 TourneyStandings (int display)
7593 {
7594     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7595     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7596     char result, *p, *names[MAXPLAYERS];
7597
7598     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7599         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7600     names[0] = p = strdup(appData.participants);
7601     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7602
7603     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7604
7605     while(result = appData.results[nr]) {
7606         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7607         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7608         wScore = bScore = 0;
7609         switch(result) {
7610           case '+': wScore = 2; break;
7611           case '-': bScore = 2; break;
7612           case '=': wScore = bScore = 1; break;
7613           case ' ':
7614           case '*': return strdup("busy"); // tourney not finished
7615         }
7616         score[w] += wScore;
7617         score[b] += bScore;
7618         games[w]++;
7619         games[b]++;
7620         nr++;
7621     }
7622     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7623     for(w=0; w<nPlayers; w++) {
7624         bScore = -1;
7625         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7626         ranking[w] = b; points[w] = bScore; score[b] = -2;
7627     }
7628     p = malloc(nPlayers*34+1);
7629     for(w=0; w<nPlayers && w<display; w++)
7630         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7631     free(names[0]);
7632     return p;
7633 }
7634
7635 void
7636 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7637 {       // count all piece types
7638         int p, f, r;
7639         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7640         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7641         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7642                 p = board[r][f];
7643                 pCnt[p]++;
7644                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7645                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7646                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7647                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7648                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7649                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7650         }
7651 }
7652
7653 int
7654 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7655 {
7656         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7657         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7658
7659         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7660         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7661         if(myPawns == 2 && nMine == 3) // KPP
7662             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7663         if(myPawns == 1 && nMine == 2) // KP
7664             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7665         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7666             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7667         if(myPawns) return FALSE;
7668         if(pCnt[WhiteRook+side])
7669             return pCnt[BlackRook-side] ||
7670                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7671                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7672                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7673         if(pCnt[WhiteCannon+side]) {
7674             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7675             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7676         }
7677         if(pCnt[WhiteKnight+side])
7678             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7679         return FALSE;
7680 }
7681
7682 int
7683 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7684 {
7685         VariantClass v = gameInfo.variant;
7686
7687         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7688         if(v == VariantShatranj) return TRUE; // always winnable through baring
7689         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7690         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7691
7692         if(v == VariantXiangqi) {
7693                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7694
7695                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7696                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7697                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7698                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7699                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7700                 if(stale) // we have at least one last-rank P plus perhaps C
7701                     return majors // KPKX
7702                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7703                 else // KCA*E*
7704                     return pCnt[WhiteFerz+side] // KCAK
7705                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7706                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7707                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7708
7709         } else if(v == VariantKnightmate) {
7710                 if(nMine == 1) return FALSE;
7711                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7712         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7713                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7714
7715                 if(nMine == 1) return FALSE; // bare King
7716                 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
7717                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7718                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7719                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7720                 if(pCnt[WhiteKnight+side])
7721                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7722                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7723                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7724                 if(nBishops)
7725                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7726                 if(pCnt[WhiteAlfil+side])
7727                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7728                 if(pCnt[WhiteWazir+side])
7729                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7730         }
7731
7732         return TRUE;
7733 }
7734
7735 int
7736 CompareWithRights (Board b1, Board b2)
7737 {
7738     int rights = 0;
7739     if(!CompareBoards(b1, b2)) return FALSE;
7740     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7741     /* compare castling rights */
7742     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7743            rights++; /* King lost rights, while rook still had them */
7744     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7745         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7746            rights++; /* but at least one rook lost them */
7747     }
7748     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7749            rights++;
7750     if( b1[CASTLING][5] != NoRights ) {
7751         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7752            rights++;
7753     }
7754     return rights == 0;
7755 }
7756
7757 int
7758 Adjudicate (ChessProgramState *cps)
7759 {       // [HGM] some adjudications useful with buggy engines
7760         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7761         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7762         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7763         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7764         int k, drop, count = 0; static int bare = 1;
7765         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7766         Boolean canAdjudicate = !appData.icsActive;
7767
7768         // most tests only when we understand the game, i.e. legality-checking on
7769             if( appData.testLegality )
7770             {   /* [HGM] Some more adjudications for obstinate engines */
7771                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7772                 static int moveCount = 6;
7773                 ChessMove result;
7774                 char *reason = NULL;
7775
7776                 /* Count what is on board. */
7777                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7778
7779                 /* Some material-based adjudications that have to be made before stalemate test */
7780                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7781                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7782                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7783                      if(canAdjudicate && appData.checkMates) {
7784                          if(engineOpponent)
7785                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7786                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7787                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7788                          return 1;
7789                      }
7790                 }
7791
7792                 /* Bare King in Shatranj (loses) or Losers (wins) */
7793                 if( nrW == 1 || nrB == 1) {
7794                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7795                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7796                      if(canAdjudicate && appData.checkMates) {
7797                          if(engineOpponent)
7798                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7799                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7800                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7801                          return 1;
7802                      }
7803                   } else
7804                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7805                   {    /* bare King */
7806                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7807                         if(canAdjudicate && appData.checkMates) {
7808                             /* but only adjudicate if adjudication enabled */
7809                             if(engineOpponent)
7810                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7811                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7812                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7813                             return 1;
7814                         }
7815                   }
7816                 } else bare = 1;
7817
7818
7819             // don't wait for engine to announce game end if we can judge ourselves
7820             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7821               case MT_CHECK:
7822                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7823                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7824                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7825                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7826                             checkCnt++;
7827                         if(checkCnt >= 2) {
7828                             reason = "Xboard adjudication: 3rd check";
7829                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7830                             break;
7831                         }
7832                     }
7833                 }
7834               case MT_NONE:
7835               default:
7836                 break;
7837               case MT_STALEMATE:
7838               case MT_STAINMATE:
7839                 reason = "Xboard adjudication: Stalemate";
7840                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7841                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7842                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7843                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7844                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7845                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7846                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7847                                                                         EP_CHECKMATE : EP_WINS);
7848                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7849                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7850                 }
7851                 break;
7852               case MT_CHECKMATE:
7853                 reason = "Xboard adjudication: Checkmate";
7854                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7855                 if(gameInfo.variant == VariantShogi) {
7856                     if(forwardMostMove > backwardMostMove
7857                        && moveList[forwardMostMove-1][1] == '@'
7858                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7859                         reason = "XBoard adjudication: pawn-drop mate";
7860                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7861                     }
7862                 }
7863                 break;
7864             }
7865
7866                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7867                     case EP_STALEMATE:
7868                         result = GameIsDrawn; break;
7869                     case EP_CHECKMATE:
7870                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7871                     case EP_WINS:
7872                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7873                     default:
7874                         result = EndOfFile;
7875                 }
7876                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7877                     if(engineOpponent)
7878                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7879                     GameEnds( result, reason, GE_XBOARD );
7880                     return 1;
7881                 }
7882
7883                 /* Next absolutely insufficient mating material. */
7884                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7885                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7886                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7887
7888                      /* always flag draws, for judging claims */
7889                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7890
7891                      if(canAdjudicate && appData.materialDraws) {
7892                          /* but only adjudicate them if adjudication enabled */
7893                          if(engineOpponent) {
7894                            SendToProgram("force\n", engineOpponent); // suppress reply
7895                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7896                          }
7897                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7898                          return 1;
7899                      }
7900                 }
7901
7902                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7903                 if(gameInfo.variant == VariantXiangqi ?
7904                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7905                  : nrW + nrB == 4 &&
7906                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7907                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7908                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7909                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7910                    ) ) {
7911                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7912                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7913                           if(engineOpponent) {
7914                             SendToProgram("force\n", engineOpponent); // suppress reply
7915                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7916                           }
7917                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7918                           return 1;
7919                      }
7920                 } else moveCount = 6;
7921             }
7922
7923         // Repetition draws and 50-move rule can be applied independently of legality testing
7924
7925                 /* Check for rep-draws */
7926                 count = 0;
7927                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7928                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7929                 for(k = forwardMostMove-2;
7930                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7931                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7932                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7933                     k-=2)
7934                 {   int rights=0;
7935                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7936                         /* compare castling rights */
7937                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7938                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7939                                 rights++; /* King lost rights, while rook still had them */
7940                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7941                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7942                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7943                                    rights++; /* but at least one rook lost them */
7944                         }
7945                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7946                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7947                                 rights++;
7948                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7949                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7950                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7951                                    rights++;
7952                         }
7953                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7954                             && appData.drawRepeats > 1) {
7955                              /* adjudicate after user-specified nr of repeats */
7956                              int result = GameIsDrawn;
7957                              char *details = "XBoard adjudication: repetition draw";
7958                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7959                                 // [HGM] xiangqi: check for forbidden perpetuals
7960                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7961                                 for(m=forwardMostMove; m>k; m-=2) {
7962                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7963                                         ourPerpetual = 0; // the current mover did not always check
7964                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7965                                         hisPerpetual = 0; // the opponent did not always check
7966                                 }
7967                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7968                                                                         ourPerpetual, hisPerpetual);
7969                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7970                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7971                                     details = "Xboard adjudication: perpetual checking";
7972                                 } else
7973                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7974                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7975                                 } else
7976                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7977                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7978                                         result = BlackWins;
7979                                         details = "Xboard adjudication: repetition";
7980                                     }
7981                                 } else // it must be XQ
7982                                 // Now check for perpetual chases
7983                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7984                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7985                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7986                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7987                                         static char resdet[MSG_SIZ];
7988                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7989                                         details = resdet;
7990                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7991                                     } else
7992                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7993                                         break; // Abort repetition-checking loop.
7994                                 }
7995                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7996                              }
7997                              if(engineOpponent) {
7998                                SendToProgram("force\n", engineOpponent); // suppress reply
7999                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8000                              }
8001                              GameEnds( result, details, GE_XBOARD );
8002                              return 1;
8003                         }
8004                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8005                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8006                     }
8007                 }
8008
8009                 /* Now we test for 50-move draws. Determine ply count */
8010                 count = forwardMostMove;
8011                 /* look for last irreversble move */
8012                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8013                     count--;
8014                 /* if we hit starting position, add initial plies */
8015                 if( count == backwardMostMove )
8016                     count -= initialRulePlies;
8017                 count = forwardMostMove - count;
8018                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8019                         // adjust reversible move counter for checks in Xiangqi
8020                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8021                         if(i < backwardMostMove) i = backwardMostMove;
8022                         while(i <= forwardMostMove) {
8023                                 lastCheck = inCheck; // check evasion does not count
8024                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8025                                 if(inCheck || lastCheck) count--; // check does not count
8026                                 i++;
8027                         }
8028                 }
8029                 if( count >= 100)
8030                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8031                          /* this is used to judge if draw claims are legal */
8032                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8033                          if(engineOpponent) {
8034                            SendToProgram("force\n", engineOpponent); // suppress reply
8035                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8036                          }
8037                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8038                          return 1;
8039                 }
8040
8041                 /* if draw offer is pending, treat it as a draw claim
8042                  * when draw condition present, to allow engines a way to
8043                  * claim draws before making their move to avoid a race
8044                  * condition occurring after their move
8045                  */
8046                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8047                          char *p = NULL;
8048                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8049                              p = "Draw claim: 50-move rule";
8050                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8051                              p = "Draw claim: 3-fold repetition";
8052                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8053                              p = "Draw claim: insufficient mating material";
8054                          if( p != NULL && canAdjudicate) {
8055                              if(engineOpponent) {
8056                                SendToProgram("force\n", engineOpponent); // suppress reply
8057                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8058                              }
8059                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8060                              return 1;
8061                          }
8062                 }
8063
8064                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8065                     if(engineOpponent) {
8066                       SendToProgram("force\n", engineOpponent); // suppress reply
8067                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8068                     }
8069                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8070                     return 1;
8071                 }
8072         return 0;
8073 }
8074
8075 char *
8076 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8077 {   // [HGM] book: this routine intercepts moves to simulate book replies
8078     char *bookHit = NULL;
8079
8080     //first determine if the incoming move brings opponent into his book
8081     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8082         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8083     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8084     if(bookHit != NULL && !cps->bookSuspend) {
8085         // make sure opponent is not going to reply after receiving move to book position
8086         SendToProgram("force\n", cps);
8087         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8088     }
8089     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8090     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8091     // now arrange restart after book miss
8092     if(bookHit) {
8093         // after a book hit we never send 'go', and the code after the call to this routine
8094         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8095         char buf[MSG_SIZ], *move = bookHit;
8096         if(cps->useSAN) {
8097             int fromX, fromY, toX, toY;
8098             char promoChar;
8099             ChessMove moveType;
8100             move = buf + 30;
8101             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8102                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8103                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8104                                     PosFlags(forwardMostMove),
8105                                     fromY, fromX, toY, toX, promoChar, move);
8106             } else {
8107                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8108                 bookHit = NULL;
8109             }
8110         }
8111         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8112         SendToProgram(buf, cps);
8113         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8114     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8115         SendToProgram("go\n", cps);
8116         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8117     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8118         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8119             SendToProgram("go\n", cps);
8120         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8121     }
8122     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8123 }
8124
8125 int
8126 LoadError (char *errmess, ChessProgramState *cps)
8127 {   // unloads engine and switches back to -ncp mode if it was first
8128     if(cps->initDone) return FALSE;
8129     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8130     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8131     cps->pr = NoProc;
8132     if(cps == &first) {
8133         appData.noChessProgram = TRUE;
8134         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8135         gameMode = BeginningOfGame; ModeHighlight();
8136         SetNCPMode();
8137     }
8138     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8139     DisplayMessage("", ""); // erase waiting message
8140     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8141     return TRUE;
8142 }
8143
8144 char *savedMessage;
8145 ChessProgramState *savedState;
8146 void
8147 DeferredBookMove (void)
8148 {
8149         if(savedState->lastPing != savedState->lastPong)
8150                     ScheduleDelayedEvent(DeferredBookMove, 10);
8151         else
8152         HandleMachineMove(savedMessage, savedState);
8153 }
8154
8155 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8156 static ChessProgramState *stalledEngine;
8157 static char stashedInputMove[MSG_SIZ];
8158
8159 void
8160 HandleMachineMove (char *message, ChessProgramState *cps)
8161 {
8162     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8163     char realname[MSG_SIZ];
8164     int fromX, fromY, toX, toY;
8165     ChessMove moveType;
8166     char promoChar;
8167     char *p, *pv=buf1;
8168     int machineWhite, oldError;
8169     char *bookHit;
8170
8171     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8172         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8173         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8174             DisplayError(_("Invalid pairing from pairing engine"), 0);
8175             return;
8176         }
8177         pairingReceived = 1;
8178         NextMatchGame();
8179         return; // Skim the pairing messages here.
8180     }
8181
8182     oldError = cps->userError; cps->userError = 0;
8183
8184 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8185     /*
8186      * Kludge to ignore BEL characters
8187      */
8188     while (*message == '\007') message++;
8189
8190     /*
8191      * [HGM] engine debug message: ignore lines starting with '#' character
8192      */
8193     if(cps->debug && *message == '#') return;
8194
8195     /*
8196      * Look for book output
8197      */
8198     if (cps == &first && bookRequested) {
8199         if (message[0] == '\t' || message[0] == ' ') {
8200             /* Part of the book output is here; append it */
8201             strcat(bookOutput, message);
8202             strcat(bookOutput, "  \n");
8203             return;
8204         } else if (bookOutput[0] != NULLCHAR) {
8205             /* All of book output has arrived; display it */
8206             char *p = bookOutput;
8207             while (*p != NULLCHAR) {
8208                 if (*p == '\t') *p = ' ';
8209                 p++;
8210             }
8211             DisplayInformation(bookOutput);
8212             bookRequested = FALSE;
8213             /* Fall through to parse the current output */
8214         }
8215     }
8216
8217     /*
8218      * Look for machine move.
8219      */
8220     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8221         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8222     {
8223         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8224             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8225             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8226             stalledEngine = cps;
8227             if(appData.ponderNextMove) { // bring opponent out of ponder
8228                 if(gameMode == TwoMachinesPlay) {
8229                     if(cps->other->pause)
8230                         PauseEngine(cps->other);
8231                     else
8232                         SendToProgram("easy\n", cps->other);
8233                 }
8234             }
8235             StopClocks();
8236             return;
8237         }
8238
8239         /* This method is only useful on engines that support ping */
8240         if (cps->lastPing != cps->lastPong) {
8241           if (gameMode == BeginningOfGame) {
8242             /* Extra move from before last new; ignore */
8243             if (appData.debugMode) {
8244                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8245             }
8246           } else {
8247             if (appData.debugMode) {
8248                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8249                         cps->which, gameMode);
8250             }
8251
8252             SendToProgram("undo\n", cps);
8253           }
8254           return;
8255         }
8256
8257         switch (gameMode) {
8258           case BeginningOfGame:
8259             /* Extra move from before last reset; ignore */
8260             if (appData.debugMode) {
8261                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8262             }
8263             return;
8264
8265           case EndOfGame:
8266           case IcsIdle:
8267           default:
8268             /* Extra move after we tried to stop.  The mode test is
8269                not a reliable way of detecting this problem, but it's
8270                the best we can do on engines that don't support ping.
8271             */
8272             if (appData.debugMode) {
8273                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8274                         cps->which, gameMode);
8275             }
8276             SendToProgram("undo\n", cps);
8277             return;
8278
8279           case MachinePlaysWhite:
8280           case IcsPlayingWhite:
8281             machineWhite = TRUE;
8282             break;
8283
8284           case MachinePlaysBlack:
8285           case IcsPlayingBlack:
8286             machineWhite = FALSE;
8287             break;
8288
8289           case TwoMachinesPlay:
8290             machineWhite = (cps->twoMachinesColor[0] == 'w');
8291             break;
8292         }
8293         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8294             if (appData.debugMode) {
8295                 fprintf(debugFP,
8296                         "Ignoring move out of turn by %s, gameMode %d"
8297                         ", forwardMost %d\n",
8298                         cps->which, gameMode, forwardMostMove);
8299             }
8300             return;
8301         }
8302
8303         if(cps->alphaRank) AlphaRank(machineMove, 4);
8304         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8305                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8306             /* Machine move could not be parsed; ignore it. */
8307           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8308                     machineMove, _(cps->which));
8309             DisplayMoveError(buf1);
8310             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8311                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8312             if (gameMode == TwoMachinesPlay) {
8313               GameEnds(machineWhite ? BlackWins : WhiteWins,
8314                        buf1, GE_XBOARD);
8315             }
8316             return;
8317         }
8318
8319         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8320         /* So we have to redo legality test with true e.p. status here,  */
8321         /* to make sure an illegal e.p. capture does not slip through,   */
8322         /* to cause a forfeit on a justified illegal-move complaint      */
8323         /* of the opponent.                                              */
8324         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8325            ChessMove moveType;
8326            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8327                              fromY, fromX, toY, toX, promoChar);
8328             if(moveType == IllegalMove) {
8329               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8330                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8331                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8332                            buf1, GE_XBOARD);
8333                 return;
8334            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8335            /* [HGM] Kludge to handle engines that send FRC-style castling
8336               when they shouldn't (like TSCP-Gothic) */
8337            switch(moveType) {
8338              case WhiteASideCastleFR:
8339              case BlackASideCastleFR:
8340                toX+=2;
8341                currentMoveString[2]++;
8342                break;
8343              case WhiteHSideCastleFR:
8344              case BlackHSideCastleFR:
8345                toX--;
8346                currentMoveString[2]--;
8347                break;
8348              default: ; // nothing to do, but suppresses warning of pedantic compilers
8349            }
8350         }
8351         hintRequested = FALSE;
8352         lastHint[0] = NULLCHAR;
8353         bookRequested = FALSE;
8354         /* Program may be pondering now */
8355         cps->maybeThinking = TRUE;
8356         if (cps->sendTime == 2) cps->sendTime = 1;
8357         if (cps->offeredDraw) cps->offeredDraw--;
8358
8359         /* [AS] Save move info*/
8360         pvInfoList[ forwardMostMove ].score = programStats.score;
8361         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8362         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8363
8364         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8365
8366         /* Test suites abort the 'game' after one move */
8367         if(*appData.finger) {
8368            static FILE *f;
8369            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8370            if(!f) f = fopen(appData.finger, "w");
8371            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8372            else { DisplayFatalError("Bad output file", errno, 0); return; }
8373            free(fen);
8374            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8375         }
8376
8377         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8378         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8379             int count = 0;
8380
8381             while( count < adjudicateLossPlies ) {
8382                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8383
8384                 if( count & 1 ) {
8385                     score = -score; /* Flip score for winning side */
8386                 }
8387
8388                 if( score > adjudicateLossThreshold ) {
8389                     break;
8390                 }
8391
8392                 count++;
8393             }
8394
8395             if( count >= adjudicateLossPlies ) {
8396                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8397
8398                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8399                     "Xboard adjudication",
8400                     GE_XBOARD );
8401
8402                 return;
8403             }
8404         }
8405
8406         if(Adjudicate(cps)) {
8407             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8408             return; // [HGM] adjudicate: for all automatic game ends
8409         }
8410
8411 #if ZIPPY
8412         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8413             first.initDone) {
8414           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8415                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8416                 SendToICS("draw ");
8417                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8418           }
8419           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8420           ics_user_moved = 1;
8421           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8422                 char buf[3*MSG_SIZ];
8423
8424                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8425                         programStats.score / 100.,
8426                         programStats.depth,
8427                         programStats.time / 100.,
8428                         (unsigned int)programStats.nodes,
8429                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8430                         programStats.movelist);
8431                 SendToICS(buf);
8432 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8433           }
8434         }
8435 #endif
8436
8437         /* [AS] Clear stats for next move */
8438         ClearProgramStats();
8439         thinkOutput[0] = NULLCHAR;
8440         hiddenThinkOutputState = 0;
8441
8442         bookHit = NULL;
8443         if (gameMode == TwoMachinesPlay) {
8444             /* [HGM] relaying draw offers moved to after reception of move */
8445             /* and interpreting offer as claim if it brings draw condition */
8446             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8447                 SendToProgram("draw\n", cps->other);
8448             }
8449             if (cps->other->sendTime) {
8450                 SendTimeRemaining(cps->other,
8451                                   cps->other->twoMachinesColor[0] == 'w');
8452             }
8453             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8454             if (firstMove && !bookHit) {
8455                 firstMove = FALSE;
8456                 if (cps->other->useColors) {
8457                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8458                 }
8459                 SendToProgram("go\n", cps->other);
8460             }
8461             cps->other->maybeThinking = TRUE;
8462         }
8463
8464         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8465
8466         if (!pausing && appData.ringBellAfterMoves) {
8467             RingBell();
8468         }
8469
8470         /*
8471          * Reenable menu items that were disabled while
8472          * machine was thinking
8473          */
8474         if (gameMode != TwoMachinesPlay)
8475             SetUserThinkingEnables();
8476
8477         // [HGM] book: after book hit opponent has received move and is now in force mode
8478         // force the book reply into it, and then fake that it outputted this move by jumping
8479         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8480         if(bookHit) {
8481                 static char bookMove[MSG_SIZ]; // a bit generous?
8482
8483                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8484                 strcat(bookMove, bookHit);
8485                 message = bookMove;
8486                 cps = cps->other;
8487                 programStats.nodes = programStats.depth = programStats.time =
8488                 programStats.score = programStats.got_only_move = 0;
8489                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8490
8491                 if(cps->lastPing != cps->lastPong) {
8492                     savedMessage = message; // args for deferred call
8493                     savedState = cps;
8494                     ScheduleDelayedEvent(DeferredBookMove, 10);
8495                     return;
8496                 }
8497                 goto FakeBookMove;
8498         }
8499
8500         return;
8501     }
8502
8503     /* Set special modes for chess engines.  Later something general
8504      *  could be added here; for now there is just one kludge feature,
8505      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8506      *  when "xboard" is given as an interactive command.
8507      */
8508     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8509         cps->useSigint = FALSE;
8510         cps->useSigterm = FALSE;
8511     }
8512     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8513       ParseFeatures(message+8, cps);
8514       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8515     }
8516
8517     if (!strncmp(message, "setup ", 6) && 
8518         (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
8519                                         ) { // [HGM] allow first engine to define opening position
8520       int dummy, s=6; char buf[MSG_SIZ];
8521       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8522       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8523       if(startedFromSetupPosition) return;
8524       if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
8525       ParseFEN(boards[0], &dummy, message+s);
8526       DrawPosition(TRUE, boards[0]);
8527       startedFromSetupPosition = TRUE;
8528       return;
8529     }
8530     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8531      * want this, I was asked to put it in, and obliged.
8532      */
8533     if (!strncmp(message, "setboard ", 9)) {
8534         Board initial_position;
8535
8536         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8537
8538         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8539             DisplayError(_("Bad FEN received from engine"), 0);
8540             return ;
8541         } else {
8542            Reset(TRUE, FALSE);
8543            CopyBoard(boards[0], initial_position);
8544            initialRulePlies = FENrulePlies;
8545            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8546            else gameMode = MachinePlaysBlack;
8547            DrawPosition(FALSE, boards[currentMove]);
8548         }
8549         return;
8550     }
8551
8552     /*
8553      * Look for communication commands
8554      */
8555     if (!strncmp(message, "telluser ", 9)) {
8556         if(message[9] == '\\' && message[10] == '\\')
8557             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8558         PlayTellSound();
8559         DisplayNote(message + 9);
8560         return;
8561     }
8562     if (!strncmp(message, "tellusererror ", 14)) {
8563         cps->userError = 1;
8564         if(message[14] == '\\' && message[15] == '\\')
8565             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8566         PlayTellSound();
8567         DisplayError(message + 14, 0);
8568         return;
8569     }
8570     if (!strncmp(message, "tellopponent ", 13)) {
8571       if (appData.icsActive) {
8572         if (loggedOn) {
8573           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8574           SendToICS(buf1);
8575         }
8576       } else {
8577         DisplayNote(message + 13);
8578       }
8579       return;
8580     }
8581     if (!strncmp(message, "tellothers ", 11)) {
8582       if (appData.icsActive) {
8583         if (loggedOn) {
8584           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8585           SendToICS(buf1);
8586         }
8587       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8588       return;
8589     }
8590     if (!strncmp(message, "tellall ", 8)) {
8591       if (appData.icsActive) {
8592         if (loggedOn) {
8593           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8594           SendToICS(buf1);
8595         }
8596       } else {
8597         DisplayNote(message + 8);
8598       }
8599       return;
8600     }
8601     if (strncmp(message, "warning", 7) == 0) {
8602         /* Undocumented feature, use tellusererror in new code */
8603         DisplayError(message, 0);
8604         return;
8605     }
8606     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8607         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8608         strcat(realname, " query");
8609         AskQuestion(realname, buf2, buf1, cps->pr);
8610         return;
8611     }
8612     /* Commands from the engine directly to ICS.  We don't allow these to be
8613      *  sent until we are logged on. Crafty kibitzes have been known to
8614      *  interfere with the login process.
8615      */
8616     if (loggedOn) {
8617         if (!strncmp(message, "tellics ", 8)) {
8618             SendToICS(message + 8);
8619             SendToICS("\n");
8620             return;
8621         }
8622         if (!strncmp(message, "tellicsnoalias ", 15)) {
8623             SendToICS(ics_prefix);
8624             SendToICS(message + 15);
8625             SendToICS("\n");
8626             return;
8627         }
8628         /* The following are for backward compatibility only */
8629         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8630             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8631             SendToICS(ics_prefix);
8632             SendToICS(message);
8633             SendToICS("\n");
8634             return;
8635         }
8636     }
8637     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8638         return;
8639     }
8640     if(!strncmp(message, "highlight ", 10)) {
8641         if(appData.testLegality && appData.markers) return;
8642         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8643         return;
8644     }
8645     /*
8646      * If the move is illegal, cancel it and redraw the board.
8647      * Also deal with other error cases.  Matching is rather loose
8648      * here to accommodate engines written before the spec.
8649      */
8650     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8651         strncmp(message, "Error", 5) == 0) {
8652         if (StrStr(message, "name") ||
8653             StrStr(message, "rating") || StrStr(message, "?") ||
8654             StrStr(message, "result") || StrStr(message, "board") ||
8655             StrStr(message, "bk") || StrStr(message, "computer") ||
8656             StrStr(message, "variant") || StrStr(message, "hint") ||
8657             StrStr(message, "random") || StrStr(message, "depth") ||
8658             StrStr(message, "accepted")) {
8659             return;
8660         }
8661         if (StrStr(message, "protover")) {
8662           /* Program is responding to input, so it's apparently done
8663              initializing, and this error message indicates it is
8664              protocol version 1.  So we don't need to wait any longer
8665              for it to initialize and send feature commands. */
8666           FeatureDone(cps, 1);
8667           cps->protocolVersion = 1;
8668           return;
8669         }
8670         cps->maybeThinking = FALSE;
8671
8672         if (StrStr(message, "draw")) {
8673             /* Program doesn't have "draw" command */
8674             cps->sendDrawOffers = 0;
8675             return;
8676         }
8677         if (cps->sendTime != 1 &&
8678             (StrStr(message, "time") || StrStr(message, "otim"))) {
8679           /* Program apparently doesn't have "time" or "otim" command */
8680           cps->sendTime = 0;
8681           return;
8682         }
8683         if (StrStr(message, "analyze")) {
8684             cps->analysisSupport = FALSE;
8685             cps->analyzing = FALSE;
8686 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8687             EditGameEvent(); // [HGM] try to preserve loaded game
8688             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8689             DisplayError(buf2, 0);
8690             return;
8691         }
8692         if (StrStr(message, "(no matching move)st")) {
8693           /* Special kludge for GNU Chess 4 only */
8694           cps->stKludge = TRUE;
8695           SendTimeControl(cps, movesPerSession, timeControl,
8696                           timeIncrement, appData.searchDepth,
8697                           searchTime);
8698           return;
8699         }
8700         if (StrStr(message, "(no matching move)sd")) {
8701           /* Special kludge for GNU Chess 4 only */
8702           cps->sdKludge = TRUE;
8703           SendTimeControl(cps, movesPerSession, timeControl,
8704                           timeIncrement, appData.searchDepth,
8705                           searchTime);
8706           return;
8707         }
8708         if (!StrStr(message, "llegal")) {
8709             return;
8710         }
8711         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8712             gameMode == IcsIdle) return;
8713         if (forwardMostMove <= backwardMostMove) return;
8714         if (pausing) PauseEvent();
8715       if(appData.forceIllegal) {
8716             // [HGM] illegal: machine refused move; force position after move into it
8717           SendToProgram("force\n", cps);
8718           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8719                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8720                 // when black is to move, while there might be nothing on a2 or black
8721                 // might already have the move. So send the board as if white has the move.
8722                 // But first we must change the stm of the engine, as it refused the last move
8723                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8724                 if(WhiteOnMove(forwardMostMove)) {
8725                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8726                     SendBoard(cps, forwardMostMove); // kludgeless board
8727                 } else {
8728                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8729                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8730                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8731                 }
8732           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8733             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8734                  gameMode == TwoMachinesPlay)
8735               SendToProgram("go\n", cps);
8736             return;
8737       } else
8738         if (gameMode == PlayFromGameFile) {
8739             /* Stop reading this game file */
8740             gameMode = EditGame;
8741             ModeHighlight();
8742         }
8743         /* [HGM] illegal-move claim should forfeit game when Xboard */
8744         /* only passes fully legal moves                            */
8745         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8746             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8747                                 "False illegal-move claim", GE_XBOARD );
8748             return; // do not take back move we tested as valid
8749         }
8750         currentMove = forwardMostMove-1;
8751         DisplayMove(currentMove-1); /* before DisplayMoveError */
8752         SwitchClocks(forwardMostMove-1); // [HGM] race
8753         DisplayBothClocks();
8754         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8755                 parseList[currentMove], _(cps->which));
8756         DisplayMoveError(buf1);
8757         DrawPosition(FALSE, boards[currentMove]);
8758
8759         SetUserThinkingEnables();
8760         return;
8761     }
8762     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8763         /* Program has a broken "time" command that
8764            outputs a string not ending in newline.
8765            Don't use it. */
8766         cps->sendTime = 0;
8767     }
8768
8769     /*
8770      * If chess program startup fails, exit with an error message.
8771      * Attempts to recover here are futile. [HGM] Well, we try anyway
8772      */
8773     if ((StrStr(message, "unknown host") != NULL)
8774         || (StrStr(message, "No remote directory") != NULL)
8775         || (StrStr(message, "not found") != NULL)
8776         || (StrStr(message, "No such file") != NULL)
8777         || (StrStr(message, "can't alloc") != NULL)
8778         || (StrStr(message, "Permission denied") != NULL)) {
8779
8780         cps->maybeThinking = FALSE;
8781         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8782                 _(cps->which), cps->program, cps->host, message);
8783         RemoveInputSource(cps->isr);
8784         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8785             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8786             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8787         }
8788         return;
8789     }
8790
8791     /*
8792      * Look for hint output
8793      */
8794     if (sscanf(message, "Hint: %s", buf1) == 1) {
8795         if (cps == &first && hintRequested) {
8796             hintRequested = FALSE;
8797             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8798                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8799                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8800                                     PosFlags(forwardMostMove),
8801                                     fromY, fromX, toY, toX, promoChar, buf1);
8802                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8803                 DisplayInformation(buf2);
8804             } else {
8805                 /* Hint move could not be parsed!? */
8806               snprintf(buf2, sizeof(buf2),
8807                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8808                         buf1, _(cps->which));
8809                 DisplayError(buf2, 0);
8810             }
8811         } else {
8812           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8813         }
8814         return;
8815     }
8816
8817     /*
8818      * Ignore other messages if game is not in progress
8819      */
8820     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8821         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8822
8823     /*
8824      * look for win, lose, draw, or draw offer
8825      */
8826     if (strncmp(message, "1-0", 3) == 0) {
8827         char *p, *q, *r = "";
8828         p = strchr(message, '{');
8829         if (p) {
8830             q = strchr(p, '}');
8831             if (q) {
8832                 *q = NULLCHAR;
8833                 r = p + 1;
8834             }
8835         }
8836         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8837         return;
8838     } else if (strncmp(message, "0-1", 3) == 0) {
8839         char *p, *q, *r = "";
8840         p = strchr(message, '{');
8841         if (p) {
8842             q = strchr(p, '}');
8843             if (q) {
8844                 *q = NULLCHAR;
8845                 r = p + 1;
8846             }
8847         }
8848         /* Kludge for Arasan 4.1 bug */
8849         if (strcmp(r, "Black resigns") == 0) {
8850             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8851             return;
8852         }
8853         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8854         return;
8855     } else if (strncmp(message, "1/2", 3) == 0) {
8856         char *p, *q, *r = "";
8857         p = strchr(message, '{');
8858         if (p) {
8859             q = strchr(p, '}');
8860             if (q) {
8861                 *q = NULLCHAR;
8862                 r = p + 1;
8863             }
8864         }
8865
8866         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8867         return;
8868
8869     } else if (strncmp(message, "White resign", 12) == 0) {
8870         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8871         return;
8872     } else if (strncmp(message, "Black resign", 12) == 0) {
8873         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8874         return;
8875     } else if (strncmp(message, "White matches", 13) == 0 ||
8876                strncmp(message, "Black matches", 13) == 0   ) {
8877         /* [HGM] ignore GNUShogi noises */
8878         return;
8879     } else if (strncmp(message, "White", 5) == 0 &&
8880                message[5] != '(' &&
8881                StrStr(message, "Black") == NULL) {
8882         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8883         return;
8884     } else if (strncmp(message, "Black", 5) == 0 &&
8885                message[5] != '(') {
8886         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8887         return;
8888     } else if (strcmp(message, "resign") == 0 ||
8889                strcmp(message, "computer resigns") == 0) {
8890         switch (gameMode) {
8891           case MachinePlaysBlack:
8892           case IcsPlayingBlack:
8893             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8894             break;
8895           case MachinePlaysWhite:
8896           case IcsPlayingWhite:
8897             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8898             break;
8899           case TwoMachinesPlay:
8900             if (cps->twoMachinesColor[0] == 'w')
8901               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8902             else
8903               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8904             break;
8905           default:
8906             /* can't happen */
8907             break;
8908         }
8909         return;
8910     } else if (strncmp(message, "opponent mates", 14) == 0) {
8911         switch (gameMode) {
8912           case MachinePlaysBlack:
8913           case IcsPlayingBlack:
8914             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8915             break;
8916           case MachinePlaysWhite:
8917           case IcsPlayingWhite:
8918             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8919             break;
8920           case TwoMachinesPlay:
8921             if (cps->twoMachinesColor[0] == 'w')
8922               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8923             else
8924               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8925             break;
8926           default:
8927             /* can't happen */
8928             break;
8929         }
8930         return;
8931     } else if (strncmp(message, "computer mates", 14) == 0) {
8932         switch (gameMode) {
8933           case MachinePlaysBlack:
8934           case IcsPlayingBlack:
8935             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8936             break;
8937           case MachinePlaysWhite:
8938           case IcsPlayingWhite:
8939             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8940             break;
8941           case TwoMachinesPlay:
8942             if (cps->twoMachinesColor[0] == 'w')
8943               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8944             else
8945               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8946             break;
8947           default:
8948             /* can't happen */
8949             break;
8950         }
8951         return;
8952     } else if (strncmp(message, "checkmate", 9) == 0) {
8953         if (WhiteOnMove(forwardMostMove)) {
8954             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8955         } else {
8956             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8957         }
8958         return;
8959     } else if (strstr(message, "Draw") != NULL ||
8960                strstr(message, "game is a draw") != NULL) {
8961         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8962         return;
8963     } else if (strstr(message, "offer") != NULL &&
8964                strstr(message, "draw") != NULL) {
8965 #if ZIPPY
8966         if (appData.zippyPlay && first.initDone) {
8967             /* Relay offer to ICS */
8968             SendToICS(ics_prefix);
8969             SendToICS("draw\n");
8970         }
8971 #endif
8972         cps->offeredDraw = 2; /* valid until this engine moves twice */
8973         if (gameMode == TwoMachinesPlay) {
8974             if (cps->other->offeredDraw) {
8975                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8976             /* [HGM] in two-machine mode we delay relaying draw offer      */
8977             /* until after we also have move, to see if it is really claim */
8978             }
8979         } else if (gameMode == MachinePlaysWhite ||
8980                    gameMode == MachinePlaysBlack) {
8981           if (userOfferedDraw) {
8982             DisplayInformation(_("Machine accepts your draw offer"));
8983             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8984           } else {
8985             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8986           }
8987         }
8988     }
8989
8990
8991     /*
8992      * Look for thinking output
8993      */
8994     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8995           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8996                                 ) {
8997         int plylev, mvleft, mvtot, curscore, time;
8998         char mvname[MOVE_LEN];
8999         u64 nodes; // [DM]
9000         char plyext;
9001         int ignore = FALSE;
9002         int prefixHint = FALSE;
9003         mvname[0] = NULLCHAR;
9004
9005         switch (gameMode) {
9006           case MachinePlaysBlack:
9007           case IcsPlayingBlack:
9008             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9009             break;
9010           case MachinePlaysWhite:
9011           case IcsPlayingWhite:
9012             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9013             break;
9014           case AnalyzeMode:
9015           case AnalyzeFile:
9016             break;
9017           case IcsObserving: /* [DM] icsEngineAnalyze */
9018             if (!appData.icsEngineAnalyze) ignore = TRUE;
9019             break;
9020           case TwoMachinesPlay:
9021             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9022                 ignore = TRUE;
9023             }
9024             break;
9025           default:
9026             ignore = TRUE;
9027             break;
9028         }
9029
9030         if (!ignore) {
9031             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9032             buf1[0] = NULLCHAR;
9033             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9034                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9035
9036                 if (plyext != ' ' && plyext != '\t') {
9037                     time *= 100;
9038                 }
9039
9040                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9041                 if( cps->scoreIsAbsolute &&
9042                     ( gameMode == MachinePlaysBlack ||
9043                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9044                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9045                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9046                      !WhiteOnMove(currentMove)
9047                     ) )
9048                 {
9049                     curscore = -curscore;
9050                 }
9051
9052                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9053
9054                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9055                         char buf[MSG_SIZ];
9056                         FILE *f;
9057                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9058                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9059                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9060                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9061                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9062                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9063                                 fclose(f);
9064                         } else DisplayError(_("failed writing PV"), 0);
9065                 }
9066
9067                 tempStats.depth = plylev;
9068                 tempStats.nodes = nodes;
9069                 tempStats.time = time;
9070                 tempStats.score = curscore;
9071                 tempStats.got_only_move = 0;
9072
9073                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9074                         int ticklen;
9075
9076                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9077                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9078                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9079                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9080                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9081                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9082                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9083                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9084                 }
9085
9086                 /* Buffer overflow protection */
9087                 if (pv[0] != NULLCHAR) {
9088                     if (strlen(pv) >= sizeof(tempStats.movelist)
9089                         && appData.debugMode) {
9090                         fprintf(debugFP,
9091                                 "PV is too long; using the first %u bytes.\n",
9092                                 (unsigned) sizeof(tempStats.movelist) - 1);
9093                     }
9094
9095                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9096                 } else {
9097                     sprintf(tempStats.movelist, " no PV\n");
9098                 }
9099
9100                 if (tempStats.seen_stat) {
9101                     tempStats.ok_to_send = 1;
9102                 }
9103
9104                 if (strchr(tempStats.movelist, '(') != NULL) {
9105                     tempStats.line_is_book = 1;
9106                     tempStats.nr_moves = 0;
9107                     tempStats.moves_left = 0;
9108                 } else {
9109                     tempStats.line_is_book = 0;
9110                 }
9111
9112                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9113                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9114
9115                 SendProgramStatsToFrontend( cps, &tempStats );
9116
9117                 /*
9118                     [AS] Protect the thinkOutput buffer from overflow... this
9119                     is only useful if buf1 hasn't overflowed first!
9120                 */
9121                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9122                          plylev,
9123                          (gameMode == TwoMachinesPlay ?
9124                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9125                          ((double) curscore) / 100.0,
9126                          prefixHint ? lastHint : "",
9127                          prefixHint ? " " : "" );
9128
9129                 if( buf1[0] != NULLCHAR ) {
9130                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9131
9132                     if( strlen(pv) > max_len ) {
9133                         if( appData.debugMode) {
9134                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9135                         }
9136                         pv[max_len+1] = '\0';
9137                     }
9138
9139                     strcat( thinkOutput, pv);
9140                 }
9141
9142                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9143                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9144                     DisplayMove(currentMove - 1);
9145                 }
9146                 return;
9147
9148             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9149                 /* crafty (9.25+) says "(only move) <move>"
9150                  * if there is only 1 legal move
9151                  */
9152                 sscanf(p, "(only move) %s", buf1);
9153                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9154                 sprintf(programStats.movelist, "%s (only move)", buf1);
9155                 programStats.depth = 1;
9156                 programStats.nr_moves = 1;
9157                 programStats.moves_left = 1;
9158                 programStats.nodes = 1;
9159                 programStats.time = 1;
9160                 programStats.got_only_move = 1;
9161
9162                 /* Not really, but we also use this member to
9163                    mean "line isn't going to change" (Crafty
9164                    isn't searching, so stats won't change) */
9165                 programStats.line_is_book = 1;
9166
9167                 SendProgramStatsToFrontend( cps, &programStats );
9168
9169                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9170                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9171                     DisplayMove(currentMove - 1);
9172                 }
9173                 return;
9174             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9175                               &time, &nodes, &plylev, &mvleft,
9176                               &mvtot, mvname) >= 5) {
9177                 /* The stat01: line is from Crafty (9.29+) in response
9178                    to the "." command */
9179                 programStats.seen_stat = 1;
9180                 cps->maybeThinking = TRUE;
9181
9182                 if (programStats.got_only_move || !appData.periodicUpdates)
9183                   return;
9184
9185                 programStats.depth = plylev;
9186                 programStats.time = time;
9187                 programStats.nodes = nodes;
9188                 programStats.moves_left = mvleft;
9189                 programStats.nr_moves = mvtot;
9190                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9191                 programStats.ok_to_send = 1;
9192                 programStats.movelist[0] = '\0';
9193
9194                 SendProgramStatsToFrontend( cps, &programStats );
9195
9196                 return;
9197
9198             } else if (strncmp(message,"++",2) == 0) {
9199                 /* Crafty 9.29+ outputs this */
9200                 programStats.got_fail = 2;
9201                 return;
9202
9203             } else if (strncmp(message,"--",2) == 0) {
9204                 /* Crafty 9.29+ outputs this */
9205                 programStats.got_fail = 1;
9206                 return;
9207
9208             } else if (thinkOutput[0] != NULLCHAR &&
9209                        strncmp(message, "    ", 4) == 0) {
9210                 unsigned message_len;
9211
9212                 p = message;
9213                 while (*p && *p == ' ') p++;
9214
9215                 message_len = strlen( p );
9216
9217                 /* [AS] Avoid buffer overflow */
9218                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9219                     strcat(thinkOutput, " ");
9220                     strcat(thinkOutput, p);
9221                 }
9222
9223                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9224                     strcat(programStats.movelist, " ");
9225                     strcat(programStats.movelist, p);
9226                 }
9227
9228                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9229                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9230                     DisplayMove(currentMove - 1);
9231                 }
9232                 return;
9233             }
9234         }
9235         else {
9236             buf1[0] = NULLCHAR;
9237
9238             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9239                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9240             {
9241                 ChessProgramStats cpstats;
9242
9243                 if (plyext != ' ' && plyext != '\t') {
9244                     time *= 100;
9245                 }
9246
9247                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9248                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9249                     curscore = -curscore;
9250                 }
9251
9252                 cpstats.depth = plylev;
9253                 cpstats.nodes = nodes;
9254                 cpstats.time = time;
9255                 cpstats.score = curscore;
9256                 cpstats.got_only_move = 0;
9257                 cpstats.movelist[0] = '\0';
9258
9259                 if (buf1[0] != NULLCHAR) {
9260                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9261                 }
9262
9263                 cpstats.ok_to_send = 0;
9264                 cpstats.line_is_book = 0;
9265                 cpstats.nr_moves = 0;
9266                 cpstats.moves_left = 0;
9267
9268                 SendProgramStatsToFrontend( cps, &cpstats );
9269             }
9270         }
9271     }
9272 }
9273
9274
9275 /* Parse a game score from the character string "game", and
9276    record it as the history of the current game.  The game
9277    score is NOT assumed to start from the standard position.
9278    The display is not updated in any way.
9279    */
9280 void
9281 ParseGameHistory (char *game)
9282 {
9283     ChessMove moveType;
9284     int fromX, fromY, toX, toY, boardIndex;
9285     char promoChar;
9286     char *p, *q;
9287     char buf[MSG_SIZ];
9288
9289     if (appData.debugMode)
9290       fprintf(debugFP, "Parsing game history: %s\n", game);
9291
9292     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9293     gameInfo.site = StrSave(appData.icsHost);
9294     gameInfo.date = PGNDate();
9295     gameInfo.round = StrSave("-");
9296
9297     /* Parse out names of players */
9298     while (*game == ' ') game++;
9299     p = buf;
9300     while (*game != ' ') *p++ = *game++;
9301     *p = NULLCHAR;
9302     gameInfo.white = StrSave(buf);
9303     while (*game == ' ') game++;
9304     p = buf;
9305     while (*game != ' ' && *game != '\n') *p++ = *game++;
9306     *p = NULLCHAR;
9307     gameInfo.black = StrSave(buf);
9308
9309     /* Parse moves */
9310     boardIndex = blackPlaysFirst ? 1 : 0;
9311     yynewstr(game);
9312     for (;;) {
9313         yyboardindex = boardIndex;
9314         moveType = (ChessMove) Myylex();
9315         switch (moveType) {
9316           case IllegalMove:             /* maybe suicide chess, etc. */
9317   if (appData.debugMode) {
9318     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9319     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9320     setbuf(debugFP, NULL);
9321   }
9322           case WhitePromotion:
9323           case BlackPromotion:
9324           case WhiteNonPromotion:
9325           case BlackNonPromotion:
9326           case NormalMove:
9327           case WhiteCapturesEnPassant:
9328           case BlackCapturesEnPassant:
9329           case WhiteKingSideCastle:
9330           case WhiteQueenSideCastle:
9331           case BlackKingSideCastle:
9332           case BlackQueenSideCastle:
9333           case WhiteKingSideCastleWild:
9334           case WhiteQueenSideCastleWild:
9335           case BlackKingSideCastleWild:
9336           case BlackQueenSideCastleWild:
9337           /* PUSH Fabien */
9338           case WhiteHSideCastleFR:
9339           case WhiteASideCastleFR:
9340           case BlackHSideCastleFR:
9341           case BlackASideCastleFR:
9342           /* POP Fabien */
9343             fromX = currentMoveString[0] - AAA;
9344             fromY = currentMoveString[1] - ONE;
9345             toX = currentMoveString[2] - AAA;
9346             toY = currentMoveString[3] - ONE;
9347             promoChar = currentMoveString[4];
9348             break;
9349           case WhiteDrop:
9350           case BlackDrop:
9351             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9352             fromX = moveType == WhiteDrop ?
9353               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9354             (int) CharToPiece(ToLower(currentMoveString[0]));
9355             fromY = DROP_RANK;
9356             toX = currentMoveString[2] - AAA;
9357             toY = currentMoveString[3] - ONE;
9358             promoChar = NULLCHAR;
9359             break;
9360           case AmbiguousMove:
9361             /* bug? */
9362             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9363   if (appData.debugMode) {
9364     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9365     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9366     setbuf(debugFP, NULL);
9367   }
9368             DisplayError(buf, 0);
9369             return;
9370           case ImpossibleMove:
9371             /* bug? */
9372             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9373   if (appData.debugMode) {
9374     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9375     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9376     setbuf(debugFP, NULL);
9377   }
9378             DisplayError(buf, 0);
9379             return;
9380           case EndOfFile:
9381             if (boardIndex < backwardMostMove) {
9382                 /* Oops, gap.  How did that happen? */
9383                 DisplayError(_("Gap in move list"), 0);
9384                 return;
9385             }
9386             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9387             if (boardIndex > forwardMostMove) {
9388                 forwardMostMove = boardIndex;
9389             }
9390             return;
9391           case ElapsedTime:
9392             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9393                 strcat(parseList[boardIndex-1], " ");
9394                 strcat(parseList[boardIndex-1], yy_text);
9395             }
9396             continue;
9397           case Comment:
9398           case PGNTag:
9399           case NAG:
9400           default:
9401             /* ignore */
9402             continue;
9403           case WhiteWins:
9404           case BlackWins:
9405           case GameIsDrawn:
9406           case GameUnfinished:
9407             if (gameMode == IcsExamining) {
9408                 if (boardIndex < backwardMostMove) {
9409                     /* Oops, gap.  How did that happen? */
9410                     return;
9411                 }
9412                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9413                 return;
9414             }
9415             gameInfo.result = moveType;
9416             p = strchr(yy_text, '{');
9417             if (p == NULL) p = strchr(yy_text, '(');
9418             if (p == NULL) {
9419                 p = yy_text;
9420                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9421             } else {
9422                 q = strchr(p, *p == '{' ? '}' : ')');
9423                 if (q != NULL) *q = NULLCHAR;
9424                 p++;
9425             }
9426             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9427             gameInfo.resultDetails = StrSave(p);
9428             continue;
9429         }
9430         if (boardIndex >= forwardMostMove &&
9431             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9432             backwardMostMove = blackPlaysFirst ? 1 : 0;
9433             return;
9434         }
9435         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9436                                  fromY, fromX, toY, toX, promoChar,
9437                                  parseList[boardIndex]);
9438         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9439         /* currentMoveString is set as a side-effect of yylex */
9440         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9441         strcat(moveList[boardIndex], "\n");
9442         boardIndex++;
9443         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9444         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9445           case MT_NONE:
9446           case MT_STALEMATE:
9447           default:
9448             break;
9449           case MT_CHECK:
9450             if(gameInfo.variant != VariantShogi)
9451                 strcat(parseList[boardIndex - 1], "+");
9452             break;
9453           case MT_CHECKMATE:
9454           case MT_STAINMATE:
9455             strcat(parseList[boardIndex - 1], "#");
9456             break;
9457         }
9458     }
9459 }
9460
9461
9462 /* Apply a move to the given board  */
9463 void
9464 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9465 {
9466   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9467   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9468
9469     /* [HGM] compute & store e.p. status and castling rights for new position */
9470     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9471
9472       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9473       oldEP = (signed char)board[EP_STATUS];
9474       board[EP_STATUS] = EP_NONE;
9475
9476   if (fromY == DROP_RANK) {
9477         /* must be first */
9478         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9479             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9480             return;
9481         }
9482         piece = board[toY][toX] = (ChessSquare) fromX;
9483   } else {
9484       int i;
9485
9486       if( board[toY][toX] != EmptySquare )
9487            board[EP_STATUS] = EP_CAPTURE;
9488
9489       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9490            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9491                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9492       } else
9493       if( board[fromY][fromX] == WhitePawn ) {
9494            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9495                board[EP_STATUS] = EP_PAWN_MOVE;
9496            if( toY-fromY==2) {
9497                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9498                         gameInfo.variant != VariantBerolina || toX < fromX)
9499                       board[EP_STATUS] = toX | berolina;
9500                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9501                         gameInfo.variant != VariantBerolina || toX > fromX)
9502                       board[EP_STATUS] = toX;
9503            }
9504       } else
9505       if( board[fromY][fromX] == BlackPawn ) {
9506            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9507                board[EP_STATUS] = EP_PAWN_MOVE;
9508            if( toY-fromY== -2) {
9509                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9510                         gameInfo.variant != VariantBerolina || toX < fromX)
9511                       board[EP_STATUS] = toX | berolina;
9512                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9513                         gameInfo.variant != VariantBerolina || toX > fromX)
9514                       board[EP_STATUS] = toX;
9515            }
9516        }
9517
9518        for(i=0; i<nrCastlingRights; i++) {
9519            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9520               board[CASTLING][i] == toX   && castlingRank[i] == toY
9521              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9522        }
9523
9524        if(gameInfo.variant == VariantSChess) { // update virginity
9525            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9526            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9527            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9528            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9529        }
9530
9531      if (fromX == toX && fromY == toY) return;
9532
9533      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9534      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9535      if(gameInfo.variant == VariantKnightmate)
9536          king += (int) WhiteUnicorn - (int) WhiteKing;
9537
9538     /* Code added by Tord: */
9539     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9540     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9541         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9542       board[fromY][fromX] = EmptySquare;
9543       board[toY][toX] = EmptySquare;
9544       if((toX > fromX) != (piece == WhiteRook)) {
9545         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9546       } else {
9547         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9548       }
9549     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9550                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9551       board[fromY][fromX] = EmptySquare;
9552       board[toY][toX] = EmptySquare;
9553       if((toX > fromX) != (piece == BlackRook)) {
9554         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9555       } else {
9556         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9557       }
9558     /* End of code added by Tord */
9559
9560     } else if (board[fromY][fromX] == king
9561         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9562         && toY == fromY && toX > fromX+1) {
9563         board[fromY][fromX] = EmptySquare;
9564         board[toY][toX] = king;
9565         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9566         board[fromY][BOARD_RGHT-1] = EmptySquare;
9567     } else if (board[fromY][fromX] == king
9568         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9569                && toY == fromY && toX < fromX-1) {
9570         board[fromY][fromX] = EmptySquare;
9571         board[toY][toX] = king;
9572         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9573         board[fromY][BOARD_LEFT] = EmptySquare;
9574     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9575                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9576                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9577                ) {
9578         /* white pawn promotion */
9579         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9580         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9581             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9582         board[fromY][fromX] = EmptySquare;
9583     } else if ((fromY >= BOARD_HEIGHT>>1)
9584                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9585                && (toX != fromX)
9586                && gameInfo.variant != VariantXiangqi
9587                && gameInfo.variant != VariantBerolina
9588                && (board[fromY][fromX] == WhitePawn)
9589                && (board[toY][toX] == EmptySquare)) {
9590         board[fromY][fromX] = EmptySquare;
9591         board[toY][toX] = WhitePawn;
9592         captured = board[toY - 1][toX];
9593         board[toY - 1][toX] = EmptySquare;
9594     } else if ((fromY == BOARD_HEIGHT-4)
9595                && (toX == fromX)
9596                && gameInfo.variant == VariantBerolina
9597                && (board[fromY][fromX] == WhitePawn)
9598                && (board[toY][toX] == EmptySquare)) {
9599         board[fromY][fromX] = EmptySquare;
9600         board[toY][toX] = WhitePawn;
9601         if(oldEP & EP_BEROLIN_A) {
9602                 captured = board[fromY][fromX-1];
9603                 board[fromY][fromX-1] = EmptySquare;
9604         }else{  captured = board[fromY][fromX+1];
9605                 board[fromY][fromX+1] = EmptySquare;
9606         }
9607     } else if (board[fromY][fromX] == king
9608         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9609                && toY == fromY && toX > fromX+1) {
9610         board[fromY][fromX] = EmptySquare;
9611         board[toY][toX] = king;
9612         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9613         board[fromY][BOARD_RGHT-1] = EmptySquare;
9614     } else if (board[fromY][fromX] == king
9615         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9616                && toY == fromY && toX < fromX-1) {
9617         board[fromY][fromX] = EmptySquare;
9618         board[toY][toX] = king;
9619         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9620         board[fromY][BOARD_LEFT] = EmptySquare;
9621     } else if (fromY == 7 && fromX == 3
9622                && board[fromY][fromX] == BlackKing
9623                && toY == 7 && toX == 5) {
9624         board[fromY][fromX] = EmptySquare;
9625         board[toY][toX] = BlackKing;
9626         board[fromY][7] = EmptySquare;
9627         board[toY][4] = BlackRook;
9628     } else if (fromY == 7 && fromX == 3
9629                && board[fromY][fromX] == BlackKing
9630                && toY == 7 && toX == 1) {
9631         board[fromY][fromX] = EmptySquare;
9632         board[toY][toX] = BlackKing;
9633         board[fromY][0] = EmptySquare;
9634         board[toY][2] = BlackRook;
9635     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9636                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9637                && toY < promoRank && promoChar
9638                ) {
9639         /* black pawn promotion */
9640         board[toY][toX] = CharToPiece(ToLower(promoChar));
9641         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9642             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9643         board[fromY][fromX] = EmptySquare;
9644     } else if ((fromY < BOARD_HEIGHT>>1)
9645                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9646                && (toX != fromX)
9647                && gameInfo.variant != VariantXiangqi
9648                && gameInfo.variant != VariantBerolina
9649                && (board[fromY][fromX] == BlackPawn)
9650                && (board[toY][toX] == EmptySquare)) {
9651         board[fromY][fromX] = EmptySquare;
9652         board[toY][toX] = BlackPawn;
9653         captured = board[toY + 1][toX];
9654         board[toY + 1][toX] = EmptySquare;
9655     } else if ((fromY == 3)
9656                && (toX == fromX)
9657                && gameInfo.variant == VariantBerolina
9658                && (board[fromY][fromX] == BlackPawn)
9659                && (board[toY][toX] == EmptySquare)) {
9660         board[fromY][fromX] = EmptySquare;
9661         board[toY][toX] = BlackPawn;
9662         if(oldEP & EP_BEROLIN_A) {
9663                 captured = board[fromY][fromX-1];
9664                 board[fromY][fromX-1] = EmptySquare;
9665         }else{  captured = board[fromY][fromX+1];
9666                 board[fromY][fromX+1] = EmptySquare;
9667         }
9668     } else {
9669         board[toY][toX] = board[fromY][fromX];
9670         board[fromY][fromX] = EmptySquare;
9671     }
9672   }
9673
9674     if (gameInfo.holdingsWidth != 0) {
9675
9676       /* !!A lot more code needs to be written to support holdings  */
9677       /* [HGM] OK, so I have written it. Holdings are stored in the */
9678       /* penultimate board files, so they are automaticlly stored   */
9679       /* in the game history.                                       */
9680       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9681                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9682         /* Delete from holdings, by decreasing count */
9683         /* and erasing image if necessary            */
9684         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9685         if(p < (int) BlackPawn) { /* white drop */
9686              p -= (int)WhitePawn;
9687                  p = PieceToNumber((ChessSquare)p);
9688              if(p >= gameInfo.holdingsSize) p = 0;
9689              if(--board[p][BOARD_WIDTH-2] <= 0)
9690                   board[p][BOARD_WIDTH-1] = EmptySquare;
9691              if((int)board[p][BOARD_WIDTH-2] < 0)
9692                         board[p][BOARD_WIDTH-2] = 0;
9693         } else {                  /* black drop */
9694              p -= (int)BlackPawn;
9695                  p = PieceToNumber((ChessSquare)p);
9696              if(p >= gameInfo.holdingsSize) p = 0;
9697              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9698                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9699              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9700                         board[BOARD_HEIGHT-1-p][1] = 0;
9701         }
9702       }
9703       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9704           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9705         /* [HGM] holdings: Add to holdings, if holdings exist */
9706         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9707                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9708                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9709         }
9710         p = (int) captured;
9711         if (p >= (int) BlackPawn) {
9712           p -= (int)BlackPawn;
9713           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9714                   /* in Shogi restore piece to its original  first */
9715                   captured = (ChessSquare) (DEMOTED captured);
9716                   p = DEMOTED p;
9717           }
9718           p = PieceToNumber((ChessSquare)p);
9719           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9720           board[p][BOARD_WIDTH-2]++;
9721           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9722         } else {
9723           p -= (int)WhitePawn;
9724           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9725                   captured = (ChessSquare) (DEMOTED captured);
9726                   p = DEMOTED p;
9727           }
9728           p = PieceToNumber((ChessSquare)p);
9729           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9730           board[BOARD_HEIGHT-1-p][1]++;
9731           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9732         }
9733       }
9734     } else if (gameInfo.variant == VariantAtomic) {
9735       if (captured != EmptySquare) {
9736         int y, x;
9737         for (y = toY-1; y <= toY+1; y++) {
9738           for (x = toX-1; x <= toX+1; x++) {
9739             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9740                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9741               board[y][x] = EmptySquare;
9742             }
9743           }
9744         }
9745         board[toY][toX] = EmptySquare;
9746       }
9747     }
9748     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9749         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9750     } else
9751     if(promoChar == '+') {
9752         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9753         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9754     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9755         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9756         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9757            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9758         board[toY][toX] = newPiece;
9759     }
9760     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9761                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9762         // [HGM] superchess: take promotion piece out of holdings
9763         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9764         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9765             if(!--board[k][BOARD_WIDTH-2])
9766                 board[k][BOARD_WIDTH-1] = EmptySquare;
9767         } else {
9768             if(!--board[BOARD_HEIGHT-1-k][1])
9769                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9770         }
9771     }
9772
9773 }
9774
9775 /* Updates forwardMostMove */
9776 void
9777 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9778 {
9779 //    forwardMostMove++; // [HGM] bare: moved downstream
9780
9781     (void) CoordsToAlgebraic(boards[forwardMostMove],
9782                              PosFlags(forwardMostMove),
9783                              fromY, fromX, toY, toX, promoChar,
9784                              parseList[forwardMostMove]);
9785
9786     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9787         int timeLeft; static int lastLoadFlag=0; int king, piece;
9788         piece = boards[forwardMostMove][fromY][fromX];
9789         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9790         if(gameInfo.variant == VariantKnightmate)
9791             king += (int) WhiteUnicorn - (int) WhiteKing;
9792         if(forwardMostMove == 0) {
9793             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9794                 fprintf(serverMoves, "%s;", UserName());
9795             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9796                 fprintf(serverMoves, "%s;", second.tidy);
9797             fprintf(serverMoves, "%s;", first.tidy);
9798             if(gameMode == MachinePlaysWhite)
9799                 fprintf(serverMoves, "%s;", UserName());
9800             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9801                 fprintf(serverMoves, "%s;", second.tidy);
9802         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9803         lastLoadFlag = loadFlag;
9804         // print base move
9805         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9806         // print castling suffix
9807         if( toY == fromY && piece == king ) {
9808             if(toX-fromX > 1)
9809                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9810             if(fromX-toX >1)
9811                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9812         }
9813         // e.p. suffix
9814         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9815              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9816              boards[forwardMostMove][toY][toX] == EmptySquare
9817              && fromX != toX && fromY != toY)
9818                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9819         // promotion suffix
9820         if(promoChar != NULLCHAR) {
9821             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9822                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9823                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9824             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9825         }
9826         if(!loadFlag) {
9827                 char buf[MOVE_LEN*2], *p; int len;
9828             fprintf(serverMoves, "/%d/%d",
9829                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9830             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9831             else                      timeLeft = blackTimeRemaining/1000;
9832             fprintf(serverMoves, "/%d", timeLeft);
9833                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9834                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9835                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9836                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9837             fprintf(serverMoves, "/%s", buf);
9838         }
9839         fflush(serverMoves);
9840     }
9841
9842     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9843         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9844       return;
9845     }
9846     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9847     if (commentList[forwardMostMove+1] != NULL) {
9848         free(commentList[forwardMostMove+1]);
9849         commentList[forwardMostMove+1] = NULL;
9850     }
9851     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9852     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9853     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9854     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9855     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9856     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9857     adjustedClock = FALSE;
9858     gameInfo.result = GameUnfinished;
9859     if (gameInfo.resultDetails != NULL) {
9860         free(gameInfo.resultDetails);
9861         gameInfo.resultDetails = NULL;
9862     }
9863     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9864                               moveList[forwardMostMove - 1]);
9865     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9866       case MT_NONE:
9867       case MT_STALEMATE:
9868       default:
9869         break;
9870       case MT_CHECK:
9871         if(gameInfo.variant != VariantShogi)
9872             strcat(parseList[forwardMostMove - 1], "+");
9873         break;
9874       case MT_CHECKMATE:
9875       case MT_STAINMATE:
9876         strcat(parseList[forwardMostMove - 1], "#");
9877         break;
9878     }
9879
9880 }
9881
9882 /* Updates currentMove if not pausing */
9883 void
9884 ShowMove (int fromX, int fromY, int toX, int toY)
9885 {
9886     int instant = (gameMode == PlayFromGameFile) ?
9887         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9888     if(appData.noGUI) return;
9889     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9890         if (!instant) {
9891             if (forwardMostMove == currentMove + 1) {
9892                 AnimateMove(boards[forwardMostMove - 1],
9893                             fromX, fromY, toX, toY);
9894             }
9895         }
9896         currentMove = forwardMostMove;
9897     }
9898
9899     if (instant) return;
9900
9901     DisplayMove(currentMove - 1);
9902     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9903             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9904                 SetHighlights(fromX, fromY, toX, toY);
9905             }
9906     }
9907     DrawPosition(FALSE, boards[currentMove]);
9908     DisplayBothClocks();
9909     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9910 }
9911
9912 void
9913 SendEgtPath (ChessProgramState *cps)
9914 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9915         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9916
9917         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9918
9919         while(*p) {
9920             char c, *q = name+1, *r, *s;
9921
9922             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9923             while(*p && *p != ',') *q++ = *p++;
9924             *q++ = ':'; *q = 0;
9925             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9926                 strcmp(name, ",nalimov:") == 0 ) {
9927                 // take nalimov path from the menu-changeable option first, if it is defined
9928               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9929                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9930             } else
9931             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9932                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9933                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9934                 s = r = StrStr(s, ":") + 1; // beginning of path info
9935                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9936                 c = *r; *r = 0;             // temporarily null-terminate path info
9937                     *--q = 0;               // strip of trailig ':' from name
9938                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9939                 *r = c;
9940                 SendToProgram(buf,cps);     // send egtbpath command for this format
9941             }
9942             if(*p == ',') p++; // read away comma to position for next format name
9943         }
9944 }
9945
9946 static int
9947 NonStandardBoardSize ()
9948 {
9949       /* [HGM] Awkward testing. Should really be a table */
9950       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9951       if( gameInfo.variant == VariantXiangqi )
9952            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9953       if( gameInfo.variant == VariantShogi )
9954            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9955       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9956            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9957       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9958           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9959            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9960       if( gameInfo.variant == VariantCourier )
9961            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9962       if( gameInfo.variant == VariantSuper )
9963            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9964       if( gameInfo.variant == VariantGreat )
9965            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9966       if( gameInfo.variant == VariantSChess )
9967            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9968       if( gameInfo.variant == VariantGrand )
9969            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9970       return overruled;
9971 }
9972
9973 void
9974 InitChessProgram (ChessProgramState *cps, int setup)
9975 /* setup needed to setup FRC opening position */
9976 {
9977     char buf[MSG_SIZ], b[MSG_SIZ];
9978     if (appData.noChessProgram) return;
9979     hintRequested = FALSE;
9980     bookRequested = FALSE;
9981
9982     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9983     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9984     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9985     if(cps->memSize) { /* [HGM] memory */
9986       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9987         SendToProgram(buf, cps);
9988     }
9989     SendEgtPath(cps); /* [HGM] EGT */
9990     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9991       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9992         SendToProgram(buf, cps);
9993     }
9994
9995     SendToProgram(cps->initString, cps);
9996     if (gameInfo.variant != VariantNormal &&
9997         gameInfo.variant != VariantLoadable
9998         /* [HGM] also send variant if board size non-standard */
9999         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10000                                             ) {
10001       char *v = VariantName(gameInfo.variant);
10002       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10003         /* [HGM] in protocol 1 we have to assume all variants valid */
10004         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10005         DisplayFatalError(buf, 0, 1);
10006         return;
10007       }
10008
10009       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10010         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10011                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10012            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10013            if(StrStr(cps->variants, b) == NULL) {
10014                // specific sized variant not known, check if general sizing allowed
10015                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10016                    if(StrStr(cps->variants, "boardsize") == NULL) {
10017                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10018                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10019                        DisplayFatalError(buf, 0, 1);
10020                        return;
10021                    }
10022                    /* [HGM] here we really should compare with the maximum supported board size */
10023                }
10024            }
10025       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10026       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10027       SendToProgram(buf, cps);
10028     }
10029     currentlyInitializedVariant = gameInfo.variant;
10030
10031     /* [HGM] send opening position in FRC to first engine */
10032     if(setup) {
10033           SendToProgram("force\n", cps);
10034           SendBoard(cps, 0);
10035           /* engine is now in force mode! Set flag to wake it up after first move. */
10036           setboardSpoiledMachineBlack = 1;
10037     }
10038
10039     if (cps->sendICS) {
10040       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10041       SendToProgram(buf, cps);
10042     }
10043     cps->maybeThinking = FALSE;
10044     cps->offeredDraw = 0;
10045     if (!appData.icsActive) {
10046         SendTimeControl(cps, movesPerSession, timeControl,
10047                         timeIncrement, appData.searchDepth,
10048                         searchTime);
10049     }
10050     if (appData.showThinking
10051         // [HGM] thinking: four options require thinking output to be sent
10052         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10053                                 ) {
10054         SendToProgram("post\n", cps);
10055     }
10056     SendToProgram("hard\n", cps);
10057     if (!appData.ponderNextMove) {
10058         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10059            it without being sure what state we are in first.  "hard"
10060            is not a toggle, so that one is OK.
10061          */
10062         SendToProgram("easy\n", cps);
10063     }
10064     if (cps->usePing) {
10065       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10066       SendToProgram(buf, cps);
10067     }
10068     cps->initDone = TRUE;
10069     ClearEngineOutputPane(cps == &second);
10070 }
10071
10072
10073 void
10074 ResendOptions (ChessProgramState *cps)
10075 { // send the stored value of the options
10076   int i;
10077   char buf[MSG_SIZ];
10078   Option *opt = cps->option;
10079   for(i=0; i<cps->nrOptions; i++, opt++) {
10080       switch(opt->type) {
10081         case Spin:
10082         case Slider:
10083         case CheckBox:
10084             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10085           break;
10086         case ComboBox:
10087           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10088           break;
10089         default:
10090             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10091           break;
10092         case Button:
10093         case SaveButton:
10094           continue;
10095       }
10096       SendToProgram(buf, cps);
10097   }
10098 }
10099
10100 void
10101 StartChessProgram (ChessProgramState *cps)
10102 {
10103     char buf[MSG_SIZ];
10104     int err;
10105
10106     if (appData.noChessProgram) return;
10107     cps->initDone = FALSE;
10108
10109     if (strcmp(cps->host, "localhost") == 0) {
10110         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10111     } else if (*appData.remoteShell == NULLCHAR) {
10112         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10113     } else {
10114         if (*appData.remoteUser == NULLCHAR) {
10115           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10116                     cps->program);
10117         } else {
10118           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10119                     cps->host, appData.remoteUser, cps->program);
10120         }
10121         err = StartChildProcess(buf, "", &cps->pr);
10122     }
10123
10124     if (err != 0) {
10125       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10126         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10127         if(cps != &first) return;
10128         appData.noChessProgram = TRUE;
10129         ThawUI();
10130         SetNCPMode();
10131 //      DisplayFatalError(buf, err, 1);
10132 //      cps->pr = NoProc;
10133 //      cps->isr = NULL;
10134         return;
10135     }
10136
10137     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10138     if (cps->protocolVersion > 1) {
10139       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10140       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10141         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10142         cps->comboCnt = 0;  //                and values of combo boxes
10143       }
10144       SendToProgram(buf, cps);
10145       if(cps->reload) ResendOptions(cps);
10146     } else {
10147       SendToProgram("xboard\n", cps);
10148     }
10149 }
10150
10151 void
10152 TwoMachinesEventIfReady P((void))
10153 {
10154   static int curMess = 0;
10155   if (first.lastPing != first.lastPong) {
10156     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10157     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10158     return;
10159   }
10160   if (second.lastPing != second.lastPong) {
10161     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10162     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10163     return;
10164   }
10165   DisplayMessage("", ""); curMess = 0;
10166   TwoMachinesEvent();
10167 }
10168
10169 char *
10170 MakeName (char *template)
10171 {
10172     time_t clock;
10173     struct tm *tm;
10174     static char buf[MSG_SIZ];
10175     char *p = buf;
10176     int i;
10177
10178     clock = time((time_t *)NULL);
10179     tm = localtime(&clock);
10180
10181     while(*p++ = *template++) if(p[-1] == '%') {
10182         switch(*template++) {
10183           case 0:   *p = 0; return buf;
10184           case 'Y': i = tm->tm_year+1900; break;
10185           case 'y': i = tm->tm_year-100; break;
10186           case 'M': i = tm->tm_mon+1; break;
10187           case 'd': i = tm->tm_mday; break;
10188           case 'h': i = tm->tm_hour; break;
10189           case 'm': i = tm->tm_min; break;
10190           case 's': i = tm->tm_sec; break;
10191           default:  i = 0;
10192         }
10193         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10194     }
10195     return buf;
10196 }
10197
10198 int
10199 CountPlayers (char *p)
10200 {
10201     int n = 0;
10202     while(p = strchr(p, '\n')) p++, n++; // count participants
10203     return n;
10204 }
10205
10206 FILE *
10207 WriteTourneyFile (char *results, FILE *f)
10208 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10209     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10210     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10211         // create a file with tournament description
10212         fprintf(f, "-participants {%s}\n", appData.participants);
10213         fprintf(f, "-seedBase %d\n", appData.seedBase);
10214         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10215         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10216         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10217         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10218         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10219         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10220         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10221         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10222         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10223         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10224         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10225         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10226         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10227         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10228         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10229         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10230         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10231         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10232         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10233         fprintf(f, "-smpCores %d\n", appData.smpCores);
10234         if(searchTime > 0)
10235                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10236         else {
10237                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10238                 fprintf(f, "-tc %s\n", appData.timeControl);
10239                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10240         }
10241         fprintf(f, "-results \"%s\"\n", results);
10242     }
10243     return f;
10244 }
10245
10246 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10247
10248 void
10249 Substitute (char *participants, int expunge)
10250 {
10251     int i, changed, changes=0, nPlayers=0;
10252     char *p, *q, *r, buf[MSG_SIZ];
10253     if(participants == NULL) return;
10254     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10255     r = p = participants; q = appData.participants;
10256     while(*p && *p == *q) {
10257         if(*p == '\n') r = p+1, nPlayers++;
10258         p++; q++;
10259     }
10260     if(*p) { // difference
10261         while(*p && *p++ != '\n');
10262         while(*q && *q++ != '\n');
10263       changed = nPlayers;
10264         changes = 1 + (strcmp(p, q) != 0);
10265     }
10266     if(changes == 1) { // a single engine mnemonic was changed
10267         q = r; while(*q) nPlayers += (*q++ == '\n');
10268         p = buf; while(*r && (*p = *r++) != '\n') p++;
10269         *p = NULLCHAR;
10270         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10271         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10272         if(mnemonic[i]) { // The substitute is valid
10273             FILE *f;
10274             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10275                 flock(fileno(f), LOCK_EX);
10276                 ParseArgsFromFile(f);
10277                 fseek(f, 0, SEEK_SET);
10278                 FREE(appData.participants); appData.participants = participants;
10279                 if(expunge) { // erase results of replaced engine
10280                     int len = strlen(appData.results), w, b, dummy;
10281                     for(i=0; i<len; i++) {
10282                         Pairing(i, nPlayers, &w, &b, &dummy);
10283                         if((w == changed || b == changed) && appData.results[i] == '*') {
10284                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10285                             fclose(f);
10286                             return;
10287                         }
10288                     }
10289                     for(i=0; i<len; i++) {
10290                         Pairing(i, nPlayers, &w, &b, &dummy);
10291                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10292                     }
10293                 }
10294                 WriteTourneyFile(appData.results, f);
10295                 fclose(f); // release lock
10296                 return;
10297             }
10298         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10299     }
10300     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10301     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10302     free(participants);
10303     return;
10304 }
10305
10306 int
10307 CheckPlayers (char *participants)
10308 {
10309         int i;
10310         char buf[MSG_SIZ], *p;
10311         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10312         while(p = strchr(participants, '\n')) {
10313             *p = NULLCHAR;
10314             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10315             if(!mnemonic[i]) {
10316                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10317                 *p = '\n';
10318                 DisplayError(buf, 0);
10319                 return 1;
10320             }
10321             *p = '\n';
10322             participants = p + 1;
10323         }
10324         return 0;
10325 }
10326
10327 int
10328 CreateTourney (char *name)
10329 {
10330         FILE *f;
10331         if(matchMode && strcmp(name, appData.tourneyFile)) {
10332              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10333         }
10334         if(name[0] == NULLCHAR) {
10335             if(appData.participants[0])
10336                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10337             return 0;
10338         }
10339         f = fopen(name, "r");
10340         if(f) { // file exists
10341             ASSIGN(appData.tourneyFile, name);
10342             ParseArgsFromFile(f); // parse it
10343         } else {
10344             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10345             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10346                 DisplayError(_("Not enough participants"), 0);
10347                 return 0;
10348             }
10349             if(CheckPlayers(appData.participants)) return 0;
10350             ASSIGN(appData.tourneyFile, name);
10351             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10352             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10353         }
10354         fclose(f);
10355         appData.noChessProgram = FALSE;
10356         appData.clockMode = TRUE;
10357         SetGNUMode();
10358         return 1;
10359 }
10360
10361 int
10362 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10363 {
10364     char buf[MSG_SIZ], *p, *q;
10365     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10366     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10367     skip = !all && group[0]; // if group requested, we start in skip mode
10368     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10369         p = names; q = buf; header = 0;
10370         while(*p && *p != '\n') *q++ = *p++;
10371         *q = 0;
10372         if(*p == '\n') p++;
10373         if(buf[0] == '#') {
10374             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10375             depth++; // we must be entering a new group
10376             if(all) continue; // suppress printing group headers when complete list requested
10377             header = 1;
10378             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10379         }
10380         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10381         if(engineList[i]) free(engineList[i]);
10382         engineList[i] = strdup(buf);
10383         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10384         if(engineMnemonic[i]) free(engineMnemonic[i]);
10385         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10386             strcat(buf, " (");
10387             sscanf(q + 8, "%s", buf + strlen(buf));
10388             strcat(buf, ")");
10389         }
10390         engineMnemonic[i] = strdup(buf);
10391         i++;
10392     }
10393     engineList[i] = engineMnemonic[i] = NULL;
10394     return i;
10395 }
10396
10397 // following implemented as macro to avoid type limitations
10398 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10399
10400 void
10401 SwapEngines (int n)
10402 {   // swap settings for first engine and other engine (so far only some selected options)
10403     int h;
10404     char *p;
10405     if(n == 0) return;
10406     SWAP(directory, p)
10407     SWAP(chessProgram, p)
10408     SWAP(isUCI, h)
10409     SWAP(hasOwnBookUCI, h)
10410     SWAP(protocolVersion, h)
10411     SWAP(reuse, h)
10412     SWAP(scoreIsAbsolute, h)
10413     SWAP(timeOdds, h)
10414     SWAP(logo, p)
10415     SWAP(pgnName, p)
10416     SWAP(pvSAN, h)
10417     SWAP(engOptions, p)
10418     SWAP(engInitString, p)
10419     SWAP(computerString, p)
10420     SWAP(features, p)
10421     SWAP(fenOverride, p)
10422     SWAP(NPS, h)
10423     SWAP(accumulateTC, h)
10424     SWAP(host, p)
10425 }
10426
10427 int
10428 GetEngineLine (char *s, int n)
10429 {
10430     int i;
10431     char buf[MSG_SIZ];
10432     extern char *icsNames;
10433     if(!s || !*s) return 0;
10434     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10435     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10436     if(!mnemonic[i]) return 0;
10437     if(n == 11) return 1; // just testing if there was a match
10438     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10439     if(n == 1) SwapEngines(n);
10440     ParseArgsFromString(buf);
10441     if(n == 1) SwapEngines(n);
10442     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10443         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10444         ParseArgsFromString(buf);
10445     }
10446     return 1;
10447 }
10448
10449 int
10450 SetPlayer (int player, char *p)
10451 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10452     int i;
10453     char buf[MSG_SIZ], *engineName;
10454     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10455     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10456     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10457     if(mnemonic[i]) {
10458         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10459         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10460         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10461         ParseArgsFromString(buf);
10462     } else { // no engine with this nickname is installed!
10463         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10464         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10465         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10466         ModeHighlight();
10467         DisplayError(buf, 0);
10468         return 0;
10469     }
10470     free(engineName);
10471     return i;
10472 }
10473
10474 char *recentEngines;
10475
10476 void
10477 RecentEngineEvent (int nr)
10478 {
10479     int n;
10480 //    SwapEngines(1); // bump first to second
10481 //    ReplaceEngine(&second, 1); // and load it there
10482     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10483     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10484     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10485         ReplaceEngine(&first, 0);
10486         FloatToFront(&appData.recentEngineList, command[n]);
10487     }
10488 }
10489
10490 int
10491 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10492 {   // determine players from game number
10493     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10494
10495     if(appData.tourneyType == 0) {
10496         roundsPerCycle = (nPlayers - 1) | 1;
10497         pairingsPerRound = nPlayers / 2;
10498     } else if(appData.tourneyType > 0) {
10499         roundsPerCycle = nPlayers - appData.tourneyType;
10500         pairingsPerRound = appData.tourneyType;
10501     }
10502     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10503     gamesPerCycle = gamesPerRound * roundsPerCycle;
10504     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10505     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10506     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10507     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10508     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10509     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10510
10511     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10512     if(appData.roundSync) *syncInterval = gamesPerRound;
10513
10514     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10515
10516     if(appData.tourneyType == 0) {
10517         if(curPairing == (nPlayers-1)/2 ) {
10518             *whitePlayer = curRound;
10519             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10520         } else {
10521             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10522             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10523             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10524             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10525         }
10526     } else if(appData.tourneyType > 1) {
10527         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10528         *whitePlayer = curRound + appData.tourneyType;
10529     } else if(appData.tourneyType > 0) {
10530         *whitePlayer = curPairing;
10531         *blackPlayer = curRound + appData.tourneyType;
10532     }
10533
10534     // take care of white/black alternation per round.
10535     // For cycles and games this is already taken care of by default, derived from matchGame!
10536     return curRound & 1;
10537 }
10538
10539 int
10540 NextTourneyGame (int nr, int *swapColors)
10541 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10542     char *p, *q;
10543     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10544     FILE *tf;
10545     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10546     tf = fopen(appData.tourneyFile, "r");
10547     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10548     ParseArgsFromFile(tf); fclose(tf);
10549     InitTimeControls(); // TC might be altered from tourney file
10550
10551     nPlayers = CountPlayers(appData.participants); // count participants
10552     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10553     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10554
10555     if(syncInterval) {
10556         p = q = appData.results;
10557         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10558         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10559             DisplayMessage(_("Waiting for other game(s)"),"");
10560             waitingForGame = TRUE;
10561             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10562             return 0;
10563         }
10564         waitingForGame = FALSE;
10565     }
10566
10567     if(appData.tourneyType < 0) {
10568         if(nr>=0 && !pairingReceived) {
10569             char buf[1<<16];
10570             if(pairing.pr == NoProc) {
10571                 if(!appData.pairingEngine[0]) {
10572                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10573                     return 0;
10574                 }
10575                 StartChessProgram(&pairing); // starts the pairing engine
10576             }
10577             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10578             SendToProgram(buf, &pairing);
10579             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10580             SendToProgram(buf, &pairing);
10581             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10582         }
10583         pairingReceived = 0;                              // ... so we continue here
10584         *swapColors = 0;
10585         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10586         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10587         matchGame = 1; roundNr = nr / syncInterval + 1;
10588     }
10589
10590     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10591
10592     // redefine engines, engine dir, etc.
10593     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10594     if(first.pr == NoProc) {
10595       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10596       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10597     }
10598     if(second.pr == NoProc) {
10599       SwapEngines(1);
10600       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10601       SwapEngines(1);         // and make that valid for second engine by swapping
10602       InitEngine(&second, 1);
10603     }
10604     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10605     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10606     return OK;
10607 }
10608
10609 void
10610 NextMatchGame ()
10611 {   // performs game initialization that does not invoke engines, and then tries to start the game
10612     int res, firstWhite, swapColors = 0;
10613     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10614     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
10615         char buf[MSG_SIZ];
10616         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10617         if(strcmp(buf, currentDebugFile)) { // name has changed
10618             FILE *f = fopen(buf, "w");
10619             if(f) { // if opening the new file failed, just keep using the old one
10620                 ASSIGN(currentDebugFile, buf);
10621                 fclose(debugFP);
10622                 debugFP = f;
10623             }
10624             if(appData.serverFileName) {
10625                 if(serverFP) fclose(serverFP);
10626                 serverFP = fopen(appData.serverFileName, "w");
10627                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10628                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10629             }
10630         }
10631     }
10632     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10633     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10634     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10635     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10636     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10637     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10638     Reset(FALSE, first.pr != NoProc);
10639     res = LoadGameOrPosition(matchGame); // setup game
10640     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10641     if(!res) return; // abort when bad game/pos file
10642     TwoMachinesEvent();
10643 }
10644
10645 void
10646 UserAdjudicationEvent (int result)
10647 {
10648     ChessMove gameResult = GameIsDrawn;
10649
10650     if( result > 0 ) {
10651         gameResult = WhiteWins;
10652     }
10653     else if( result < 0 ) {
10654         gameResult = BlackWins;
10655     }
10656
10657     if( gameMode == TwoMachinesPlay ) {
10658         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10659     }
10660 }
10661
10662
10663 // [HGM] save: calculate checksum of game to make games easily identifiable
10664 int
10665 StringCheckSum (char *s)
10666 {
10667         int i = 0;
10668         if(s==NULL) return 0;
10669         while(*s) i = i*259 + *s++;
10670         return i;
10671 }
10672
10673 int
10674 GameCheckSum ()
10675 {
10676         int i, sum=0;
10677         for(i=backwardMostMove; i<forwardMostMove; i++) {
10678                 sum += pvInfoList[i].depth;
10679                 sum += StringCheckSum(parseList[i]);
10680                 sum += StringCheckSum(commentList[i]);
10681                 sum *= 261;
10682         }
10683         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10684         return sum + StringCheckSum(commentList[i]);
10685 } // end of save patch
10686
10687 void
10688 GameEnds (ChessMove result, char *resultDetails, int whosays)
10689 {
10690     GameMode nextGameMode;
10691     int isIcsGame;
10692     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10693
10694     if(endingGame) return; /* [HGM] crash: forbid recursion */
10695     endingGame = 1;
10696     if(twoBoards) { // [HGM] dual: switch back to one board
10697         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10698         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10699     }
10700     if (appData.debugMode) {
10701       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10702               result, resultDetails ? resultDetails : "(null)", whosays);
10703     }
10704
10705     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10706
10707     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10708
10709     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10710         /* If we are playing on ICS, the server decides when the
10711            game is over, but the engine can offer to draw, claim
10712            a draw, or resign.
10713          */
10714 #if ZIPPY
10715         if (appData.zippyPlay && first.initDone) {
10716             if (result == GameIsDrawn) {
10717                 /* In case draw still needs to be claimed */
10718                 SendToICS(ics_prefix);
10719                 SendToICS("draw\n");
10720             } else if (StrCaseStr(resultDetails, "resign")) {
10721                 SendToICS(ics_prefix);
10722                 SendToICS("resign\n");
10723             }
10724         }
10725 #endif
10726         endingGame = 0; /* [HGM] crash */
10727         return;
10728     }
10729
10730     /* If we're loading the game from a file, stop */
10731     if (whosays == GE_FILE) {
10732       (void) StopLoadGameTimer();
10733       gameFileFP = NULL;
10734     }
10735
10736     /* Cancel draw offers */
10737     first.offeredDraw = second.offeredDraw = 0;
10738
10739     /* If this is an ICS game, only ICS can really say it's done;
10740        if not, anyone can. */
10741     isIcsGame = (gameMode == IcsPlayingWhite ||
10742                  gameMode == IcsPlayingBlack ||
10743                  gameMode == IcsObserving    ||
10744                  gameMode == IcsExamining);
10745
10746     if (!isIcsGame || whosays == GE_ICS) {
10747         /* OK -- not an ICS game, or ICS said it was done */
10748         StopClocks();
10749         if (!isIcsGame && !appData.noChessProgram)
10750           SetUserThinkingEnables();
10751
10752         /* [HGM] if a machine claims the game end we verify this claim */
10753         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10754             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10755                 char claimer;
10756                 ChessMove trueResult = (ChessMove) -1;
10757
10758                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10759                                             first.twoMachinesColor[0] :
10760                                             second.twoMachinesColor[0] ;
10761
10762                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10763                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10764                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10765                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10766                 } else
10767                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10768                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10769                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10770                 } else
10771                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10772                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10773                 }
10774
10775                 // now verify win claims, but not in drop games, as we don't understand those yet
10776                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10777                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10778                     (result == WhiteWins && claimer == 'w' ||
10779                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10780                       if (appData.debugMode) {
10781                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10782                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10783                       }
10784                       if(result != trueResult) {
10785                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10786                               result = claimer == 'w' ? BlackWins : WhiteWins;
10787                               resultDetails = buf;
10788                       }
10789                 } else
10790                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10791                     && (forwardMostMove <= backwardMostMove ||
10792                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10793                         (claimer=='b')==(forwardMostMove&1))
10794                                                                                   ) {
10795                       /* [HGM] verify: draws that were not flagged are false claims */
10796                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10797                       result = claimer == 'w' ? BlackWins : WhiteWins;
10798                       resultDetails = buf;
10799                 }
10800                 /* (Claiming a loss is accepted no questions asked!) */
10801             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10802                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10803                 result = GameUnfinished;
10804                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10805             }
10806             /* [HGM] bare: don't allow bare King to win */
10807             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10808                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10809                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10810                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10811                && result != GameIsDrawn)
10812             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10813                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10814                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10815                         if(p >= 0 && p <= (int)WhiteKing) k++;
10816                 }
10817                 if (appData.debugMode) {
10818                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10819                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10820                 }
10821                 if(k <= 1) {
10822                         result = GameIsDrawn;
10823                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10824                         resultDetails = buf;
10825                 }
10826             }
10827         }
10828
10829
10830         if(serverMoves != NULL && !loadFlag) { char c = '=';
10831             if(result==WhiteWins) c = '+';
10832             if(result==BlackWins) c = '-';
10833             if(resultDetails != NULL)
10834                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10835         }
10836         if (resultDetails != NULL) {
10837             gameInfo.result = result;
10838             gameInfo.resultDetails = StrSave(resultDetails);
10839
10840             /* display last move only if game was not loaded from file */
10841             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10842                 DisplayMove(currentMove - 1);
10843
10844             if (forwardMostMove != 0) {
10845                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10846                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10847                                                                 ) {
10848                     if (*appData.saveGameFile != NULLCHAR) {
10849                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10850                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10851                         else
10852                         SaveGameToFile(appData.saveGameFile, TRUE);
10853                     } else if (appData.autoSaveGames) {
10854                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10855                     }
10856                     if (*appData.savePositionFile != NULLCHAR) {
10857                         SavePositionToFile(appData.savePositionFile);
10858                     }
10859                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10860                 }
10861             }
10862
10863             /* Tell program how game ended in case it is learning */
10864             /* [HGM] Moved this to after saving the PGN, just in case */
10865             /* engine died and we got here through time loss. In that */
10866             /* case we will get a fatal error writing the pipe, which */
10867             /* would otherwise lose us the PGN.                       */
10868             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10869             /* output during GameEnds should never be fatal anymore   */
10870             if (gameMode == MachinePlaysWhite ||
10871                 gameMode == MachinePlaysBlack ||
10872                 gameMode == TwoMachinesPlay ||
10873                 gameMode == IcsPlayingWhite ||
10874                 gameMode == IcsPlayingBlack ||
10875                 gameMode == BeginningOfGame) {
10876                 char buf[MSG_SIZ];
10877                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10878                         resultDetails);
10879                 if (first.pr != NoProc) {
10880                     SendToProgram(buf, &first);
10881                 }
10882                 if (second.pr != NoProc &&
10883                     gameMode == TwoMachinesPlay) {
10884                     SendToProgram(buf, &second);
10885                 }
10886             }
10887         }
10888
10889         if (appData.icsActive) {
10890             if (appData.quietPlay &&
10891                 (gameMode == IcsPlayingWhite ||
10892                  gameMode == IcsPlayingBlack)) {
10893                 SendToICS(ics_prefix);
10894                 SendToICS("set shout 1\n");
10895             }
10896             nextGameMode = IcsIdle;
10897             ics_user_moved = FALSE;
10898             /* clean up premove.  It's ugly when the game has ended and the
10899              * premove highlights are still on the board.
10900              */
10901             if (gotPremove) {
10902               gotPremove = FALSE;
10903               ClearPremoveHighlights();
10904               DrawPosition(FALSE, boards[currentMove]);
10905             }
10906             if (whosays == GE_ICS) {
10907                 switch (result) {
10908                 case WhiteWins:
10909                     if (gameMode == IcsPlayingWhite)
10910                         PlayIcsWinSound();
10911                     else if(gameMode == IcsPlayingBlack)
10912                         PlayIcsLossSound();
10913                     break;
10914                 case BlackWins:
10915                     if (gameMode == IcsPlayingBlack)
10916                         PlayIcsWinSound();
10917                     else if(gameMode == IcsPlayingWhite)
10918                         PlayIcsLossSound();
10919                     break;
10920                 case GameIsDrawn:
10921                     PlayIcsDrawSound();
10922                     break;
10923                 default:
10924                     PlayIcsUnfinishedSound();
10925                 }
10926             }
10927             if(appData.quitNext) { ExitEvent(0); return; }
10928         } else if (gameMode == EditGame ||
10929                    gameMode == PlayFromGameFile ||
10930                    gameMode == AnalyzeMode ||
10931                    gameMode == AnalyzeFile) {
10932             nextGameMode = gameMode;
10933         } else {
10934             nextGameMode = EndOfGame;
10935         }
10936         pausing = FALSE;
10937         ModeHighlight();
10938     } else {
10939         nextGameMode = gameMode;
10940     }
10941
10942     if (appData.noChessProgram) {
10943         gameMode = nextGameMode;
10944         ModeHighlight();
10945         endingGame = 0; /* [HGM] crash */
10946         return;
10947     }
10948
10949     if (first.reuse) {
10950         /* Put first chess program into idle state */
10951         if (first.pr != NoProc &&
10952             (gameMode == MachinePlaysWhite ||
10953              gameMode == MachinePlaysBlack ||
10954              gameMode == TwoMachinesPlay ||
10955              gameMode == IcsPlayingWhite ||
10956              gameMode == IcsPlayingBlack ||
10957              gameMode == BeginningOfGame)) {
10958             SendToProgram("force\n", &first);
10959             if (first.usePing) {
10960               char buf[MSG_SIZ];
10961               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10962               SendToProgram(buf, &first);
10963             }
10964         }
10965     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10966         /* Kill off first chess program */
10967         if (first.isr != NULL)
10968           RemoveInputSource(first.isr);
10969         first.isr = NULL;
10970
10971         if (first.pr != NoProc) {
10972             ExitAnalyzeMode();
10973             DoSleep( appData.delayBeforeQuit );
10974             SendToProgram("quit\n", &first);
10975             DoSleep( appData.delayAfterQuit );
10976             DestroyChildProcess(first.pr, first.useSigterm);
10977             first.reload = TRUE;
10978         }
10979         first.pr = NoProc;
10980     }
10981     if (second.reuse) {
10982         /* Put second chess program into idle state */
10983         if (second.pr != NoProc &&
10984             gameMode == TwoMachinesPlay) {
10985             SendToProgram("force\n", &second);
10986             if (second.usePing) {
10987               char buf[MSG_SIZ];
10988               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10989               SendToProgram(buf, &second);
10990             }
10991         }
10992     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10993         /* Kill off second chess program */
10994         if (second.isr != NULL)
10995           RemoveInputSource(second.isr);
10996         second.isr = NULL;
10997
10998         if (second.pr != NoProc) {
10999             DoSleep( appData.delayBeforeQuit );
11000             SendToProgram("quit\n", &second);
11001             DoSleep( appData.delayAfterQuit );
11002             DestroyChildProcess(second.pr, second.useSigterm);
11003             second.reload = TRUE;
11004         }
11005         second.pr = NoProc;
11006     }
11007
11008     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11009         char resChar = '=';
11010         switch (result) {
11011         case WhiteWins:
11012           resChar = '+';
11013           if (first.twoMachinesColor[0] == 'w') {
11014             first.matchWins++;
11015           } else {
11016             second.matchWins++;
11017           }
11018           break;
11019         case BlackWins:
11020           resChar = '-';
11021           if (first.twoMachinesColor[0] == 'b') {
11022             first.matchWins++;
11023           } else {
11024             second.matchWins++;
11025           }
11026           break;
11027         case GameUnfinished:
11028           resChar = ' ';
11029         default:
11030           break;
11031         }
11032
11033         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11034         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11035             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11036             ReserveGame(nextGame, resChar); // sets nextGame
11037             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11038             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11039         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11040
11041         if (nextGame <= appData.matchGames && !abortMatch) {
11042             gameMode = nextGameMode;
11043             matchGame = nextGame; // this will be overruled in tourney mode!
11044             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11045             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11046             endingGame = 0; /* [HGM] crash */
11047             return;
11048         } else {
11049             gameMode = nextGameMode;
11050             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11051                      first.tidy, second.tidy,
11052                      first.matchWins, second.matchWins,
11053                      appData.matchGames - (first.matchWins + second.matchWins));
11054             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11055             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11056             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11057             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11058                 first.twoMachinesColor = "black\n";
11059                 second.twoMachinesColor = "white\n";
11060             } else {
11061                 first.twoMachinesColor = "white\n";
11062                 second.twoMachinesColor = "black\n";
11063             }
11064         }
11065     }
11066     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11067         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11068       ExitAnalyzeMode();
11069     gameMode = nextGameMode;
11070     ModeHighlight();
11071     endingGame = 0;  /* [HGM] crash */
11072     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11073         if(matchMode == TRUE) { // match through command line: exit with or without popup
11074             if(ranking) {
11075                 ToNrEvent(forwardMostMove);
11076                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11077                 else ExitEvent(0);
11078             } else DisplayFatalError(buf, 0, 0);
11079         } else { // match through menu; just stop, with or without popup
11080             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11081             ModeHighlight();
11082             if(ranking){
11083                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11084             } else DisplayNote(buf);
11085       }
11086       if(ranking) free(ranking);
11087     }
11088 }
11089
11090 /* Assumes program was just initialized (initString sent).
11091    Leaves program in force mode. */
11092 void
11093 FeedMovesToProgram (ChessProgramState *cps, int upto)
11094 {
11095     int i;
11096
11097     if (appData.debugMode)
11098       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11099               startedFromSetupPosition ? "position and " : "",
11100               backwardMostMove, upto, cps->which);
11101     if(currentlyInitializedVariant != gameInfo.variant) {
11102       char buf[MSG_SIZ];
11103         // [HGM] variantswitch: make engine aware of new variant
11104         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11105                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11106         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11107         SendToProgram(buf, cps);
11108         currentlyInitializedVariant = gameInfo.variant;
11109     }
11110     SendToProgram("force\n", cps);
11111     if (startedFromSetupPosition) {
11112         SendBoard(cps, backwardMostMove);
11113     if (appData.debugMode) {
11114         fprintf(debugFP, "feedMoves\n");
11115     }
11116     }
11117     for (i = backwardMostMove; i < upto; i++) {
11118         SendMoveToProgram(i, cps);
11119     }
11120 }
11121
11122
11123 int
11124 ResurrectChessProgram ()
11125 {
11126      /* The chess program may have exited.
11127         If so, restart it and feed it all the moves made so far. */
11128     static int doInit = 0;
11129
11130     if (appData.noChessProgram) return 1;
11131
11132     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11133         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11134         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11135         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11136     } else {
11137         if (first.pr != NoProc) return 1;
11138         StartChessProgram(&first);
11139     }
11140     InitChessProgram(&first, FALSE);
11141     FeedMovesToProgram(&first, currentMove);
11142
11143     if (!first.sendTime) {
11144         /* can't tell gnuchess what its clock should read,
11145            so we bow to its notion. */
11146         ResetClocks();
11147         timeRemaining[0][currentMove] = whiteTimeRemaining;
11148         timeRemaining[1][currentMove] = blackTimeRemaining;
11149     }
11150
11151     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11152                 appData.icsEngineAnalyze) && first.analysisSupport) {
11153       SendToProgram("analyze\n", &first);
11154       first.analyzing = TRUE;
11155     }
11156     return 1;
11157 }
11158
11159 /*
11160  * Button procedures
11161  */
11162 void
11163 Reset (int redraw, int init)
11164 {
11165     int i;
11166
11167     if (appData.debugMode) {
11168         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11169                 redraw, init, gameMode);
11170     }
11171     CleanupTail(); // [HGM] vari: delete any stored variations
11172     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11173     pausing = pauseExamInvalid = FALSE;
11174     startedFromSetupPosition = blackPlaysFirst = FALSE;
11175     firstMove = TRUE;
11176     whiteFlag = blackFlag = FALSE;
11177     userOfferedDraw = FALSE;
11178     hintRequested = bookRequested = FALSE;
11179     first.maybeThinking = FALSE;
11180     second.maybeThinking = FALSE;
11181     first.bookSuspend = FALSE; // [HGM] book
11182     second.bookSuspend = FALSE;
11183     thinkOutput[0] = NULLCHAR;
11184     lastHint[0] = NULLCHAR;
11185     ClearGameInfo(&gameInfo);
11186     gameInfo.variant = StringToVariant(appData.variant);
11187     ics_user_moved = ics_clock_paused = FALSE;
11188     ics_getting_history = H_FALSE;
11189     ics_gamenum = -1;
11190     white_holding[0] = black_holding[0] = NULLCHAR;
11191     ClearProgramStats();
11192     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11193
11194     ResetFrontEnd();
11195     ClearHighlights();
11196     flipView = appData.flipView;
11197     ClearPremoveHighlights();
11198     gotPremove = FALSE;
11199     alarmSounded = FALSE;
11200
11201     GameEnds(EndOfFile, NULL, GE_PLAYER);
11202     if(appData.serverMovesName != NULL) {
11203         /* [HGM] prepare to make moves file for broadcasting */
11204         clock_t t = clock();
11205         if(serverMoves != NULL) fclose(serverMoves);
11206         serverMoves = fopen(appData.serverMovesName, "r");
11207         if(serverMoves != NULL) {
11208             fclose(serverMoves);
11209             /* delay 15 sec before overwriting, so all clients can see end */
11210             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11211         }
11212         serverMoves = fopen(appData.serverMovesName, "w");
11213     }
11214
11215     ExitAnalyzeMode();
11216     gameMode = BeginningOfGame;
11217     ModeHighlight();
11218     if(appData.icsActive) gameInfo.variant = VariantNormal;
11219     currentMove = forwardMostMove = backwardMostMove = 0;
11220     MarkTargetSquares(1);
11221     InitPosition(redraw);
11222     for (i = 0; i < MAX_MOVES; i++) {
11223         if (commentList[i] != NULL) {
11224             free(commentList[i]);
11225             commentList[i] = NULL;
11226         }
11227     }
11228     ResetClocks();
11229     timeRemaining[0][0] = whiteTimeRemaining;
11230     timeRemaining[1][0] = blackTimeRemaining;
11231
11232     if (first.pr == NoProc) {
11233         StartChessProgram(&first);
11234     }
11235     if (init) {
11236             InitChessProgram(&first, startedFromSetupPosition);
11237     }
11238     DisplayTitle("");
11239     DisplayMessage("", "");
11240     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11241     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11242     ClearMap();        // [HGM] exclude: invalidate map
11243 }
11244
11245 void
11246 AutoPlayGameLoop ()
11247 {
11248     for (;;) {
11249         if (!AutoPlayOneMove())
11250           return;
11251         if (matchMode || appData.timeDelay == 0)
11252           continue;
11253         if (appData.timeDelay < 0)
11254           return;
11255         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11256         break;
11257     }
11258 }
11259
11260 void
11261 AnalyzeNextGame()
11262 {
11263     ReloadGame(1); // next game
11264 }
11265
11266 int
11267 AutoPlayOneMove ()
11268 {
11269     int fromX, fromY, toX, toY;
11270
11271     if (appData.debugMode) {
11272       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11273     }
11274
11275     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11276       return FALSE;
11277
11278     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11279       pvInfoList[currentMove].depth = programStats.depth;
11280       pvInfoList[currentMove].score = programStats.score;
11281       pvInfoList[currentMove].time  = 0;
11282       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11283       else { // append analysis of final position as comment
11284         char buf[MSG_SIZ];
11285         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11286         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11287       }
11288       programStats.depth = 0;
11289     }
11290
11291     if (currentMove >= forwardMostMove) {
11292       if(gameMode == AnalyzeFile) {
11293           if(appData.loadGameIndex == -1) {
11294             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11295           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11296           } else {
11297           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11298         }
11299       }
11300 //      gameMode = EndOfGame;
11301 //      ModeHighlight();
11302
11303       /* [AS] Clear current move marker at the end of a game */
11304       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11305
11306       return FALSE;
11307     }
11308
11309     toX = moveList[currentMove][2] - AAA;
11310     toY = moveList[currentMove][3] - ONE;
11311
11312     if (moveList[currentMove][1] == '@') {
11313         if (appData.highlightLastMove) {
11314             SetHighlights(-1, -1, toX, toY);
11315         }
11316     } else {
11317         fromX = moveList[currentMove][0] - AAA;
11318         fromY = moveList[currentMove][1] - ONE;
11319
11320         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11321
11322         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11323
11324         if (appData.highlightLastMove) {
11325             SetHighlights(fromX, fromY, toX, toY);
11326         }
11327     }
11328     DisplayMove(currentMove);
11329     SendMoveToProgram(currentMove++, &first);
11330     DisplayBothClocks();
11331     DrawPosition(FALSE, boards[currentMove]);
11332     // [HGM] PV info: always display, routine tests if empty
11333     DisplayComment(currentMove - 1, commentList[currentMove]);
11334     return TRUE;
11335 }
11336
11337
11338 int
11339 LoadGameOneMove (ChessMove readAhead)
11340 {
11341     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11342     char promoChar = NULLCHAR;
11343     ChessMove moveType;
11344     char move[MSG_SIZ];
11345     char *p, *q;
11346
11347     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11348         gameMode != AnalyzeMode && gameMode != Training) {
11349         gameFileFP = NULL;
11350         return FALSE;
11351     }
11352
11353     yyboardindex = forwardMostMove;
11354     if (readAhead != EndOfFile) {
11355       moveType = readAhead;
11356     } else {
11357       if (gameFileFP == NULL)
11358           return FALSE;
11359       moveType = (ChessMove) Myylex();
11360     }
11361
11362     done = FALSE;
11363     switch (moveType) {
11364       case Comment:
11365         if (appData.debugMode)
11366           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11367         p = yy_text;
11368
11369         /* append the comment but don't display it */
11370         AppendComment(currentMove, p, FALSE);
11371         return TRUE;
11372
11373       case WhiteCapturesEnPassant:
11374       case BlackCapturesEnPassant:
11375       case WhitePromotion:
11376       case BlackPromotion:
11377       case WhiteNonPromotion:
11378       case BlackNonPromotion:
11379       case NormalMove:
11380       case WhiteKingSideCastle:
11381       case WhiteQueenSideCastle:
11382       case BlackKingSideCastle:
11383       case BlackQueenSideCastle:
11384       case WhiteKingSideCastleWild:
11385       case WhiteQueenSideCastleWild:
11386       case BlackKingSideCastleWild:
11387       case BlackQueenSideCastleWild:
11388       /* PUSH Fabien */
11389       case WhiteHSideCastleFR:
11390       case WhiteASideCastleFR:
11391       case BlackHSideCastleFR:
11392       case BlackASideCastleFR:
11393       /* POP Fabien */
11394         if (appData.debugMode)
11395           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11396         fromX = currentMoveString[0] - AAA;
11397         fromY = currentMoveString[1] - ONE;
11398         toX = currentMoveString[2] - AAA;
11399         toY = currentMoveString[3] - ONE;
11400         promoChar = currentMoveString[4];
11401         break;
11402
11403       case WhiteDrop:
11404       case BlackDrop:
11405         if (appData.debugMode)
11406           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11407         fromX = moveType == WhiteDrop ?
11408           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11409         (int) CharToPiece(ToLower(currentMoveString[0]));
11410         fromY = DROP_RANK;
11411         toX = currentMoveString[2] - AAA;
11412         toY = currentMoveString[3] - ONE;
11413         break;
11414
11415       case WhiteWins:
11416       case BlackWins:
11417       case GameIsDrawn:
11418       case GameUnfinished:
11419         if (appData.debugMode)
11420           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11421         p = strchr(yy_text, '{');
11422         if (p == NULL) p = strchr(yy_text, '(');
11423         if (p == NULL) {
11424             p = yy_text;
11425             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11426         } else {
11427             q = strchr(p, *p == '{' ? '}' : ')');
11428             if (q != NULL) *q = NULLCHAR;
11429             p++;
11430         }
11431         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11432         GameEnds(moveType, p, GE_FILE);
11433         done = TRUE;
11434         if (cmailMsgLoaded) {
11435             ClearHighlights();
11436             flipView = WhiteOnMove(currentMove);
11437             if (moveType == GameUnfinished) flipView = !flipView;
11438             if (appData.debugMode)
11439               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11440         }
11441         break;
11442
11443       case EndOfFile:
11444         if (appData.debugMode)
11445           fprintf(debugFP, "Parser hit end of file\n");
11446         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11447           case MT_NONE:
11448           case MT_CHECK:
11449             break;
11450           case MT_CHECKMATE:
11451           case MT_STAINMATE:
11452             if (WhiteOnMove(currentMove)) {
11453                 GameEnds(BlackWins, "Black mates", GE_FILE);
11454             } else {
11455                 GameEnds(WhiteWins, "White mates", GE_FILE);
11456             }
11457             break;
11458           case MT_STALEMATE:
11459             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11460             break;
11461         }
11462         done = TRUE;
11463         break;
11464
11465       case MoveNumberOne:
11466         if (lastLoadGameStart == GNUChessGame) {
11467             /* GNUChessGames have numbers, but they aren't move numbers */
11468             if (appData.debugMode)
11469               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11470                       yy_text, (int) moveType);
11471             return LoadGameOneMove(EndOfFile); /* tail recursion */
11472         }
11473         /* else fall thru */
11474
11475       case XBoardGame:
11476       case GNUChessGame:
11477       case PGNTag:
11478         /* Reached start of next game in file */
11479         if (appData.debugMode)
11480           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11481         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11482           case MT_NONE:
11483           case MT_CHECK:
11484             break;
11485           case MT_CHECKMATE:
11486           case MT_STAINMATE:
11487             if (WhiteOnMove(currentMove)) {
11488                 GameEnds(BlackWins, "Black mates", GE_FILE);
11489             } else {
11490                 GameEnds(WhiteWins, "White mates", GE_FILE);
11491             }
11492             break;
11493           case MT_STALEMATE:
11494             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11495             break;
11496         }
11497         done = TRUE;
11498         break;
11499
11500       case PositionDiagram:     /* should not happen; ignore */
11501       case ElapsedTime:         /* ignore */
11502       case NAG:                 /* ignore */
11503         if (appData.debugMode)
11504           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11505                   yy_text, (int) moveType);
11506         return LoadGameOneMove(EndOfFile); /* tail recursion */
11507
11508       case IllegalMove:
11509         if (appData.testLegality) {
11510             if (appData.debugMode)
11511               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11512             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11513                     (forwardMostMove / 2) + 1,
11514                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11515             DisplayError(move, 0);
11516             done = TRUE;
11517         } else {
11518             if (appData.debugMode)
11519               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11520                       yy_text, currentMoveString);
11521             fromX = currentMoveString[0] - AAA;
11522             fromY = currentMoveString[1] - ONE;
11523             toX = currentMoveString[2] - AAA;
11524             toY = currentMoveString[3] - ONE;
11525             promoChar = currentMoveString[4];
11526         }
11527         break;
11528
11529       case AmbiguousMove:
11530         if (appData.debugMode)
11531           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11532         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11533                 (forwardMostMove / 2) + 1,
11534                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11535         DisplayError(move, 0);
11536         done = TRUE;
11537         break;
11538
11539       default:
11540       case ImpossibleMove:
11541         if (appData.debugMode)
11542           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11543         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11544                 (forwardMostMove / 2) + 1,
11545                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11546         DisplayError(move, 0);
11547         done = TRUE;
11548         break;
11549     }
11550
11551     if (done) {
11552         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11553             DrawPosition(FALSE, boards[currentMove]);
11554             DisplayBothClocks();
11555             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11556               DisplayComment(currentMove - 1, commentList[currentMove]);
11557         }
11558         (void) StopLoadGameTimer();
11559         gameFileFP = NULL;
11560         cmailOldMove = forwardMostMove;
11561         return FALSE;
11562     } else {
11563         /* currentMoveString is set as a side-effect of yylex */
11564
11565         thinkOutput[0] = NULLCHAR;
11566         MakeMove(fromX, fromY, toX, toY, promoChar);
11567         currentMove = forwardMostMove;
11568         return TRUE;
11569     }
11570 }
11571
11572 /* Load the nth game from the given file */
11573 int
11574 LoadGameFromFile (char *filename, int n, char *title, int useList)
11575 {
11576     FILE *f;
11577     char buf[MSG_SIZ];
11578
11579     if (strcmp(filename, "-") == 0) {
11580         f = stdin;
11581         title = "stdin";
11582     } else {
11583         f = fopen(filename, "rb");
11584         if (f == NULL) {
11585           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11586             DisplayError(buf, errno);
11587             return FALSE;
11588         }
11589     }
11590     if (fseek(f, 0, 0) == -1) {
11591         /* f is not seekable; probably a pipe */
11592         useList = FALSE;
11593     }
11594     if (useList && n == 0) {
11595         int error = GameListBuild(f);
11596         if (error) {
11597             DisplayError(_("Cannot build game list"), error);
11598         } else if (!ListEmpty(&gameList) &&
11599                    ((ListGame *) gameList.tailPred)->number > 1) {
11600             GameListPopUp(f, title);
11601             return TRUE;
11602         }
11603         GameListDestroy();
11604         n = 1;
11605     }
11606     if (n == 0) n = 1;
11607     return LoadGame(f, n, title, FALSE);
11608 }
11609
11610
11611 void
11612 MakeRegisteredMove ()
11613 {
11614     int fromX, fromY, toX, toY;
11615     char promoChar;
11616     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11617         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11618           case CMAIL_MOVE:
11619           case CMAIL_DRAW:
11620             if (appData.debugMode)
11621               fprintf(debugFP, "Restoring %s for game %d\n",
11622                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11623
11624             thinkOutput[0] = NULLCHAR;
11625             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11626             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11627             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11628             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11629             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11630             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11631             MakeMove(fromX, fromY, toX, toY, promoChar);
11632             ShowMove(fromX, fromY, toX, toY);
11633
11634             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11635               case MT_NONE:
11636               case MT_CHECK:
11637                 break;
11638
11639               case MT_CHECKMATE:
11640               case MT_STAINMATE:
11641                 if (WhiteOnMove(currentMove)) {
11642                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11643                 } else {
11644                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11645                 }
11646                 break;
11647
11648               case MT_STALEMATE:
11649                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11650                 break;
11651             }
11652
11653             break;
11654
11655           case CMAIL_RESIGN:
11656             if (WhiteOnMove(currentMove)) {
11657                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11658             } else {
11659                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11660             }
11661             break;
11662
11663           case CMAIL_ACCEPT:
11664             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11665             break;
11666
11667           default:
11668             break;
11669         }
11670     }
11671
11672     return;
11673 }
11674
11675 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11676 int
11677 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11678 {
11679     int retVal;
11680
11681     if (gameNumber > nCmailGames) {
11682         DisplayError(_("No more games in this message"), 0);
11683         return FALSE;
11684     }
11685     if (f == lastLoadGameFP) {
11686         int offset = gameNumber - lastLoadGameNumber;
11687         if (offset == 0) {
11688             cmailMsg[0] = NULLCHAR;
11689             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11690                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11691                 nCmailMovesRegistered--;
11692             }
11693             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11694             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11695                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11696             }
11697         } else {
11698             if (! RegisterMove()) return FALSE;
11699         }
11700     }
11701
11702     retVal = LoadGame(f, gameNumber, title, useList);
11703
11704     /* Make move registered during previous look at this game, if any */
11705     MakeRegisteredMove();
11706
11707     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11708         commentList[currentMove]
11709           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11710         DisplayComment(currentMove - 1, commentList[currentMove]);
11711     }
11712
11713     return retVal;
11714 }
11715
11716 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11717 int
11718 ReloadGame (int offset)
11719 {
11720     int gameNumber = lastLoadGameNumber + offset;
11721     if (lastLoadGameFP == NULL) {
11722         DisplayError(_("No game has been loaded yet"), 0);
11723         return FALSE;
11724     }
11725     if (gameNumber <= 0) {
11726         DisplayError(_("Can't back up any further"), 0);
11727         return FALSE;
11728     }
11729     if (cmailMsgLoaded) {
11730         return CmailLoadGame(lastLoadGameFP, gameNumber,
11731                              lastLoadGameTitle, lastLoadGameUseList);
11732     } else {
11733         return LoadGame(lastLoadGameFP, gameNumber,
11734                         lastLoadGameTitle, lastLoadGameUseList);
11735     }
11736 }
11737
11738 int keys[EmptySquare+1];
11739
11740 int
11741 PositionMatches (Board b1, Board b2)
11742 {
11743     int r, f, sum=0;
11744     switch(appData.searchMode) {
11745         case 1: return CompareWithRights(b1, b2);
11746         case 2:
11747             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11748                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11749             }
11750             return TRUE;
11751         case 3:
11752             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11753               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11754                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11755             }
11756             return sum==0;
11757         case 4:
11758             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11759                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11760             }
11761             return sum==0;
11762     }
11763     return TRUE;
11764 }
11765
11766 #define Q_PROMO  4
11767 #define Q_EP     3
11768 #define Q_BCASTL 2
11769 #define Q_WCASTL 1
11770
11771 int pieceList[256], quickBoard[256];
11772 ChessSquare pieceType[256] = { EmptySquare };
11773 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11774 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11775 int soughtTotal, turn;
11776 Boolean epOK, flipSearch;
11777
11778 typedef struct {
11779     unsigned char piece, to;
11780 } Move;
11781
11782 #define DSIZE (250000)
11783
11784 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11785 Move *moveDatabase = initialSpace;
11786 unsigned int movePtr, dataSize = DSIZE;
11787
11788 int
11789 MakePieceList (Board board, int *counts)
11790 {
11791     int r, f, n=Q_PROMO, total=0;
11792     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11793     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11794         int sq = f + (r<<4);
11795         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11796             quickBoard[sq] = ++n;
11797             pieceList[n] = sq;
11798             pieceType[n] = board[r][f];
11799             counts[board[r][f]]++;
11800             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11801             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11802             total++;
11803         }
11804     }
11805     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11806     return total;
11807 }
11808
11809 void
11810 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11811 {
11812     int sq = fromX + (fromY<<4);
11813     int piece = quickBoard[sq];
11814     quickBoard[sq] = 0;
11815     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11816     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11817         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11818         moveDatabase[movePtr++].piece = Q_WCASTL;
11819         quickBoard[sq] = piece;
11820         piece = quickBoard[from]; quickBoard[from] = 0;
11821         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11822     } else
11823     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11824         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11825         moveDatabase[movePtr++].piece = Q_BCASTL;
11826         quickBoard[sq] = piece;
11827         piece = quickBoard[from]; quickBoard[from] = 0;
11828         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11829     } else
11830     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11831         quickBoard[(fromY<<4)+toX] = 0;
11832         moveDatabase[movePtr].piece = Q_EP;
11833         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11834         moveDatabase[movePtr].to = sq;
11835     } else
11836     if(promoPiece != pieceType[piece]) {
11837         moveDatabase[movePtr++].piece = Q_PROMO;
11838         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11839     }
11840     moveDatabase[movePtr].piece = piece;
11841     quickBoard[sq] = piece;
11842     movePtr++;
11843 }
11844
11845 int
11846 PackGame (Board board)
11847 {
11848     Move *newSpace = NULL;
11849     moveDatabase[movePtr].piece = 0; // terminate previous game
11850     if(movePtr > dataSize) {
11851         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11852         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11853         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11854         if(newSpace) {
11855             int i;
11856             Move *p = moveDatabase, *q = newSpace;
11857             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11858             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11859             moveDatabase = newSpace;
11860         } else { // calloc failed, we must be out of memory. Too bad...
11861             dataSize = 0; // prevent calloc events for all subsequent games
11862             return 0;     // and signal this one isn't cached
11863         }
11864     }
11865     movePtr++;
11866     MakePieceList(board, counts);
11867     return movePtr;
11868 }
11869
11870 int
11871 QuickCompare (Board board, int *minCounts, int *maxCounts)
11872 {   // compare according to search mode
11873     int r, f;
11874     switch(appData.searchMode)
11875     {
11876       case 1: // exact position match
11877         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11878         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11879             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11880         }
11881         break;
11882       case 2: // can have extra material on empty squares
11883         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11884             if(board[r][f] == EmptySquare) continue;
11885             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11886         }
11887         break;
11888       case 3: // material with exact Pawn structure
11889         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11890             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11891             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11892         } // fall through to material comparison
11893       case 4: // exact material
11894         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11895         break;
11896       case 6: // material range with given imbalance
11897         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11898         // fall through to range comparison
11899       case 5: // material range
11900         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11901     }
11902     return TRUE;
11903 }
11904
11905 int
11906 QuickScan (Board board, Move *move)
11907 {   // reconstruct game,and compare all positions in it
11908     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11909     do {
11910         int piece = move->piece;
11911         int to = move->to, from = pieceList[piece];
11912         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11913           if(!piece) return -1;
11914           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11915             piece = (++move)->piece;
11916             from = pieceList[piece];
11917             counts[pieceType[piece]]--;
11918             pieceType[piece] = (ChessSquare) move->to;
11919             counts[move->to]++;
11920           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11921             counts[pieceType[quickBoard[to]]]--;
11922             quickBoard[to] = 0; total--;
11923             move++;
11924             continue;
11925           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11926             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11927             from  = pieceList[piece]; // so this must be King
11928             quickBoard[from] = 0;
11929             pieceList[piece] = to;
11930             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11931             quickBoard[from] = 0; // rook
11932             quickBoard[to] = piece;
11933             to = move->to; piece = move->piece;
11934             goto aftercastle;
11935           }
11936         }
11937         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11938         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11939         quickBoard[from] = 0;
11940       aftercastle:
11941         quickBoard[to] = piece;
11942         pieceList[piece] = to;
11943         cnt++; turn ^= 3;
11944         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11945            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11946            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11947                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11948           ) {
11949             static int lastCounts[EmptySquare+1];
11950             int i;
11951             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11952             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11953         } else stretch = 0;
11954         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11955         move++;
11956     } while(1);
11957 }
11958
11959 void
11960 InitSearch ()
11961 {
11962     int r, f;
11963     flipSearch = FALSE;
11964     CopyBoard(soughtBoard, boards[currentMove]);
11965     soughtTotal = MakePieceList(soughtBoard, maxSought);
11966     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11967     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11968     CopyBoard(reverseBoard, boards[currentMove]);
11969     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11970         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11971         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11972         reverseBoard[r][f] = piece;
11973     }
11974     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11975     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11976     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11977                  || (boards[currentMove][CASTLING][2] == NoRights ||
11978                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11979                  && (boards[currentMove][CASTLING][5] == NoRights ||
11980                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11981       ) {
11982         flipSearch = TRUE;
11983         CopyBoard(flipBoard, soughtBoard);
11984         CopyBoard(rotateBoard, reverseBoard);
11985         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11986             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11987             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11988         }
11989     }
11990     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11991     if(appData.searchMode >= 5) {
11992         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11993         MakePieceList(soughtBoard, minSought);
11994         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11995     }
11996     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11997         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11998 }
11999
12000 GameInfo dummyInfo;
12001 static int creatingBook;
12002
12003 int
12004 GameContainsPosition (FILE *f, ListGame *lg)
12005 {
12006     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12007     int fromX, fromY, toX, toY;
12008     char promoChar;
12009     static int initDone=FALSE;
12010
12011     // weed out games based on numerical tag comparison
12012     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12013     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12014     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12015     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12016     if(!initDone) {
12017         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12018         initDone = TRUE;
12019     }
12020     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12021     else CopyBoard(boards[scratch], initialPosition); // default start position
12022     if(lg->moves) {
12023         turn = btm + 1;
12024         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12025         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12026     }
12027     if(btm) plyNr++;
12028     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12029     fseek(f, lg->offset, 0);
12030     yynewfile(f);
12031     while(1) {
12032         yyboardindex = scratch;
12033         quickFlag = plyNr+1;
12034         next = Myylex();
12035         quickFlag = 0;
12036         switch(next) {
12037             case PGNTag:
12038                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12039             default:
12040                 continue;
12041
12042             case XBoardGame:
12043             case GNUChessGame:
12044                 if(plyNr) return -1; // after we have seen moves, this is for new game
12045               continue;
12046
12047             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12048             case ImpossibleMove:
12049             case WhiteWins: // game ends here with these four
12050             case BlackWins:
12051             case GameIsDrawn:
12052             case GameUnfinished:
12053                 return -1;
12054
12055             case IllegalMove:
12056                 if(appData.testLegality) return -1;
12057             case WhiteCapturesEnPassant:
12058             case BlackCapturesEnPassant:
12059             case WhitePromotion:
12060             case BlackPromotion:
12061             case WhiteNonPromotion:
12062             case BlackNonPromotion:
12063             case NormalMove:
12064             case WhiteKingSideCastle:
12065             case WhiteQueenSideCastle:
12066             case BlackKingSideCastle:
12067             case BlackQueenSideCastle:
12068             case WhiteKingSideCastleWild:
12069             case WhiteQueenSideCastleWild:
12070             case BlackKingSideCastleWild:
12071             case BlackQueenSideCastleWild:
12072             case WhiteHSideCastleFR:
12073             case WhiteASideCastleFR:
12074             case BlackHSideCastleFR:
12075             case BlackASideCastleFR:
12076                 fromX = currentMoveString[0] - AAA;
12077                 fromY = currentMoveString[1] - ONE;
12078                 toX = currentMoveString[2] - AAA;
12079                 toY = currentMoveString[3] - ONE;
12080                 promoChar = currentMoveString[4];
12081                 break;
12082             case WhiteDrop:
12083             case BlackDrop:
12084                 fromX = next == WhiteDrop ?
12085                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12086                   (int) CharToPiece(ToLower(currentMoveString[0]));
12087                 fromY = DROP_RANK;
12088                 toX = currentMoveString[2] - AAA;
12089                 toY = currentMoveString[3] - ONE;
12090                 promoChar = 0;
12091                 break;
12092         }
12093         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12094         plyNr++;
12095         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12096         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12097         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12098         if(appData.findMirror) {
12099             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12100             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12101         }
12102     }
12103 }
12104
12105 /* Load the nth game from open file f */
12106 int
12107 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12108 {
12109     ChessMove cm;
12110     char buf[MSG_SIZ];
12111     int gn = gameNumber;
12112     ListGame *lg = NULL;
12113     int numPGNTags = 0;
12114     int err, pos = -1;
12115     GameMode oldGameMode;
12116     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12117
12118     if (appData.debugMode)
12119         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12120
12121     if (gameMode == Training )
12122         SetTrainingModeOff();
12123
12124     oldGameMode = gameMode;
12125     if (gameMode != BeginningOfGame) {
12126       Reset(FALSE, TRUE);
12127     }
12128
12129     gameFileFP = f;
12130     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12131         fclose(lastLoadGameFP);
12132     }
12133
12134     if (useList) {
12135         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12136
12137         if (lg) {
12138             fseek(f, lg->offset, 0);
12139             GameListHighlight(gameNumber);
12140             pos = lg->position;
12141             gn = 1;
12142         }
12143         else {
12144             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12145               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12146             else
12147             DisplayError(_("Game number out of range"), 0);
12148             return FALSE;
12149         }
12150     } else {
12151         GameListDestroy();
12152         if (fseek(f, 0, 0) == -1) {
12153             if (f == lastLoadGameFP ?
12154                 gameNumber == lastLoadGameNumber + 1 :
12155                 gameNumber == 1) {
12156                 gn = 1;
12157             } else {
12158                 DisplayError(_("Can't seek on game file"), 0);
12159                 return FALSE;
12160             }
12161         }
12162     }
12163     lastLoadGameFP = f;
12164     lastLoadGameNumber = gameNumber;
12165     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12166     lastLoadGameUseList = useList;
12167
12168     yynewfile(f);
12169
12170     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12171       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12172                 lg->gameInfo.black);
12173             DisplayTitle(buf);
12174     } else if (*title != NULLCHAR) {
12175         if (gameNumber > 1) {
12176           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12177             DisplayTitle(buf);
12178         } else {
12179             DisplayTitle(title);
12180         }
12181     }
12182
12183     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12184         gameMode = PlayFromGameFile;
12185         ModeHighlight();
12186     }
12187
12188     currentMove = forwardMostMove = backwardMostMove = 0;
12189     CopyBoard(boards[0], initialPosition);
12190     StopClocks();
12191
12192     /*
12193      * Skip the first gn-1 games in the file.
12194      * Also skip over anything that precedes an identifiable
12195      * start of game marker, to avoid being confused by
12196      * garbage at the start of the file.  Currently
12197      * recognized start of game markers are the move number "1",
12198      * the pattern "gnuchess .* game", the pattern
12199      * "^[#;%] [^ ]* game file", and a PGN tag block.
12200      * A game that starts with one of the latter two patterns
12201      * will also have a move number 1, possibly
12202      * following a position diagram.
12203      * 5-4-02: Let's try being more lenient and allowing a game to
12204      * start with an unnumbered move.  Does that break anything?
12205      */
12206     cm = lastLoadGameStart = EndOfFile;
12207     while (gn > 0) {
12208         yyboardindex = forwardMostMove;
12209         cm = (ChessMove) Myylex();
12210         switch (cm) {
12211           case EndOfFile:
12212             if (cmailMsgLoaded) {
12213                 nCmailGames = CMAIL_MAX_GAMES - gn;
12214             } else {
12215                 Reset(TRUE, TRUE);
12216                 DisplayError(_("Game not found in file"), 0);
12217             }
12218             return FALSE;
12219
12220           case GNUChessGame:
12221           case XBoardGame:
12222             gn--;
12223             lastLoadGameStart = cm;
12224             break;
12225
12226           case MoveNumberOne:
12227             switch (lastLoadGameStart) {
12228               case GNUChessGame:
12229               case XBoardGame:
12230               case PGNTag:
12231                 break;
12232               case MoveNumberOne:
12233               case EndOfFile:
12234                 gn--;           /* count this game */
12235                 lastLoadGameStart = cm;
12236                 break;
12237               default:
12238                 /* impossible */
12239                 break;
12240             }
12241             break;
12242
12243           case PGNTag:
12244             switch (lastLoadGameStart) {
12245               case GNUChessGame:
12246               case PGNTag:
12247               case MoveNumberOne:
12248               case EndOfFile:
12249                 gn--;           /* count this game */
12250                 lastLoadGameStart = cm;
12251                 break;
12252               case XBoardGame:
12253                 lastLoadGameStart = cm; /* game counted already */
12254                 break;
12255               default:
12256                 /* impossible */
12257                 break;
12258             }
12259             if (gn > 0) {
12260                 do {
12261                     yyboardindex = forwardMostMove;
12262                     cm = (ChessMove) Myylex();
12263                 } while (cm == PGNTag || cm == Comment);
12264             }
12265             break;
12266
12267           case WhiteWins:
12268           case BlackWins:
12269           case GameIsDrawn:
12270             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12271                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12272                     != CMAIL_OLD_RESULT) {
12273                     nCmailResults ++ ;
12274                     cmailResult[  CMAIL_MAX_GAMES
12275                                 - gn - 1] = CMAIL_OLD_RESULT;
12276                 }
12277             }
12278             break;
12279
12280           case NormalMove:
12281             /* Only a NormalMove can be at the start of a game
12282              * without a position diagram. */
12283             if (lastLoadGameStart == EndOfFile ) {
12284               gn--;
12285               lastLoadGameStart = MoveNumberOne;
12286             }
12287             break;
12288
12289           default:
12290             break;
12291         }
12292     }
12293
12294     if (appData.debugMode)
12295       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12296
12297     if (cm == XBoardGame) {
12298         /* Skip any header junk before position diagram and/or move 1 */
12299         for (;;) {
12300             yyboardindex = forwardMostMove;
12301             cm = (ChessMove) Myylex();
12302
12303             if (cm == EndOfFile ||
12304                 cm == GNUChessGame || cm == XBoardGame) {
12305                 /* Empty game; pretend end-of-file and handle later */
12306                 cm = EndOfFile;
12307                 break;
12308             }
12309
12310             if (cm == MoveNumberOne || cm == PositionDiagram ||
12311                 cm == PGNTag || cm == Comment)
12312               break;
12313         }
12314     } else if (cm == GNUChessGame) {
12315         if (gameInfo.event != NULL) {
12316             free(gameInfo.event);
12317         }
12318         gameInfo.event = StrSave(yy_text);
12319     }
12320
12321     startedFromSetupPosition = FALSE;
12322     while (cm == PGNTag) {
12323         if (appData.debugMode)
12324           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12325         err = ParsePGNTag(yy_text, &gameInfo);
12326         if (!err) numPGNTags++;
12327
12328         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12329         if(gameInfo.variant != oldVariant) {
12330             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12331             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12332             InitPosition(TRUE);
12333             oldVariant = gameInfo.variant;
12334             if (appData.debugMode)
12335               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12336         }
12337
12338
12339         if (gameInfo.fen != NULL) {
12340           Board initial_position;
12341           startedFromSetupPosition = TRUE;
12342           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12343             Reset(TRUE, TRUE);
12344             DisplayError(_("Bad FEN position in file"), 0);
12345             return FALSE;
12346           }
12347           CopyBoard(boards[0], initial_position);
12348           if (blackPlaysFirst) {
12349             currentMove = forwardMostMove = backwardMostMove = 1;
12350             CopyBoard(boards[1], initial_position);
12351             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12352             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12353             timeRemaining[0][1] = whiteTimeRemaining;
12354             timeRemaining[1][1] = blackTimeRemaining;
12355             if (commentList[0] != NULL) {
12356               commentList[1] = commentList[0];
12357               commentList[0] = NULL;
12358             }
12359           } else {
12360             currentMove = forwardMostMove = backwardMostMove = 0;
12361           }
12362           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12363           {   int i;
12364               initialRulePlies = FENrulePlies;
12365               for( i=0; i< nrCastlingRights; i++ )
12366                   initialRights[i] = initial_position[CASTLING][i];
12367           }
12368           yyboardindex = forwardMostMove;
12369           free(gameInfo.fen);
12370           gameInfo.fen = NULL;
12371         }
12372
12373         yyboardindex = forwardMostMove;
12374         cm = (ChessMove) Myylex();
12375
12376         /* Handle comments interspersed among the tags */
12377         while (cm == Comment) {
12378             char *p;
12379             if (appData.debugMode)
12380               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12381             p = yy_text;
12382             AppendComment(currentMove, p, FALSE);
12383             yyboardindex = forwardMostMove;
12384             cm = (ChessMove) Myylex();
12385         }
12386     }
12387
12388     /* don't rely on existence of Event tag since if game was
12389      * pasted from clipboard the Event tag may not exist
12390      */
12391     if (numPGNTags > 0){
12392         char *tags;
12393         if (gameInfo.variant == VariantNormal) {
12394           VariantClass v = StringToVariant(gameInfo.event);
12395           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12396           if(v < VariantShogi) gameInfo.variant = v;
12397         }
12398         if (!matchMode) {
12399           if( appData.autoDisplayTags ) {
12400             tags = PGNTags(&gameInfo);
12401             TagsPopUp(tags, CmailMsg());
12402             free(tags);
12403           }
12404         }
12405     } else {
12406         /* Make something up, but don't display it now */
12407         SetGameInfo();
12408         TagsPopDown();
12409     }
12410
12411     if (cm == PositionDiagram) {
12412         int i, j;
12413         char *p;
12414         Board initial_position;
12415
12416         if (appData.debugMode)
12417           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12418
12419         if (!startedFromSetupPosition) {
12420             p = yy_text;
12421             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12422               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12423                 switch (*p) {
12424                   case '{':
12425                   case '[':
12426                   case '-':
12427                   case ' ':
12428                   case '\t':
12429                   case '\n':
12430                   case '\r':
12431                     break;
12432                   default:
12433                     initial_position[i][j++] = CharToPiece(*p);
12434                     break;
12435                 }
12436             while (*p == ' ' || *p == '\t' ||
12437                    *p == '\n' || *p == '\r') p++;
12438
12439             if (strncmp(p, "black", strlen("black"))==0)
12440               blackPlaysFirst = TRUE;
12441             else
12442               blackPlaysFirst = FALSE;
12443             startedFromSetupPosition = TRUE;
12444
12445             CopyBoard(boards[0], initial_position);
12446             if (blackPlaysFirst) {
12447                 currentMove = forwardMostMove = backwardMostMove = 1;
12448                 CopyBoard(boards[1], initial_position);
12449                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12450                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12451                 timeRemaining[0][1] = whiteTimeRemaining;
12452                 timeRemaining[1][1] = blackTimeRemaining;
12453                 if (commentList[0] != NULL) {
12454                     commentList[1] = commentList[0];
12455                     commentList[0] = NULL;
12456                 }
12457             } else {
12458                 currentMove = forwardMostMove = backwardMostMove = 0;
12459             }
12460         }
12461         yyboardindex = forwardMostMove;
12462         cm = (ChessMove) Myylex();
12463     }
12464
12465   if(!creatingBook) {
12466     if (first.pr == NoProc) {
12467         StartChessProgram(&first);
12468     }
12469     InitChessProgram(&first, FALSE);
12470     SendToProgram("force\n", &first);
12471     if (startedFromSetupPosition) {
12472         SendBoard(&first, forwardMostMove);
12473     if (appData.debugMode) {
12474         fprintf(debugFP, "Load Game\n");
12475     }
12476         DisplayBothClocks();
12477     }
12478   }
12479
12480     /* [HGM] server: flag to write setup moves in broadcast file as one */
12481     loadFlag = appData.suppressLoadMoves;
12482
12483     while (cm == Comment) {
12484         char *p;
12485         if (appData.debugMode)
12486           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12487         p = yy_text;
12488         AppendComment(currentMove, p, FALSE);
12489         yyboardindex = forwardMostMove;
12490         cm = (ChessMove) Myylex();
12491     }
12492
12493     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12494         cm == WhiteWins || cm == BlackWins ||
12495         cm == GameIsDrawn || cm == GameUnfinished) {
12496         DisplayMessage("", _("No moves in game"));
12497         if (cmailMsgLoaded) {
12498             if (appData.debugMode)
12499               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12500             ClearHighlights();
12501             flipView = FALSE;
12502         }
12503         DrawPosition(FALSE, boards[currentMove]);
12504         DisplayBothClocks();
12505         gameMode = EditGame;
12506         ModeHighlight();
12507         gameFileFP = NULL;
12508         cmailOldMove = 0;
12509         return TRUE;
12510     }
12511
12512     // [HGM] PV info: routine tests if comment empty
12513     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12514         DisplayComment(currentMove - 1, commentList[currentMove]);
12515     }
12516     if (!matchMode && appData.timeDelay != 0)
12517       DrawPosition(FALSE, boards[currentMove]);
12518
12519     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12520       programStats.ok_to_send = 1;
12521     }
12522
12523     /* if the first token after the PGN tags is a move
12524      * and not move number 1, retrieve it from the parser
12525      */
12526     if (cm != MoveNumberOne)
12527         LoadGameOneMove(cm);
12528
12529     /* load the remaining moves from the file */
12530     while (LoadGameOneMove(EndOfFile)) {
12531       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12532       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12533     }
12534
12535     /* rewind to the start of the game */
12536     currentMove = backwardMostMove;
12537
12538     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12539
12540     if (oldGameMode == AnalyzeFile) {
12541       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12542       AnalyzeFileEvent();
12543     } else
12544     if (oldGameMode == AnalyzeMode) {
12545       AnalyzeFileEvent();
12546     }
12547
12548     if(creatingBook) return TRUE;
12549     if (!matchMode && pos > 0) {
12550         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12551     } else
12552     if (matchMode || appData.timeDelay == 0) {
12553       ToEndEvent();
12554     } else if (appData.timeDelay > 0) {
12555       AutoPlayGameLoop();
12556     }
12557
12558     if (appData.debugMode)
12559         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12560
12561     loadFlag = 0; /* [HGM] true game starts */
12562     return TRUE;
12563 }
12564
12565 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12566 int
12567 ReloadPosition (int offset)
12568 {
12569     int positionNumber = lastLoadPositionNumber + offset;
12570     if (lastLoadPositionFP == NULL) {
12571         DisplayError(_("No position has been loaded yet"), 0);
12572         return FALSE;
12573     }
12574     if (positionNumber <= 0) {
12575         DisplayError(_("Can't back up any further"), 0);
12576         return FALSE;
12577     }
12578     return LoadPosition(lastLoadPositionFP, positionNumber,
12579                         lastLoadPositionTitle);
12580 }
12581
12582 /* Load the nth position from the given file */
12583 int
12584 LoadPositionFromFile (char *filename, int n, char *title)
12585 {
12586     FILE *f;
12587     char buf[MSG_SIZ];
12588
12589     if (strcmp(filename, "-") == 0) {
12590         return LoadPosition(stdin, n, "stdin");
12591     } else {
12592         f = fopen(filename, "rb");
12593         if (f == NULL) {
12594             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12595             DisplayError(buf, errno);
12596             return FALSE;
12597         } else {
12598             return LoadPosition(f, n, title);
12599         }
12600     }
12601 }
12602
12603 /* Load the nth position from the given open file, and close it */
12604 int
12605 LoadPosition (FILE *f, int positionNumber, char *title)
12606 {
12607     char *p, line[MSG_SIZ];
12608     Board initial_position;
12609     int i, j, fenMode, pn;
12610
12611     if (gameMode == Training )
12612         SetTrainingModeOff();
12613
12614     if (gameMode != BeginningOfGame) {
12615         Reset(FALSE, TRUE);
12616     }
12617     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12618         fclose(lastLoadPositionFP);
12619     }
12620     if (positionNumber == 0) positionNumber = 1;
12621     lastLoadPositionFP = f;
12622     lastLoadPositionNumber = positionNumber;
12623     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12624     if (first.pr == NoProc && !appData.noChessProgram) {
12625       StartChessProgram(&first);
12626       InitChessProgram(&first, FALSE);
12627     }
12628     pn = positionNumber;
12629     if (positionNumber < 0) {
12630         /* Negative position number means to seek to that byte offset */
12631         if (fseek(f, -positionNumber, 0) == -1) {
12632             DisplayError(_("Can't seek on position file"), 0);
12633             return FALSE;
12634         };
12635         pn = 1;
12636     } else {
12637         if (fseek(f, 0, 0) == -1) {
12638             if (f == lastLoadPositionFP ?
12639                 positionNumber == lastLoadPositionNumber + 1 :
12640                 positionNumber == 1) {
12641                 pn = 1;
12642             } else {
12643                 DisplayError(_("Can't seek on position file"), 0);
12644                 return FALSE;
12645             }
12646         }
12647     }
12648     /* See if this file is FEN or old-style xboard */
12649     if (fgets(line, MSG_SIZ, f) == NULL) {
12650         DisplayError(_("Position not found in file"), 0);
12651         return FALSE;
12652     }
12653     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12654     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12655
12656     if (pn >= 2) {
12657         if (fenMode || line[0] == '#') pn--;
12658         while (pn > 0) {
12659             /* skip positions before number pn */
12660             if (fgets(line, MSG_SIZ, f) == NULL) {
12661                 Reset(TRUE, TRUE);
12662                 DisplayError(_("Position not found in file"), 0);
12663                 return FALSE;
12664             }
12665             if (fenMode || line[0] == '#') pn--;
12666         }
12667     }
12668
12669     if (fenMode) {
12670         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12671             DisplayError(_("Bad FEN position in file"), 0);
12672             return FALSE;
12673         }
12674     } else {
12675         (void) fgets(line, MSG_SIZ, f);
12676         (void) fgets(line, MSG_SIZ, f);
12677
12678         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12679             (void) fgets(line, MSG_SIZ, f);
12680             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12681                 if (*p == ' ')
12682                   continue;
12683                 initial_position[i][j++] = CharToPiece(*p);
12684             }
12685         }
12686
12687         blackPlaysFirst = FALSE;
12688         if (!feof(f)) {
12689             (void) fgets(line, MSG_SIZ, f);
12690             if (strncmp(line, "black", strlen("black"))==0)
12691               blackPlaysFirst = TRUE;
12692         }
12693     }
12694     startedFromSetupPosition = TRUE;
12695
12696     CopyBoard(boards[0], initial_position);
12697     if (blackPlaysFirst) {
12698         currentMove = forwardMostMove = backwardMostMove = 1;
12699         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12700         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12701         CopyBoard(boards[1], initial_position);
12702         DisplayMessage("", _("Black to play"));
12703     } else {
12704         currentMove = forwardMostMove = backwardMostMove = 0;
12705         DisplayMessage("", _("White to play"));
12706     }
12707     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12708     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12709         SendToProgram("force\n", &first);
12710         SendBoard(&first, forwardMostMove);
12711     }
12712     if (appData.debugMode) {
12713 int i, j;
12714   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12715   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12716         fprintf(debugFP, "Load Position\n");
12717     }
12718
12719     if (positionNumber > 1) {
12720       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12721         DisplayTitle(line);
12722     } else {
12723         DisplayTitle(title);
12724     }
12725     gameMode = EditGame;
12726     ModeHighlight();
12727     ResetClocks();
12728     timeRemaining[0][1] = whiteTimeRemaining;
12729     timeRemaining[1][1] = blackTimeRemaining;
12730     DrawPosition(FALSE, boards[currentMove]);
12731
12732     return TRUE;
12733 }
12734
12735
12736 void
12737 CopyPlayerNameIntoFileName (char **dest, char *src)
12738 {
12739     while (*src != NULLCHAR && *src != ',') {
12740         if (*src == ' ') {
12741             *(*dest)++ = '_';
12742             src++;
12743         } else {
12744             *(*dest)++ = *src++;
12745         }
12746     }
12747 }
12748
12749 char *
12750 DefaultFileName (char *ext)
12751 {
12752     static char def[MSG_SIZ];
12753     char *p;
12754
12755     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12756         p = def;
12757         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12758         *p++ = '-';
12759         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12760         *p++ = '.';
12761         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12762     } else {
12763         def[0] = NULLCHAR;
12764     }
12765     return def;
12766 }
12767
12768 /* Save the current game to the given file */
12769 int
12770 SaveGameToFile (char *filename, int append)
12771 {
12772     FILE *f;
12773     char buf[MSG_SIZ];
12774     int result, i, t,tot=0;
12775
12776     if (strcmp(filename, "-") == 0) {
12777         return SaveGame(stdout, 0, NULL);
12778     } else {
12779         for(i=0; i<10; i++) { // upto 10 tries
12780              f = fopen(filename, append ? "a" : "w");
12781              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12782              if(f || errno != 13) break;
12783              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12784              tot += t;
12785         }
12786         if (f == NULL) {
12787             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12788             DisplayError(buf, errno);
12789             return FALSE;
12790         } else {
12791             safeStrCpy(buf, lastMsg, MSG_SIZ);
12792             DisplayMessage(_("Waiting for access to save file"), "");
12793             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12794             DisplayMessage(_("Saving game"), "");
12795             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12796             result = SaveGame(f, 0, NULL);
12797             DisplayMessage(buf, "");
12798             return result;
12799         }
12800     }
12801 }
12802
12803 char *
12804 SavePart (char *str)
12805 {
12806     static char buf[MSG_SIZ];
12807     char *p;
12808
12809     p = strchr(str, ' ');
12810     if (p == NULL) return str;
12811     strncpy(buf, str, p - str);
12812     buf[p - str] = NULLCHAR;
12813     return buf;
12814 }
12815
12816 #define PGN_MAX_LINE 75
12817
12818 #define PGN_SIDE_WHITE  0
12819 #define PGN_SIDE_BLACK  1
12820
12821 static int
12822 FindFirstMoveOutOfBook (int side)
12823 {
12824     int result = -1;
12825
12826     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12827         int index = backwardMostMove;
12828         int has_book_hit = 0;
12829
12830         if( (index % 2) != side ) {
12831             index++;
12832         }
12833
12834         while( index < forwardMostMove ) {
12835             /* Check to see if engine is in book */
12836             int depth = pvInfoList[index].depth;
12837             int score = pvInfoList[index].score;
12838             int in_book = 0;
12839
12840             if( depth <= 2 ) {
12841                 in_book = 1;
12842             }
12843             else if( score == 0 && depth == 63 ) {
12844                 in_book = 1; /* Zappa */
12845             }
12846             else if( score == 2 && depth == 99 ) {
12847                 in_book = 1; /* Abrok */
12848             }
12849
12850             has_book_hit += in_book;
12851
12852             if( ! in_book ) {
12853                 result = index;
12854
12855                 break;
12856             }
12857
12858             index += 2;
12859         }
12860     }
12861
12862     return result;
12863 }
12864
12865 void
12866 GetOutOfBookInfo (char * buf)
12867 {
12868     int oob[2];
12869     int i;
12870     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12871
12872     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12873     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12874
12875     *buf = '\0';
12876
12877     if( oob[0] >= 0 || oob[1] >= 0 ) {
12878         for( i=0; i<2; i++ ) {
12879             int idx = oob[i];
12880
12881             if( idx >= 0 ) {
12882                 if( i > 0 && oob[0] >= 0 ) {
12883                     strcat( buf, "   " );
12884                 }
12885
12886                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12887                 sprintf( buf+strlen(buf), "%s%.2f",
12888                     pvInfoList[idx].score >= 0 ? "+" : "",
12889                     pvInfoList[idx].score / 100.0 );
12890             }
12891         }
12892     }
12893 }
12894
12895 /* Save game in PGN style and close the file */
12896 int
12897 SaveGamePGN (FILE *f)
12898 {
12899     int i, offset, linelen, newblock;
12900 //    char *movetext;
12901     char numtext[32];
12902     int movelen, numlen, blank;
12903     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12904
12905     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12906
12907     PrintPGNTags(f, &gameInfo);
12908
12909     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12910
12911     if (backwardMostMove > 0 || startedFromSetupPosition) {
12912         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12913         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12914         fprintf(f, "\n{--------------\n");
12915         PrintPosition(f, backwardMostMove);
12916         fprintf(f, "--------------}\n");
12917         free(fen);
12918     }
12919     else {
12920         /* [AS] Out of book annotation */
12921         if( appData.saveOutOfBookInfo ) {
12922             char buf[64];
12923
12924             GetOutOfBookInfo( buf );
12925
12926             if( buf[0] != '\0' ) {
12927                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12928             }
12929         }
12930
12931         fprintf(f, "\n");
12932     }
12933
12934     i = backwardMostMove;
12935     linelen = 0;
12936     newblock = TRUE;
12937
12938     while (i < forwardMostMove) {
12939         /* Print comments preceding this move */
12940         if (commentList[i] != NULL) {
12941             if (linelen > 0) fprintf(f, "\n");
12942             fprintf(f, "%s", commentList[i]);
12943             linelen = 0;
12944             newblock = TRUE;
12945         }
12946
12947         /* Format move number */
12948         if ((i % 2) == 0)
12949           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12950         else
12951           if (newblock)
12952             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12953           else
12954             numtext[0] = NULLCHAR;
12955
12956         numlen = strlen(numtext);
12957         newblock = FALSE;
12958
12959         /* Print move number */
12960         blank = linelen > 0 && numlen > 0;
12961         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12962             fprintf(f, "\n");
12963             linelen = 0;
12964             blank = 0;
12965         }
12966         if (blank) {
12967             fprintf(f, " ");
12968             linelen++;
12969         }
12970         fprintf(f, "%s", numtext);
12971         linelen += numlen;
12972
12973         /* Get move */
12974         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12975         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12976
12977         /* Print move */
12978         blank = linelen > 0 && movelen > 0;
12979         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12980             fprintf(f, "\n");
12981             linelen = 0;
12982             blank = 0;
12983         }
12984         if (blank) {
12985             fprintf(f, " ");
12986             linelen++;
12987         }
12988         fprintf(f, "%s", move_buffer);
12989         linelen += movelen;
12990
12991         /* [AS] Add PV info if present */
12992         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12993             /* [HGM] add time */
12994             char buf[MSG_SIZ]; int seconds;
12995
12996             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12997
12998             if( seconds <= 0)
12999               buf[0] = 0;
13000             else
13001               if( seconds < 30 )
13002                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13003               else
13004                 {
13005                   seconds = (seconds + 4)/10; // round to full seconds
13006                   if( seconds < 60 )
13007                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13008                   else
13009                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13010                 }
13011
13012             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13013                       pvInfoList[i].score >= 0 ? "+" : "",
13014                       pvInfoList[i].score / 100.0,
13015                       pvInfoList[i].depth,
13016                       buf );
13017
13018             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13019
13020             /* Print score/depth */
13021             blank = linelen > 0 && movelen > 0;
13022             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13023                 fprintf(f, "\n");
13024                 linelen = 0;
13025                 blank = 0;
13026             }
13027             if (blank) {
13028                 fprintf(f, " ");
13029                 linelen++;
13030             }
13031             fprintf(f, "%s", move_buffer);
13032             linelen += movelen;
13033         }
13034
13035         i++;
13036     }
13037
13038     /* Start a new line */
13039     if (linelen > 0) fprintf(f, "\n");
13040
13041     /* Print comments after last move */
13042     if (commentList[i] != NULL) {
13043         fprintf(f, "%s\n", commentList[i]);
13044     }
13045
13046     /* Print result */
13047     if (gameInfo.resultDetails != NULL &&
13048         gameInfo.resultDetails[0] != NULLCHAR) {
13049         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13050                 PGNResult(gameInfo.result));
13051     } else {
13052         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13053     }
13054
13055     fclose(f);
13056     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13057     return TRUE;
13058 }
13059
13060 /* Save game in old style and close the file */
13061 int
13062 SaveGameOldStyle (FILE *f)
13063 {
13064     int i, offset;
13065     time_t tm;
13066
13067     tm = time((time_t *) NULL);
13068
13069     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13070     PrintOpponents(f);
13071
13072     if (backwardMostMove > 0 || startedFromSetupPosition) {
13073         fprintf(f, "\n[--------------\n");
13074         PrintPosition(f, backwardMostMove);
13075         fprintf(f, "--------------]\n");
13076     } else {
13077         fprintf(f, "\n");
13078     }
13079
13080     i = backwardMostMove;
13081     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13082
13083     while (i < forwardMostMove) {
13084         if (commentList[i] != NULL) {
13085             fprintf(f, "[%s]\n", commentList[i]);
13086         }
13087
13088         if ((i % 2) == 1) {
13089             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13090             i++;
13091         } else {
13092             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13093             i++;
13094             if (commentList[i] != NULL) {
13095                 fprintf(f, "\n");
13096                 continue;
13097             }
13098             if (i >= forwardMostMove) {
13099                 fprintf(f, "\n");
13100                 break;
13101             }
13102             fprintf(f, "%s\n", parseList[i]);
13103             i++;
13104         }
13105     }
13106
13107     if (commentList[i] != NULL) {
13108         fprintf(f, "[%s]\n", commentList[i]);
13109     }
13110
13111     /* This isn't really the old style, but it's close enough */
13112     if (gameInfo.resultDetails != NULL &&
13113         gameInfo.resultDetails[0] != NULLCHAR) {
13114         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13115                 gameInfo.resultDetails);
13116     } else {
13117         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13118     }
13119
13120     fclose(f);
13121     return TRUE;
13122 }
13123
13124 /* Save the current game to open file f and close the file */
13125 int
13126 SaveGame (FILE *f, int dummy, char *dummy2)
13127 {
13128     if (gameMode == EditPosition) EditPositionDone(TRUE);
13129     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13130     if (appData.oldSaveStyle)
13131       return SaveGameOldStyle(f);
13132     else
13133       return SaveGamePGN(f);
13134 }
13135
13136 /* Save the current position to the given file */
13137 int
13138 SavePositionToFile (char *filename)
13139 {
13140     FILE *f;
13141     char buf[MSG_SIZ];
13142
13143     if (strcmp(filename, "-") == 0) {
13144         return SavePosition(stdout, 0, NULL);
13145     } else {
13146         f = fopen(filename, "a");
13147         if (f == NULL) {
13148             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13149             DisplayError(buf, errno);
13150             return FALSE;
13151         } else {
13152             safeStrCpy(buf, lastMsg, MSG_SIZ);
13153             DisplayMessage(_("Waiting for access to save file"), "");
13154             flock(fileno(f), LOCK_EX); // [HGM] lock
13155             DisplayMessage(_("Saving position"), "");
13156             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13157             SavePosition(f, 0, NULL);
13158             DisplayMessage(buf, "");
13159             return TRUE;
13160         }
13161     }
13162 }
13163
13164 /* Save the current position to the given open file and close the file */
13165 int
13166 SavePosition (FILE *f, int dummy, char *dummy2)
13167 {
13168     time_t tm;
13169     char *fen;
13170
13171     if (gameMode == EditPosition) EditPositionDone(TRUE);
13172     if (appData.oldSaveStyle) {
13173         tm = time((time_t *) NULL);
13174
13175         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13176         PrintOpponents(f);
13177         fprintf(f, "[--------------\n");
13178         PrintPosition(f, currentMove);
13179         fprintf(f, "--------------]\n");
13180     } else {
13181         fen = PositionToFEN(currentMove, NULL, 1);
13182         fprintf(f, "%s\n", fen);
13183         free(fen);
13184     }
13185     fclose(f);
13186     return TRUE;
13187 }
13188
13189 void
13190 ReloadCmailMsgEvent (int unregister)
13191 {
13192 #if !WIN32
13193     static char *inFilename = NULL;
13194     static char *outFilename;
13195     int i;
13196     struct stat inbuf, outbuf;
13197     int status;
13198
13199     /* Any registered moves are unregistered if unregister is set, */
13200     /* i.e. invoked by the signal handler */
13201     if (unregister) {
13202         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13203             cmailMoveRegistered[i] = FALSE;
13204             if (cmailCommentList[i] != NULL) {
13205                 free(cmailCommentList[i]);
13206                 cmailCommentList[i] = NULL;
13207             }
13208         }
13209         nCmailMovesRegistered = 0;
13210     }
13211
13212     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13213         cmailResult[i] = CMAIL_NOT_RESULT;
13214     }
13215     nCmailResults = 0;
13216
13217     if (inFilename == NULL) {
13218         /* Because the filenames are static they only get malloced once  */
13219         /* and they never get freed                                      */
13220         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13221         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13222
13223         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13224         sprintf(outFilename, "%s.out", appData.cmailGameName);
13225     }
13226
13227     status = stat(outFilename, &outbuf);
13228     if (status < 0) {
13229         cmailMailedMove = FALSE;
13230     } else {
13231         status = stat(inFilename, &inbuf);
13232         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13233     }
13234
13235     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13236        counts the games, notes how each one terminated, etc.
13237
13238        It would be nice to remove this kludge and instead gather all
13239        the information while building the game list.  (And to keep it
13240        in the game list nodes instead of having a bunch of fixed-size
13241        parallel arrays.)  Note this will require getting each game's
13242        termination from the PGN tags, as the game list builder does
13243        not process the game moves.  --mann
13244        */
13245     cmailMsgLoaded = TRUE;
13246     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13247
13248     /* Load first game in the file or popup game menu */
13249     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13250
13251 #endif /* !WIN32 */
13252     return;
13253 }
13254
13255 int
13256 RegisterMove ()
13257 {
13258     FILE *f;
13259     char string[MSG_SIZ];
13260
13261     if (   cmailMailedMove
13262         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13263         return TRUE;            /* Allow free viewing  */
13264     }
13265
13266     /* Unregister move to ensure that we don't leave RegisterMove        */
13267     /* with the move registered when the conditions for registering no   */
13268     /* longer hold                                                       */
13269     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13270         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13271         nCmailMovesRegistered --;
13272
13273         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13274           {
13275               free(cmailCommentList[lastLoadGameNumber - 1]);
13276               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13277           }
13278     }
13279
13280     if (cmailOldMove == -1) {
13281         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13282         return FALSE;
13283     }
13284
13285     if (currentMove > cmailOldMove + 1) {
13286         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13287         return FALSE;
13288     }
13289
13290     if (currentMove < cmailOldMove) {
13291         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13292         return FALSE;
13293     }
13294
13295     if (forwardMostMove > currentMove) {
13296         /* Silently truncate extra moves */
13297         TruncateGame();
13298     }
13299
13300     if (   (currentMove == cmailOldMove + 1)
13301         || (   (currentMove == cmailOldMove)
13302             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13303                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13304         if (gameInfo.result != GameUnfinished) {
13305             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13306         }
13307
13308         if (commentList[currentMove] != NULL) {
13309             cmailCommentList[lastLoadGameNumber - 1]
13310               = StrSave(commentList[currentMove]);
13311         }
13312         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13313
13314         if (appData.debugMode)
13315           fprintf(debugFP, "Saving %s for game %d\n",
13316                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13317
13318         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13319
13320         f = fopen(string, "w");
13321         if (appData.oldSaveStyle) {
13322             SaveGameOldStyle(f); /* also closes the file */
13323
13324             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13325             f = fopen(string, "w");
13326             SavePosition(f, 0, NULL); /* also closes the file */
13327         } else {
13328             fprintf(f, "{--------------\n");
13329             PrintPosition(f, currentMove);
13330             fprintf(f, "--------------}\n\n");
13331
13332             SaveGame(f, 0, NULL); /* also closes the file*/
13333         }
13334
13335         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13336         nCmailMovesRegistered ++;
13337     } else if (nCmailGames == 1) {
13338         DisplayError(_("You have not made a move yet"), 0);
13339         return FALSE;
13340     }
13341
13342     return TRUE;
13343 }
13344
13345 void
13346 MailMoveEvent ()
13347 {
13348 #if !WIN32
13349     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13350     FILE *commandOutput;
13351     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13352     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13353     int nBuffers;
13354     int i;
13355     int archived;
13356     char *arcDir;
13357
13358     if (! cmailMsgLoaded) {
13359         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13360         return;
13361     }
13362
13363     if (nCmailGames == nCmailResults) {
13364         DisplayError(_("No unfinished games"), 0);
13365         return;
13366     }
13367
13368 #if CMAIL_PROHIBIT_REMAIL
13369     if (cmailMailedMove) {
13370       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);
13371         DisplayError(msg, 0);
13372         return;
13373     }
13374 #endif
13375
13376     if (! (cmailMailedMove || RegisterMove())) return;
13377
13378     if (   cmailMailedMove
13379         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13380       snprintf(string, MSG_SIZ, partCommandString,
13381                appData.debugMode ? " -v" : "", appData.cmailGameName);
13382         commandOutput = popen(string, "r");
13383
13384         if (commandOutput == NULL) {
13385             DisplayError(_("Failed to invoke cmail"), 0);
13386         } else {
13387             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13388                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13389             }
13390             if (nBuffers > 1) {
13391                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13392                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13393                 nBytes = MSG_SIZ - 1;
13394             } else {
13395                 (void) memcpy(msg, buffer, nBytes);
13396             }
13397             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13398
13399             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13400                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13401
13402                 archived = TRUE;
13403                 for (i = 0; i < nCmailGames; i ++) {
13404                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13405                         archived = FALSE;
13406                     }
13407                 }
13408                 if (   archived
13409                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13410                         != NULL)) {
13411                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13412                            arcDir,
13413                            appData.cmailGameName,
13414                            gameInfo.date);
13415                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13416                     cmailMsgLoaded = FALSE;
13417                 }
13418             }
13419
13420             DisplayInformation(msg);
13421             pclose(commandOutput);
13422         }
13423     } else {
13424         if ((*cmailMsg) != '\0') {
13425             DisplayInformation(cmailMsg);
13426         }
13427     }
13428
13429     return;
13430 #endif /* !WIN32 */
13431 }
13432
13433 char *
13434 CmailMsg ()
13435 {
13436 #if WIN32
13437     return NULL;
13438 #else
13439     int  prependComma = 0;
13440     char number[5];
13441     char string[MSG_SIZ];       /* Space for game-list */
13442     int  i;
13443
13444     if (!cmailMsgLoaded) return "";
13445
13446     if (cmailMailedMove) {
13447       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13448     } else {
13449         /* Create a list of games left */
13450       snprintf(string, MSG_SIZ, "[");
13451         for (i = 0; i < nCmailGames; i ++) {
13452             if (! (   cmailMoveRegistered[i]
13453                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13454                 if (prependComma) {
13455                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13456                 } else {
13457                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13458                     prependComma = 1;
13459                 }
13460
13461                 strcat(string, number);
13462             }
13463         }
13464         strcat(string, "]");
13465
13466         if (nCmailMovesRegistered + nCmailResults == 0) {
13467             switch (nCmailGames) {
13468               case 1:
13469                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13470                 break;
13471
13472               case 2:
13473                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13474                 break;
13475
13476               default:
13477                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13478                          nCmailGames);
13479                 break;
13480             }
13481         } else {
13482             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13483               case 1:
13484                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13485                          string);
13486                 break;
13487
13488               case 0:
13489                 if (nCmailResults == nCmailGames) {
13490                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13491                 } else {
13492                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13493                 }
13494                 break;
13495
13496               default:
13497                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13498                          string);
13499             }
13500         }
13501     }
13502     return cmailMsg;
13503 #endif /* WIN32 */
13504 }
13505
13506 void
13507 ResetGameEvent ()
13508 {
13509     if (gameMode == Training)
13510       SetTrainingModeOff();
13511
13512     Reset(TRUE, TRUE);
13513     cmailMsgLoaded = FALSE;
13514     if (appData.icsActive) {
13515       SendToICS(ics_prefix);
13516       SendToICS("refresh\n");
13517     }
13518 }
13519
13520 void
13521 ExitEvent (int status)
13522 {
13523     exiting++;
13524     if (exiting > 2) {
13525       /* Give up on clean exit */
13526       exit(status);
13527     }
13528     if (exiting > 1) {
13529       /* Keep trying for clean exit */
13530       return;
13531     }
13532
13533     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13534
13535     if (telnetISR != NULL) {
13536       RemoveInputSource(telnetISR);
13537     }
13538     if (icsPR != NoProc) {
13539       DestroyChildProcess(icsPR, TRUE);
13540     }
13541
13542     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13543     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13544
13545     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13546     /* make sure this other one finishes before killing it!                  */
13547     if(endingGame) { int count = 0;
13548         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13549         while(endingGame && count++ < 10) DoSleep(1);
13550         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13551     }
13552
13553     /* Kill off chess programs */
13554     if (first.pr != NoProc) {
13555         ExitAnalyzeMode();
13556
13557         DoSleep( appData.delayBeforeQuit );
13558         SendToProgram("quit\n", &first);
13559         DoSleep( appData.delayAfterQuit );
13560         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13561     }
13562     if (second.pr != NoProc) {
13563         DoSleep( appData.delayBeforeQuit );
13564         SendToProgram("quit\n", &second);
13565         DoSleep( appData.delayAfterQuit );
13566         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13567     }
13568     if (first.isr != NULL) {
13569         RemoveInputSource(first.isr);
13570     }
13571     if (second.isr != NULL) {
13572         RemoveInputSource(second.isr);
13573     }
13574
13575     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13576     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13577
13578     ShutDownFrontEnd();
13579     exit(status);
13580 }
13581
13582 void
13583 PauseEngine (ChessProgramState *cps)
13584 {
13585     SendToProgram("pause\n", cps);
13586     cps->pause = 2;
13587 }
13588
13589 void
13590 UnPauseEngine (ChessProgramState *cps)
13591 {
13592     SendToProgram("resume\n", cps);
13593     cps->pause = 1;
13594 }
13595
13596 void
13597 PauseEvent ()
13598 {
13599     if (appData.debugMode)
13600         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13601     if (pausing) {
13602         pausing = FALSE;
13603         ModeHighlight();
13604         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13605             StartClocks();
13606             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13607                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13608                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13609             }
13610             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13611             HandleMachineMove(stashedInputMove, stalledEngine);
13612             stalledEngine = NULL;
13613             return;
13614         }
13615         if (gameMode == MachinePlaysWhite ||
13616             gameMode == TwoMachinesPlay   ||
13617             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13618             if(first.pause)  UnPauseEngine(&first);
13619             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13620             if(second.pause) UnPauseEngine(&second);
13621             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13622             StartClocks();
13623         } else {
13624             DisplayBothClocks();
13625         }
13626         if (gameMode == PlayFromGameFile) {
13627             if (appData.timeDelay >= 0)
13628                 AutoPlayGameLoop();
13629         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13630             Reset(FALSE, TRUE);
13631             SendToICS(ics_prefix);
13632             SendToICS("refresh\n");
13633         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13634             ForwardInner(forwardMostMove);
13635         }
13636         pauseExamInvalid = FALSE;
13637     } else {
13638         switch (gameMode) {
13639           default:
13640             return;
13641           case IcsExamining:
13642             pauseExamForwardMostMove = forwardMostMove;
13643             pauseExamInvalid = FALSE;
13644             /* fall through */
13645           case IcsObserving:
13646           case IcsPlayingWhite:
13647           case IcsPlayingBlack:
13648             pausing = TRUE;
13649             ModeHighlight();
13650             return;
13651           case PlayFromGameFile:
13652             (void) StopLoadGameTimer();
13653             pausing = TRUE;
13654             ModeHighlight();
13655             break;
13656           case BeginningOfGame:
13657             if (appData.icsActive) return;
13658             /* else fall through */
13659           case MachinePlaysWhite:
13660           case MachinePlaysBlack:
13661           case TwoMachinesPlay:
13662             if (forwardMostMove == 0)
13663               return;           /* don't pause if no one has moved */
13664             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13665                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13666                 if(onMove->pause) {           // thinking engine can be paused
13667                     PauseEngine(onMove);      // do it
13668                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13669                         PauseEngine(onMove->other);
13670                     else
13671                         SendToProgram("easy\n", onMove->other);
13672                     StopClocks();
13673                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13674             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13675                 if(first.pause) {
13676                     PauseEngine(&first);
13677                     StopClocks();
13678                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13679             } else { // human on move, pause pondering by either method
13680                 if(first.pause)
13681                     PauseEngine(&first);
13682                 else if(appData.ponderNextMove)
13683                     SendToProgram("easy\n", &first);
13684                 StopClocks();
13685             }
13686             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13687           case AnalyzeMode:
13688             pausing = TRUE;
13689             ModeHighlight();
13690             break;
13691         }
13692     }
13693 }
13694
13695 void
13696 EditCommentEvent ()
13697 {
13698     char title[MSG_SIZ];
13699
13700     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13701       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13702     } else {
13703       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13704                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13705                parseList[currentMove - 1]);
13706     }
13707
13708     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13709 }
13710
13711
13712 void
13713 EditTagsEvent ()
13714 {
13715     char *tags = PGNTags(&gameInfo);
13716     bookUp = FALSE;
13717     EditTagsPopUp(tags, NULL);
13718     free(tags);
13719 }
13720
13721 void
13722 ToggleSecond ()
13723 {
13724   if(second.analyzing) {
13725     SendToProgram("exit\n", &second);
13726     second.analyzing = FALSE;
13727   } else {
13728     if (second.pr == NoProc) StartChessProgram(&second);
13729     InitChessProgram(&second, FALSE);
13730     FeedMovesToProgram(&second, currentMove);
13731
13732     SendToProgram("analyze\n", &second);
13733     second.analyzing = TRUE;
13734   }
13735 }
13736
13737 /* Toggle ShowThinking */
13738 void
13739 ToggleShowThinking()
13740 {
13741   appData.showThinking = !appData.showThinking;
13742   ShowThinkingEvent();
13743 }
13744
13745 int
13746 AnalyzeModeEvent ()
13747 {
13748     char buf[MSG_SIZ];
13749
13750     if (!first.analysisSupport) {
13751       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13752       DisplayError(buf, 0);
13753       return 0;
13754     }
13755     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13756     if (appData.icsActive) {
13757         if (gameMode != IcsObserving) {
13758           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13759             DisplayError(buf, 0);
13760             /* secure check */
13761             if (appData.icsEngineAnalyze) {
13762                 if (appData.debugMode)
13763                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13764                 ExitAnalyzeMode();
13765                 ModeHighlight();
13766             }
13767             return 0;
13768         }
13769         /* if enable, user wants to disable icsEngineAnalyze */
13770         if (appData.icsEngineAnalyze) {
13771                 ExitAnalyzeMode();
13772                 ModeHighlight();
13773                 return 0;
13774         }
13775         appData.icsEngineAnalyze = TRUE;
13776         if (appData.debugMode)
13777             fprintf(debugFP, "ICS engine analyze starting... \n");
13778     }
13779
13780     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13781     if (appData.noChessProgram || gameMode == AnalyzeMode)
13782       return 0;
13783
13784     if (gameMode != AnalyzeFile) {
13785         if (!appData.icsEngineAnalyze) {
13786                EditGameEvent();
13787                if (gameMode != EditGame) return 0;
13788         }
13789         if (!appData.showThinking) ToggleShowThinking();
13790         ResurrectChessProgram();
13791         SendToProgram("analyze\n", &first);
13792         first.analyzing = TRUE;
13793         /*first.maybeThinking = TRUE;*/
13794         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13795         EngineOutputPopUp();
13796     }
13797     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13798     pausing = FALSE;
13799     ModeHighlight();
13800     SetGameInfo();
13801
13802     StartAnalysisClock();
13803     GetTimeMark(&lastNodeCountTime);
13804     lastNodeCount = 0;
13805     return 1;
13806 }
13807
13808 void
13809 AnalyzeFileEvent ()
13810 {
13811     if (appData.noChessProgram || gameMode == AnalyzeFile)
13812       return;
13813
13814     if (!first.analysisSupport) {
13815       char buf[MSG_SIZ];
13816       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13817       DisplayError(buf, 0);
13818       return;
13819     }
13820
13821     if (gameMode != AnalyzeMode) {
13822         keepInfo = 1; // mere annotating should not alter PGN tags
13823         EditGameEvent();
13824         keepInfo = 0;
13825         if (gameMode != EditGame) return;
13826         if (!appData.showThinking) ToggleShowThinking();
13827         ResurrectChessProgram();
13828         SendToProgram("analyze\n", &first);
13829         first.analyzing = TRUE;
13830         /*first.maybeThinking = TRUE;*/
13831         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13832         EngineOutputPopUp();
13833     }
13834     gameMode = AnalyzeFile;
13835     pausing = FALSE;
13836     ModeHighlight();
13837
13838     StartAnalysisClock();
13839     GetTimeMark(&lastNodeCountTime);
13840     lastNodeCount = 0;
13841     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13842     AnalysisPeriodicEvent(1);
13843 }
13844
13845 void
13846 MachineWhiteEvent ()
13847 {
13848     char buf[MSG_SIZ];
13849     char *bookHit = NULL;
13850
13851     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13852       return;
13853
13854
13855     if (gameMode == PlayFromGameFile ||
13856         gameMode == TwoMachinesPlay  ||
13857         gameMode == Training         ||
13858         gameMode == AnalyzeMode      ||
13859         gameMode == EndOfGame)
13860         EditGameEvent();
13861
13862     if (gameMode == EditPosition)
13863         EditPositionDone(TRUE);
13864
13865     if (!WhiteOnMove(currentMove)) {
13866         DisplayError(_("It is not White's turn"), 0);
13867         return;
13868     }
13869
13870     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13871       ExitAnalyzeMode();
13872
13873     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13874         gameMode == AnalyzeFile)
13875         TruncateGame();
13876
13877     ResurrectChessProgram();    /* in case it isn't running */
13878     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13879         gameMode = MachinePlaysWhite;
13880         ResetClocks();
13881     } else
13882     gameMode = MachinePlaysWhite;
13883     pausing = FALSE;
13884     ModeHighlight();
13885     SetGameInfo();
13886     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13887     DisplayTitle(buf);
13888     if (first.sendName) {
13889       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13890       SendToProgram(buf, &first);
13891     }
13892     if (first.sendTime) {
13893       if (first.useColors) {
13894         SendToProgram("black\n", &first); /*gnu kludge*/
13895       }
13896       SendTimeRemaining(&first, TRUE);
13897     }
13898     if (first.useColors) {
13899       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13900     }
13901     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13902     SetMachineThinkingEnables();
13903     first.maybeThinking = TRUE;
13904     StartClocks();
13905     firstMove = FALSE;
13906
13907     if (appData.autoFlipView && !flipView) {
13908       flipView = !flipView;
13909       DrawPosition(FALSE, NULL);
13910       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13911     }
13912
13913     if(bookHit) { // [HGM] book: simulate book reply
13914         static char bookMove[MSG_SIZ]; // a bit generous?
13915
13916         programStats.nodes = programStats.depth = programStats.time =
13917         programStats.score = programStats.got_only_move = 0;
13918         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13919
13920         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13921         strcat(bookMove, bookHit);
13922         HandleMachineMove(bookMove, &first);
13923     }
13924 }
13925
13926 void
13927 MachineBlackEvent ()
13928 {
13929   char buf[MSG_SIZ];
13930   char *bookHit = NULL;
13931
13932     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13933         return;
13934
13935
13936     if (gameMode == PlayFromGameFile ||
13937         gameMode == TwoMachinesPlay  ||
13938         gameMode == Training         ||
13939         gameMode == AnalyzeMode      ||
13940         gameMode == EndOfGame)
13941         EditGameEvent();
13942
13943     if (gameMode == EditPosition)
13944         EditPositionDone(TRUE);
13945
13946     if (WhiteOnMove(currentMove)) {
13947         DisplayError(_("It is not Black's turn"), 0);
13948         return;
13949     }
13950
13951     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13952       ExitAnalyzeMode();
13953
13954     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13955         gameMode == AnalyzeFile)
13956         TruncateGame();
13957
13958     ResurrectChessProgram();    /* in case it isn't running */
13959     gameMode = MachinePlaysBlack;
13960     pausing = FALSE;
13961     ModeHighlight();
13962     SetGameInfo();
13963     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13964     DisplayTitle(buf);
13965     if (first.sendName) {
13966       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13967       SendToProgram(buf, &first);
13968     }
13969     if (first.sendTime) {
13970       if (first.useColors) {
13971         SendToProgram("white\n", &first); /*gnu kludge*/
13972       }
13973       SendTimeRemaining(&first, FALSE);
13974     }
13975     if (first.useColors) {
13976       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13977     }
13978     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13979     SetMachineThinkingEnables();
13980     first.maybeThinking = TRUE;
13981     StartClocks();
13982
13983     if (appData.autoFlipView && flipView) {
13984       flipView = !flipView;
13985       DrawPosition(FALSE, NULL);
13986       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13987     }
13988     if(bookHit) { // [HGM] book: simulate book reply
13989         static char bookMove[MSG_SIZ]; // a bit generous?
13990
13991         programStats.nodes = programStats.depth = programStats.time =
13992         programStats.score = programStats.got_only_move = 0;
13993         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13994
13995         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13996         strcat(bookMove, bookHit);
13997         HandleMachineMove(bookMove, &first);
13998     }
13999 }
14000
14001
14002 void
14003 DisplayTwoMachinesTitle ()
14004 {
14005     char buf[MSG_SIZ];
14006     if (appData.matchGames > 0) {
14007         if(appData.tourneyFile[0]) {
14008           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14009                    gameInfo.white, _("vs."), gameInfo.black,
14010                    nextGame+1, appData.matchGames+1,
14011                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14012         } else
14013         if (first.twoMachinesColor[0] == 'w') {
14014           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14015                    gameInfo.white, _("vs."),  gameInfo.black,
14016                    first.matchWins, second.matchWins,
14017                    matchGame - 1 - (first.matchWins + second.matchWins));
14018         } else {
14019           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14020                    gameInfo.white, _("vs."), gameInfo.black,
14021                    second.matchWins, first.matchWins,
14022                    matchGame - 1 - (first.matchWins + second.matchWins));
14023         }
14024     } else {
14025       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14026     }
14027     DisplayTitle(buf);
14028 }
14029
14030 void
14031 SettingsMenuIfReady ()
14032 {
14033   if (second.lastPing != second.lastPong) {
14034     DisplayMessage("", _("Waiting for second chess program"));
14035     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14036     return;
14037   }
14038   ThawUI();
14039   DisplayMessage("", "");
14040   SettingsPopUp(&second);
14041 }
14042
14043 int
14044 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14045 {
14046     char buf[MSG_SIZ];
14047     if (cps->pr == NoProc) {
14048         StartChessProgram(cps);
14049         if (cps->protocolVersion == 1) {
14050           retry();
14051           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14052         } else {
14053           /* kludge: allow timeout for initial "feature" command */
14054           if(retry != TwoMachinesEventIfReady) FreezeUI();
14055           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14056           DisplayMessage("", buf);
14057           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14058         }
14059         return 1;
14060     }
14061     return 0;
14062 }
14063
14064 void
14065 TwoMachinesEvent P((void))
14066 {
14067     int i;
14068     char buf[MSG_SIZ];
14069     ChessProgramState *onmove;
14070     char *bookHit = NULL;
14071     static int stalling = 0;
14072     TimeMark now;
14073     long wait;
14074
14075     if (appData.noChessProgram) return;
14076
14077     switch (gameMode) {
14078       case TwoMachinesPlay:
14079         return;
14080       case MachinePlaysWhite:
14081       case MachinePlaysBlack:
14082         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14083             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14084             return;
14085         }
14086         /* fall through */
14087       case BeginningOfGame:
14088       case PlayFromGameFile:
14089       case EndOfGame:
14090         EditGameEvent();
14091         if (gameMode != EditGame) return;
14092         break;
14093       case EditPosition:
14094         EditPositionDone(TRUE);
14095         break;
14096       case AnalyzeMode:
14097       case AnalyzeFile:
14098         ExitAnalyzeMode();
14099         break;
14100       case EditGame:
14101       default:
14102         break;
14103     }
14104
14105 //    forwardMostMove = currentMove;
14106     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14107     startingEngine = TRUE;
14108
14109     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14110
14111     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14112     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14113       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14114       return;
14115     }
14116     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14117
14118     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14119         startingEngine = FALSE;
14120         DisplayError("second engine does not play this", 0);
14121         return;
14122     }
14123
14124     if(!stalling) {
14125       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14126       SendToProgram("force\n", &second);
14127       stalling = 1;
14128       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14129       return;
14130     }
14131     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14132     if(appData.matchPause>10000 || appData.matchPause<10)
14133                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14134     wait = SubtractTimeMarks(&now, &pauseStart);
14135     if(wait < appData.matchPause) {
14136         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14137         return;
14138     }
14139     // we are now committed to starting the game
14140     stalling = 0;
14141     DisplayMessage("", "");
14142     if (startedFromSetupPosition) {
14143         SendBoard(&second, backwardMostMove);
14144     if (appData.debugMode) {
14145         fprintf(debugFP, "Two Machines\n");
14146     }
14147     }
14148     for (i = backwardMostMove; i < forwardMostMove; i++) {
14149         SendMoveToProgram(i, &second);
14150     }
14151
14152     gameMode = TwoMachinesPlay;
14153     pausing = startingEngine = FALSE;
14154     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14155     SetGameInfo();
14156     DisplayTwoMachinesTitle();
14157     firstMove = TRUE;
14158     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14159         onmove = &first;
14160     } else {
14161         onmove = &second;
14162     }
14163     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14164     SendToProgram(first.computerString, &first);
14165     if (first.sendName) {
14166       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14167       SendToProgram(buf, &first);
14168     }
14169     SendToProgram(second.computerString, &second);
14170     if (second.sendName) {
14171       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14172       SendToProgram(buf, &second);
14173     }
14174
14175     ResetClocks();
14176     if (!first.sendTime || !second.sendTime) {
14177         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14178         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14179     }
14180     if (onmove->sendTime) {
14181       if (onmove->useColors) {
14182         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14183       }
14184       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14185     }
14186     if (onmove->useColors) {
14187       SendToProgram(onmove->twoMachinesColor, onmove);
14188     }
14189     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14190 //    SendToProgram("go\n", onmove);
14191     onmove->maybeThinking = TRUE;
14192     SetMachineThinkingEnables();
14193
14194     StartClocks();
14195
14196     if(bookHit) { // [HGM] book: simulate book reply
14197         static char bookMove[MSG_SIZ]; // a bit generous?
14198
14199         programStats.nodes = programStats.depth = programStats.time =
14200         programStats.score = programStats.got_only_move = 0;
14201         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14202
14203         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14204         strcat(bookMove, bookHit);
14205         savedMessage = bookMove; // args for deferred call
14206         savedState = onmove;
14207         ScheduleDelayedEvent(DeferredBookMove, 1);
14208     }
14209 }
14210
14211 void
14212 TrainingEvent ()
14213 {
14214     if (gameMode == Training) {
14215       SetTrainingModeOff();
14216       gameMode = PlayFromGameFile;
14217       DisplayMessage("", _("Training mode off"));
14218     } else {
14219       gameMode = Training;
14220       animateTraining = appData.animate;
14221
14222       /* make sure we are not already at the end of the game */
14223       if (currentMove < forwardMostMove) {
14224         SetTrainingModeOn();
14225         DisplayMessage("", _("Training mode on"));
14226       } else {
14227         gameMode = PlayFromGameFile;
14228         DisplayError(_("Already at end of game"), 0);
14229       }
14230     }
14231     ModeHighlight();
14232 }
14233
14234 void
14235 IcsClientEvent ()
14236 {
14237     if (!appData.icsActive) return;
14238     switch (gameMode) {
14239       case IcsPlayingWhite:
14240       case IcsPlayingBlack:
14241       case IcsObserving:
14242       case IcsIdle:
14243       case BeginningOfGame:
14244       case IcsExamining:
14245         return;
14246
14247       case EditGame:
14248         break;
14249
14250       case EditPosition:
14251         EditPositionDone(TRUE);
14252         break;
14253
14254       case AnalyzeMode:
14255       case AnalyzeFile:
14256         ExitAnalyzeMode();
14257         break;
14258
14259       default:
14260         EditGameEvent();
14261         break;
14262     }
14263
14264     gameMode = IcsIdle;
14265     ModeHighlight();
14266     return;
14267 }
14268
14269 void
14270 EditGameEvent ()
14271 {
14272     int i;
14273
14274     switch (gameMode) {
14275       case Training:
14276         SetTrainingModeOff();
14277         break;
14278       case MachinePlaysWhite:
14279       case MachinePlaysBlack:
14280       case BeginningOfGame:
14281         SendToProgram("force\n", &first);
14282         SetUserThinkingEnables();
14283         break;
14284       case PlayFromGameFile:
14285         (void) StopLoadGameTimer();
14286         if (gameFileFP != NULL) {
14287             gameFileFP = NULL;
14288         }
14289         break;
14290       case EditPosition:
14291         EditPositionDone(TRUE);
14292         break;
14293       case AnalyzeMode:
14294       case AnalyzeFile:
14295         ExitAnalyzeMode();
14296         SendToProgram("force\n", &first);
14297         break;
14298       case TwoMachinesPlay:
14299         GameEnds(EndOfFile, NULL, GE_PLAYER);
14300         ResurrectChessProgram();
14301         SetUserThinkingEnables();
14302         break;
14303       case EndOfGame:
14304         ResurrectChessProgram();
14305         break;
14306       case IcsPlayingBlack:
14307       case IcsPlayingWhite:
14308         DisplayError(_("Warning: You are still playing a game"), 0);
14309         break;
14310       case IcsObserving:
14311         DisplayError(_("Warning: You are still observing a game"), 0);
14312         break;
14313       case IcsExamining:
14314         DisplayError(_("Warning: You are still examining a game"), 0);
14315         break;
14316       case IcsIdle:
14317         break;
14318       case EditGame:
14319       default:
14320         return;
14321     }
14322
14323     pausing = FALSE;
14324     StopClocks();
14325     first.offeredDraw = second.offeredDraw = 0;
14326
14327     if (gameMode == PlayFromGameFile) {
14328         whiteTimeRemaining = timeRemaining[0][currentMove];
14329         blackTimeRemaining = timeRemaining[1][currentMove];
14330         DisplayTitle("");
14331     }
14332
14333     if (gameMode == MachinePlaysWhite ||
14334         gameMode == MachinePlaysBlack ||
14335         gameMode == TwoMachinesPlay ||
14336         gameMode == EndOfGame) {
14337         i = forwardMostMove;
14338         while (i > currentMove) {
14339             SendToProgram("undo\n", &first);
14340             i--;
14341         }
14342         if(!adjustedClock) {
14343         whiteTimeRemaining = timeRemaining[0][currentMove];
14344         blackTimeRemaining = timeRemaining[1][currentMove];
14345         DisplayBothClocks();
14346         }
14347         if (whiteFlag || blackFlag) {
14348             whiteFlag = blackFlag = 0;
14349         }
14350         DisplayTitle("");
14351     }
14352
14353     gameMode = EditGame;
14354     ModeHighlight();
14355     SetGameInfo();
14356 }
14357
14358
14359 void
14360 EditPositionEvent ()
14361 {
14362     if (gameMode == EditPosition) {
14363         EditGameEvent();
14364         return;
14365     }
14366
14367     EditGameEvent();
14368     if (gameMode != EditGame) return;
14369
14370     gameMode = EditPosition;
14371     ModeHighlight();
14372     SetGameInfo();
14373     if (currentMove > 0)
14374       CopyBoard(boards[0], boards[currentMove]);
14375
14376     blackPlaysFirst = !WhiteOnMove(currentMove);
14377     ResetClocks();
14378     currentMove = forwardMostMove = backwardMostMove = 0;
14379     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14380     DisplayMove(-1);
14381     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14382 }
14383
14384 void
14385 ExitAnalyzeMode ()
14386 {
14387     /* [DM] icsEngineAnalyze - possible call from other functions */
14388     if (appData.icsEngineAnalyze) {
14389         appData.icsEngineAnalyze = FALSE;
14390
14391         DisplayMessage("",_("Close ICS engine analyze..."));
14392     }
14393     if (first.analysisSupport && first.analyzing) {
14394       SendToBoth("exit\n");
14395       first.analyzing = second.analyzing = FALSE;
14396     }
14397     thinkOutput[0] = NULLCHAR;
14398 }
14399
14400 void
14401 EditPositionDone (Boolean fakeRights)
14402 {
14403     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14404
14405     startedFromSetupPosition = TRUE;
14406     InitChessProgram(&first, FALSE);
14407     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14408       boards[0][EP_STATUS] = EP_NONE;
14409       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14410       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14411         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14412         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14413       } else boards[0][CASTLING][2] = NoRights;
14414       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14415         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14416         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14417       } else boards[0][CASTLING][5] = NoRights;
14418       if(gameInfo.variant == VariantSChess) {
14419         int i;
14420         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14421           boards[0][VIRGIN][i] = 0;
14422           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14423           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14424         }
14425       }
14426     }
14427     SendToProgram("force\n", &first);
14428     if (blackPlaysFirst) {
14429         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14430         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14431         currentMove = forwardMostMove = backwardMostMove = 1;
14432         CopyBoard(boards[1], boards[0]);
14433     } else {
14434         currentMove = forwardMostMove = backwardMostMove = 0;
14435     }
14436     SendBoard(&first, forwardMostMove);
14437     if (appData.debugMode) {
14438         fprintf(debugFP, "EditPosDone\n");
14439     }
14440     DisplayTitle("");
14441     DisplayMessage("", "");
14442     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14443     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14444     gameMode = EditGame;
14445     ModeHighlight();
14446     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14447     ClearHighlights(); /* [AS] */
14448 }
14449
14450 /* Pause for `ms' milliseconds */
14451 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14452 void
14453 TimeDelay (long ms)
14454 {
14455     TimeMark m1, m2;
14456
14457     GetTimeMark(&m1);
14458     do {
14459         GetTimeMark(&m2);
14460     } while (SubtractTimeMarks(&m2, &m1) < ms);
14461 }
14462
14463 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14464 void
14465 SendMultiLineToICS (char *buf)
14466 {
14467     char temp[MSG_SIZ+1], *p;
14468     int len;
14469
14470     len = strlen(buf);
14471     if (len > MSG_SIZ)
14472       len = MSG_SIZ;
14473
14474     strncpy(temp, buf, len);
14475     temp[len] = 0;
14476
14477     p = temp;
14478     while (*p) {
14479         if (*p == '\n' || *p == '\r')
14480           *p = ' ';
14481         ++p;
14482     }
14483
14484     strcat(temp, "\n");
14485     SendToICS(temp);
14486     SendToPlayer(temp, strlen(temp));
14487 }
14488
14489 void
14490 SetWhiteToPlayEvent ()
14491 {
14492     if (gameMode == EditPosition) {
14493         blackPlaysFirst = FALSE;
14494         DisplayBothClocks();    /* works because currentMove is 0 */
14495     } else if (gameMode == IcsExamining) {
14496         SendToICS(ics_prefix);
14497         SendToICS("tomove white\n");
14498     }
14499 }
14500
14501 void
14502 SetBlackToPlayEvent ()
14503 {
14504     if (gameMode == EditPosition) {
14505         blackPlaysFirst = TRUE;
14506         currentMove = 1;        /* kludge */
14507         DisplayBothClocks();
14508         currentMove = 0;
14509     } else if (gameMode == IcsExamining) {
14510         SendToICS(ics_prefix);
14511         SendToICS("tomove black\n");
14512     }
14513 }
14514
14515 void
14516 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14517 {
14518     char buf[MSG_SIZ];
14519     ChessSquare piece = boards[0][y][x];
14520
14521     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14522
14523     switch (selection) {
14524       case ClearBoard:
14525         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14526             SendToICS(ics_prefix);
14527             SendToICS("bsetup clear\n");
14528         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14529             SendToICS(ics_prefix);
14530             SendToICS("clearboard\n");
14531         } else {
14532             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14533                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14534                 for (y = 0; y < BOARD_HEIGHT; y++) {
14535                     if (gameMode == IcsExamining) {
14536                         if (boards[currentMove][y][x] != EmptySquare) {
14537                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14538                                     AAA + x, ONE + y);
14539                             SendToICS(buf);
14540                         }
14541                     } else {
14542                         boards[0][y][x] = p;
14543                     }
14544                 }
14545             }
14546         }
14547         if (gameMode == EditPosition) {
14548             DrawPosition(FALSE, boards[0]);
14549         }
14550         break;
14551
14552       case WhitePlay:
14553         SetWhiteToPlayEvent();
14554         break;
14555
14556       case BlackPlay:
14557         SetBlackToPlayEvent();
14558         break;
14559
14560       case EmptySquare:
14561         if (gameMode == IcsExamining) {
14562             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14563             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14564             SendToICS(buf);
14565         } else {
14566             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14567                 if(x == BOARD_LEFT-2) {
14568                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14569                     boards[0][y][1] = 0;
14570                 } else
14571                 if(x == BOARD_RGHT+1) {
14572                     if(y >= gameInfo.holdingsSize) break;
14573                     boards[0][y][BOARD_WIDTH-2] = 0;
14574                 } else break;
14575             }
14576             boards[0][y][x] = EmptySquare;
14577             DrawPosition(FALSE, boards[0]);
14578         }
14579         break;
14580
14581       case PromotePiece:
14582         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14583            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14584             selection = (ChessSquare) (PROMOTED piece);
14585         } else if(piece == EmptySquare) selection = WhiteSilver;
14586         else selection = (ChessSquare)((int)piece - 1);
14587         goto defaultlabel;
14588
14589       case DemotePiece:
14590         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14591            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14592             selection = (ChessSquare) (DEMOTED piece);
14593         } else if(piece == EmptySquare) selection = BlackSilver;
14594         else selection = (ChessSquare)((int)piece + 1);
14595         goto defaultlabel;
14596
14597       case WhiteQueen:
14598       case BlackQueen:
14599         if(gameInfo.variant == VariantShatranj ||
14600            gameInfo.variant == VariantXiangqi  ||
14601            gameInfo.variant == VariantCourier  ||
14602            gameInfo.variant == VariantASEAN    ||
14603            gameInfo.variant == VariantMakruk     )
14604             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14605         goto defaultlabel;
14606
14607       case WhiteKing:
14608       case BlackKing:
14609         if(gameInfo.variant == VariantXiangqi)
14610             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14611         if(gameInfo.variant == VariantKnightmate)
14612             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14613       default:
14614         defaultlabel:
14615         if (gameMode == IcsExamining) {
14616             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14617             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14618                      PieceToChar(selection), AAA + x, ONE + y);
14619             SendToICS(buf);
14620         } else {
14621             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14622                 int n;
14623                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14624                     n = PieceToNumber(selection - BlackPawn);
14625                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14626                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14627                     boards[0][BOARD_HEIGHT-1-n][1]++;
14628                 } else
14629                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14630                     n = PieceToNumber(selection);
14631                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14632                     boards[0][n][BOARD_WIDTH-1] = selection;
14633                     boards[0][n][BOARD_WIDTH-2]++;
14634                 }
14635             } else
14636             boards[0][y][x] = selection;
14637             DrawPosition(TRUE, boards[0]);
14638             ClearHighlights();
14639             fromX = fromY = -1;
14640         }
14641         break;
14642     }
14643 }
14644
14645
14646 void
14647 DropMenuEvent (ChessSquare selection, int x, int y)
14648 {
14649     ChessMove moveType;
14650
14651     switch (gameMode) {
14652       case IcsPlayingWhite:
14653       case MachinePlaysBlack:
14654         if (!WhiteOnMove(currentMove)) {
14655             DisplayMoveError(_("It is Black's turn"));
14656             return;
14657         }
14658         moveType = WhiteDrop;
14659         break;
14660       case IcsPlayingBlack:
14661       case MachinePlaysWhite:
14662         if (WhiteOnMove(currentMove)) {
14663             DisplayMoveError(_("It is White's turn"));
14664             return;
14665         }
14666         moveType = BlackDrop;
14667         break;
14668       case EditGame:
14669         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14670         break;
14671       default:
14672         return;
14673     }
14674
14675     if (moveType == BlackDrop && selection < BlackPawn) {
14676       selection = (ChessSquare) ((int) selection
14677                                  + (int) BlackPawn - (int) WhitePawn);
14678     }
14679     if (boards[currentMove][y][x] != EmptySquare) {
14680         DisplayMoveError(_("That square is occupied"));
14681         return;
14682     }
14683
14684     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14685 }
14686
14687 void
14688 AcceptEvent ()
14689 {
14690     /* Accept a pending offer of any kind from opponent */
14691
14692     if (appData.icsActive) {
14693         SendToICS(ics_prefix);
14694         SendToICS("accept\n");
14695     } else if (cmailMsgLoaded) {
14696         if (currentMove == cmailOldMove &&
14697             commentList[cmailOldMove] != NULL &&
14698             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14699                    "Black offers a draw" : "White offers a draw")) {
14700             TruncateGame();
14701             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14702             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14703         } else {
14704             DisplayError(_("There is no pending offer on this move"), 0);
14705             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14706         }
14707     } else {
14708         /* Not used for offers from chess program */
14709     }
14710 }
14711
14712 void
14713 DeclineEvent ()
14714 {
14715     /* Decline a pending offer of any kind from opponent */
14716
14717     if (appData.icsActive) {
14718         SendToICS(ics_prefix);
14719         SendToICS("decline\n");
14720     } else if (cmailMsgLoaded) {
14721         if (currentMove == cmailOldMove &&
14722             commentList[cmailOldMove] != NULL &&
14723             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14724                    "Black offers a draw" : "White offers a draw")) {
14725 #ifdef NOTDEF
14726             AppendComment(cmailOldMove, "Draw declined", TRUE);
14727             DisplayComment(cmailOldMove - 1, "Draw declined");
14728 #endif /*NOTDEF*/
14729         } else {
14730             DisplayError(_("There is no pending offer on this move"), 0);
14731         }
14732     } else {
14733         /* Not used for offers from chess program */
14734     }
14735 }
14736
14737 void
14738 RematchEvent ()
14739 {
14740     /* Issue ICS rematch command */
14741     if (appData.icsActive) {
14742         SendToICS(ics_prefix);
14743         SendToICS("rematch\n");
14744     }
14745 }
14746
14747 void
14748 CallFlagEvent ()
14749 {
14750     /* Call your opponent's flag (claim a win on time) */
14751     if (appData.icsActive) {
14752         SendToICS(ics_prefix);
14753         SendToICS("flag\n");
14754     } else {
14755         switch (gameMode) {
14756           default:
14757             return;
14758           case MachinePlaysWhite:
14759             if (whiteFlag) {
14760                 if (blackFlag)
14761                   GameEnds(GameIsDrawn, "Both players ran out of time",
14762                            GE_PLAYER);
14763                 else
14764                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14765             } else {
14766                 DisplayError(_("Your opponent is not out of time"), 0);
14767             }
14768             break;
14769           case MachinePlaysBlack:
14770             if (blackFlag) {
14771                 if (whiteFlag)
14772                   GameEnds(GameIsDrawn, "Both players ran out of time",
14773                            GE_PLAYER);
14774                 else
14775                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14776             } else {
14777                 DisplayError(_("Your opponent is not out of time"), 0);
14778             }
14779             break;
14780         }
14781     }
14782 }
14783
14784 void
14785 ClockClick (int which)
14786 {       // [HGM] code moved to back-end from winboard.c
14787         if(which) { // black clock
14788           if (gameMode == EditPosition || gameMode == IcsExamining) {
14789             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14790             SetBlackToPlayEvent();
14791           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14792           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14793           } else if (shiftKey) {
14794             AdjustClock(which, -1);
14795           } else if (gameMode == IcsPlayingWhite ||
14796                      gameMode == MachinePlaysBlack) {
14797             CallFlagEvent();
14798           }
14799         } else { // white clock
14800           if (gameMode == EditPosition || gameMode == IcsExamining) {
14801             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14802             SetWhiteToPlayEvent();
14803           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14804           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14805           } else if (shiftKey) {
14806             AdjustClock(which, -1);
14807           } else if (gameMode == IcsPlayingBlack ||
14808                    gameMode == MachinePlaysWhite) {
14809             CallFlagEvent();
14810           }
14811         }
14812 }
14813
14814 void
14815 DrawEvent ()
14816 {
14817     /* Offer draw or accept pending draw offer from opponent */
14818
14819     if (appData.icsActive) {
14820         /* Note: tournament rules require draw offers to be
14821            made after you make your move but before you punch
14822            your clock.  Currently ICS doesn't let you do that;
14823            instead, you immediately punch your clock after making
14824            a move, but you can offer a draw at any time. */
14825
14826         SendToICS(ics_prefix);
14827         SendToICS("draw\n");
14828         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14829     } else if (cmailMsgLoaded) {
14830         if (currentMove == cmailOldMove &&
14831             commentList[cmailOldMove] != NULL &&
14832             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14833                    "Black offers a draw" : "White offers a draw")) {
14834             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14835             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14836         } else if (currentMove == cmailOldMove + 1) {
14837             char *offer = WhiteOnMove(cmailOldMove) ?
14838               "White offers a draw" : "Black offers a draw";
14839             AppendComment(currentMove, offer, TRUE);
14840             DisplayComment(currentMove - 1, offer);
14841             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14842         } else {
14843             DisplayError(_("You must make your move before offering a draw"), 0);
14844             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14845         }
14846     } else if (first.offeredDraw) {
14847         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14848     } else {
14849         if (first.sendDrawOffers) {
14850             SendToProgram("draw\n", &first);
14851             userOfferedDraw = TRUE;
14852         }
14853     }
14854 }
14855
14856 void
14857 AdjournEvent ()
14858 {
14859     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14860
14861     if (appData.icsActive) {
14862         SendToICS(ics_prefix);
14863         SendToICS("adjourn\n");
14864     } else {
14865         /* Currently GNU Chess doesn't offer or accept Adjourns */
14866     }
14867 }
14868
14869
14870 void
14871 AbortEvent ()
14872 {
14873     /* Offer Abort or accept pending Abort offer from opponent */
14874
14875     if (appData.icsActive) {
14876         SendToICS(ics_prefix);
14877         SendToICS("abort\n");
14878     } else {
14879         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14880     }
14881 }
14882
14883 void
14884 ResignEvent ()
14885 {
14886     /* Resign.  You can do this even if it's not your turn. */
14887
14888     if (appData.icsActive) {
14889         SendToICS(ics_prefix);
14890         SendToICS("resign\n");
14891     } else {
14892         switch (gameMode) {
14893           case MachinePlaysWhite:
14894             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14895             break;
14896           case MachinePlaysBlack:
14897             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14898             break;
14899           case EditGame:
14900             if (cmailMsgLoaded) {
14901                 TruncateGame();
14902                 if (WhiteOnMove(cmailOldMove)) {
14903                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14904                 } else {
14905                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14906                 }
14907                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14908             }
14909             break;
14910           default:
14911             break;
14912         }
14913     }
14914 }
14915
14916
14917 void
14918 StopObservingEvent ()
14919 {
14920     /* Stop observing current games */
14921     SendToICS(ics_prefix);
14922     SendToICS("unobserve\n");
14923 }
14924
14925 void
14926 StopExaminingEvent ()
14927 {
14928     /* Stop observing current game */
14929     SendToICS(ics_prefix);
14930     SendToICS("unexamine\n");
14931 }
14932
14933 void
14934 ForwardInner (int target)
14935 {
14936     int limit; int oldSeekGraphUp = seekGraphUp;
14937
14938     if (appData.debugMode)
14939         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14940                 target, currentMove, forwardMostMove);
14941
14942     if (gameMode == EditPosition)
14943       return;
14944
14945     seekGraphUp = FALSE;
14946     MarkTargetSquares(1);
14947
14948     if (gameMode == PlayFromGameFile && !pausing)
14949       PauseEvent();
14950
14951     if (gameMode == IcsExamining && pausing)
14952       limit = pauseExamForwardMostMove;
14953     else
14954       limit = forwardMostMove;
14955
14956     if (target > limit) target = limit;
14957
14958     if (target > 0 && moveList[target - 1][0]) {
14959         int fromX, fromY, toX, toY;
14960         toX = moveList[target - 1][2] - AAA;
14961         toY = moveList[target - 1][3] - ONE;
14962         if (moveList[target - 1][1] == '@') {
14963             if (appData.highlightLastMove) {
14964                 SetHighlights(-1, -1, toX, toY);
14965             }
14966         } else {
14967             fromX = moveList[target - 1][0] - AAA;
14968             fromY = moveList[target - 1][1] - ONE;
14969             if (target == currentMove + 1) {
14970                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14971             }
14972             if (appData.highlightLastMove) {
14973                 SetHighlights(fromX, fromY, toX, toY);
14974             }
14975         }
14976     }
14977     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14978         gameMode == Training || gameMode == PlayFromGameFile ||
14979         gameMode == AnalyzeFile) {
14980         while (currentMove < target) {
14981             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14982             SendMoveToProgram(currentMove++, &first);
14983         }
14984     } else {
14985         currentMove = target;
14986     }
14987
14988     if (gameMode == EditGame || gameMode == EndOfGame) {
14989         whiteTimeRemaining = timeRemaining[0][currentMove];
14990         blackTimeRemaining = timeRemaining[1][currentMove];
14991     }
14992     DisplayBothClocks();
14993     DisplayMove(currentMove - 1);
14994     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14995     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14996     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14997         DisplayComment(currentMove - 1, commentList[currentMove]);
14998     }
14999     ClearMap(); // [HGM] exclude: invalidate map
15000 }
15001
15002
15003 void
15004 ForwardEvent ()
15005 {
15006     if (gameMode == IcsExamining && !pausing) {
15007         SendToICS(ics_prefix);
15008         SendToICS("forward\n");
15009     } else {
15010         ForwardInner(currentMove + 1);
15011     }
15012 }
15013
15014 void
15015 ToEndEvent ()
15016 {
15017     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15018         /* to optimze, we temporarily turn off analysis mode while we feed
15019          * the remaining moves to the engine. Otherwise we get analysis output
15020          * after each move.
15021          */
15022         if (first.analysisSupport) {
15023           SendToProgram("exit\nforce\n", &first);
15024           first.analyzing = FALSE;
15025         }
15026     }
15027
15028     if (gameMode == IcsExamining && !pausing) {
15029         SendToICS(ics_prefix);
15030         SendToICS("forward 999999\n");
15031     } else {
15032         ForwardInner(forwardMostMove);
15033     }
15034
15035     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15036         /* we have fed all the moves, so reactivate analysis mode */
15037         SendToProgram("analyze\n", &first);
15038         first.analyzing = TRUE;
15039         /*first.maybeThinking = TRUE;*/
15040         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15041     }
15042 }
15043
15044 void
15045 BackwardInner (int target)
15046 {
15047     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15048
15049     if (appData.debugMode)
15050         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15051                 target, currentMove, forwardMostMove);
15052
15053     if (gameMode == EditPosition) return;
15054     seekGraphUp = FALSE;
15055     MarkTargetSquares(1);
15056     if (currentMove <= backwardMostMove) {
15057         ClearHighlights();
15058         DrawPosition(full_redraw, boards[currentMove]);
15059         return;
15060     }
15061     if (gameMode == PlayFromGameFile && !pausing)
15062       PauseEvent();
15063
15064     if (moveList[target][0]) {
15065         int fromX, fromY, toX, toY;
15066         toX = moveList[target][2] - AAA;
15067         toY = moveList[target][3] - ONE;
15068         if (moveList[target][1] == '@') {
15069             if (appData.highlightLastMove) {
15070                 SetHighlights(-1, -1, toX, toY);
15071             }
15072         } else {
15073             fromX = moveList[target][0] - AAA;
15074             fromY = moveList[target][1] - ONE;
15075             if (target == currentMove - 1) {
15076                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15077             }
15078             if (appData.highlightLastMove) {
15079                 SetHighlights(fromX, fromY, toX, toY);
15080             }
15081         }
15082     }
15083     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15084         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15085         while (currentMove > target) {
15086             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15087                 // null move cannot be undone. Reload program with move history before it.
15088                 int i;
15089                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15090                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15091                 }
15092                 SendBoard(&first, i);
15093               if(second.analyzing) SendBoard(&second, i);
15094                 for(currentMove=i; currentMove<target; currentMove++) {
15095                     SendMoveToProgram(currentMove, &first);
15096                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15097                 }
15098                 break;
15099             }
15100             SendToBoth("undo\n");
15101             currentMove--;
15102         }
15103     } else {
15104         currentMove = target;
15105     }
15106
15107     if (gameMode == EditGame || gameMode == EndOfGame) {
15108         whiteTimeRemaining = timeRemaining[0][currentMove];
15109         blackTimeRemaining = timeRemaining[1][currentMove];
15110     }
15111     DisplayBothClocks();
15112     DisplayMove(currentMove - 1);
15113     DrawPosition(full_redraw, boards[currentMove]);
15114     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15115     // [HGM] PV info: routine tests if comment empty
15116     DisplayComment(currentMove - 1, commentList[currentMove]);
15117     ClearMap(); // [HGM] exclude: invalidate map
15118 }
15119
15120 void
15121 BackwardEvent ()
15122 {
15123     if (gameMode == IcsExamining && !pausing) {
15124         SendToICS(ics_prefix);
15125         SendToICS("backward\n");
15126     } else {
15127         BackwardInner(currentMove - 1);
15128     }
15129 }
15130
15131 void
15132 ToStartEvent ()
15133 {
15134     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15135         /* to optimize, we temporarily turn off analysis mode while we undo
15136          * all the moves. Otherwise we get analysis output after each undo.
15137          */
15138         if (first.analysisSupport) {
15139           SendToProgram("exit\nforce\n", &first);
15140           first.analyzing = FALSE;
15141         }
15142     }
15143
15144     if (gameMode == IcsExamining && !pausing) {
15145         SendToICS(ics_prefix);
15146         SendToICS("backward 999999\n");
15147     } else {
15148         BackwardInner(backwardMostMove);
15149     }
15150
15151     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15152         /* we have fed all the moves, so reactivate analysis mode */
15153         SendToProgram("analyze\n", &first);
15154         first.analyzing = TRUE;
15155         /*first.maybeThinking = TRUE;*/
15156         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15157     }
15158 }
15159
15160 void
15161 ToNrEvent (int to)
15162 {
15163   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15164   if (to >= forwardMostMove) to = forwardMostMove;
15165   if (to <= backwardMostMove) to = backwardMostMove;
15166   if (to < currentMove) {
15167     BackwardInner(to);
15168   } else {
15169     ForwardInner(to);
15170   }
15171 }
15172
15173 void
15174 RevertEvent (Boolean annotate)
15175 {
15176     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15177         return;
15178     }
15179     if (gameMode != IcsExamining) {
15180         DisplayError(_("You are not examining a game"), 0);
15181         return;
15182     }
15183     if (pausing) {
15184         DisplayError(_("You can't revert while pausing"), 0);
15185         return;
15186     }
15187     SendToICS(ics_prefix);
15188     SendToICS("revert\n");
15189 }
15190
15191 void
15192 RetractMoveEvent ()
15193 {
15194     switch (gameMode) {
15195       case MachinePlaysWhite:
15196       case MachinePlaysBlack:
15197         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15198             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15199             return;
15200         }
15201         if (forwardMostMove < 2) return;
15202         currentMove = forwardMostMove = forwardMostMove - 2;
15203         whiteTimeRemaining = timeRemaining[0][currentMove];
15204         blackTimeRemaining = timeRemaining[1][currentMove];
15205         DisplayBothClocks();
15206         DisplayMove(currentMove - 1);
15207         ClearHighlights();/*!! could figure this out*/
15208         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15209         SendToProgram("remove\n", &first);
15210         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15211         break;
15212
15213       case BeginningOfGame:
15214       default:
15215         break;
15216
15217       case IcsPlayingWhite:
15218       case IcsPlayingBlack:
15219         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15220             SendToICS(ics_prefix);
15221             SendToICS("takeback 2\n");
15222         } else {
15223             SendToICS(ics_prefix);
15224             SendToICS("takeback 1\n");
15225         }
15226         break;
15227     }
15228 }
15229
15230 void
15231 MoveNowEvent ()
15232 {
15233     ChessProgramState *cps;
15234
15235     switch (gameMode) {
15236       case MachinePlaysWhite:
15237         if (!WhiteOnMove(forwardMostMove)) {
15238             DisplayError(_("It is your turn"), 0);
15239             return;
15240         }
15241         cps = &first;
15242         break;
15243       case MachinePlaysBlack:
15244         if (WhiteOnMove(forwardMostMove)) {
15245             DisplayError(_("It is your turn"), 0);
15246             return;
15247         }
15248         cps = &first;
15249         break;
15250       case TwoMachinesPlay:
15251         if (WhiteOnMove(forwardMostMove) ==
15252             (first.twoMachinesColor[0] == 'w')) {
15253             cps = &first;
15254         } else {
15255             cps = &second;
15256         }
15257         break;
15258       case BeginningOfGame:
15259       default:
15260         return;
15261     }
15262     SendToProgram("?\n", cps);
15263 }
15264
15265 void
15266 TruncateGameEvent ()
15267 {
15268     EditGameEvent();
15269     if (gameMode != EditGame) return;
15270     TruncateGame();
15271 }
15272
15273 void
15274 TruncateGame ()
15275 {
15276     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15277     if (forwardMostMove > currentMove) {
15278         if (gameInfo.resultDetails != NULL) {
15279             free(gameInfo.resultDetails);
15280             gameInfo.resultDetails = NULL;
15281             gameInfo.result = GameUnfinished;
15282         }
15283         forwardMostMove = currentMove;
15284         HistorySet(parseList, backwardMostMove, forwardMostMove,
15285                    currentMove-1);
15286     }
15287 }
15288
15289 void
15290 HintEvent ()
15291 {
15292     if (appData.noChessProgram) return;
15293     switch (gameMode) {
15294       case MachinePlaysWhite:
15295         if (WhiteOnMove(forwardMostMove)) {
15296             DisplayError(_("Wait until your turn"), 0);
15297             return;
15298         }
15299         break;
15300       case BeginningOfGame:
15301       case MachinePlaysBlack:
15302         if (!WhiteOnMove(forwardMostMove)) {
15303             DisplayError(_("Wait until your turn"), 0);
15304             return;
15305         }
15306         break;
15307       default:
15308         DisplayError(_("No hint available"), 0);
15309         return;
15310     }
15311     SendToProgram("hint\n", &first);
15312     hintRequested = TRUE;
15313 }
15314
15315 void
15316 CreateBookEvent ()
15317 {
15318     ListGame * lg = (ListGame *) gameList.head;
15319     FILE *f, *g;
15320     int nItem;
15321     static int secondTime = FALSE;
15322
15323     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15324         DisplayError(_("Game list not loaded or empty"), 0);
15325         return;
15326     }
15327
15328     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15329         fclose(g);
15330         secondTime++;
15331         DisplayNote(_("Book file exists! Try again for overwrite."));
15332         return;
15333     }
15334
15335     creatingBook = TRUE;
15336     secondTime = FALSE;
15337
15338     /* Get list size */
15339     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15340         LoadGame(f, nItem, "", TRUE);
15341         AddGameToBook(TRUE);
15342         lg = (ListGame *) lg->node.succ;
15343     }
15344
15345     creatingBook = FALSE;
15346     FlushBook();
15347 }
15348
15349 void
15350 BookEvent ()
15351 {
15352     if (appData.noChessProgram) return;
15353     switch (gameMode) {
15354       case MachinePlaysWhite:
15355         if (WhiteOnMove(forwardMostMove)) {
15356             DisplayError(_("Wait until your turn"), 0);
15357             return;
15358         }
15359         break;
15360       case BeginningOfGame:
15361       case MachinePlaysBlack:
15362         if (!WhiteOnMove(forwardMostMove)) {
15363             DisplayError(_("Wait until your turn"), 0);
15364             return;
15365         }
15366         break;
15367       case EditPosition:
15368         EditPositionDone(TRUE);
15369         break;
15370       case TwoMachinesPlay:
15371         return;
15372       default:
15373         break;
15374     }
15375     SendToProgram("bk\n", &first);
15376     bookOutput[0] = NULLCHAR;
15377     bookRequested = TRUE;
15378 }
15379
15380 void
15381 AboutGameEvent ()
15382 {
15383     char *tags = PGNTags(&gameInfo);
15384     TagsPopUp(tags, CmailMsg());
15385     free(tags);
15386 }
15387
15388 /* end button procedures */
15389
15390 void
15391 PrintPosition (FILE *fp, int move)
15392 {
15393     int i, j;
15394
15395     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15396         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15397             char c = PieceToChar(boards[move][i][j]);
15398             fputc(c == 'x' ? '.' : c, fp);
15399             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15400         }
15401     }
15402     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15403       fprintf(fp, "white to play\n");
15404     else
15405       fprintf(fp, "black to play\n");
15406 }
15407
15408 void
15409 PrintOpponents (FILE *fp)
15410 {
15411     if (gameInfo.white != NULL) {
15412         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15413     } else {
15414         fprintf(fp, "\n");
15415     }
15416 }
15417
15418 /* Find last component of program's own name, using some heuristics */
15419 void
15420 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15421 {
15422     char *p, *q, c;
15423     int local = (strcmp(host, "localhost") == 0);
15424     while (!local && (p = strchr(prog, ';')) != NULL) {
15425         p++;
15426         while (*p == ' ') p++;
15427         prog = p;
15428     }
15429     if (*prog == '"' || *prog == '\'') {
15430         q = strchr(prog + 1, *prog);
15431     } else {
15432         q = strchr(prog, ' ');
15433     }
15434     if (q == NULL) q = prog + strlen(prog);
15435     p = q;
15436     while (p >= prog && *p != '/' && *p != '\\') p--;
15437     p++;
15438     if(p == prog && *p == '"') p++;
15439     c = *q; *q = 0;
15440     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15441     memcpy(buf, p, q - p);
15442     buf[q - p] = NULLCHAR;
15443     if (!local) {
15444         strcat(buf, "@");
15445         strcat(buf, host);
15446     }
15447 }
15448
15449 char *
15450 TimeControlTagValue ()
15451 {
15452     char buf[MSG_SIZ];
15453     if (!appData.clockMode) {
15454       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15455     } else if (movesPerSession > 0) {
15456       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15457     } else if (timeIncrement == 0) {
15458       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15459     } else {
15460       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15461     }
15462     return StrSave(buf);
15463 }
15464
15465 void
15466 SetGameInfo ()
15467 {
15468     /* This routine is used only for certain modes */
15469     VariantClass v = gameInfo.variant;
15470     ChessMove r = GameUnfinished;
15471     char *p = NULL;
15472
15473     if(keepInfo) return;
15474
15475     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15476         r = gameInfo.result;
15477         p = gameInfo.resultDetails;
15478         gameInfo.resultDetails = NULL;
15479     }
15480     ClearGameInfo(&gameInfo);
15481     gameInfo.variant = v;
15482
15483     switch (gameMode) {
15484       case MachinePlaysWhite:
15485         gameInfo.event = StrSave( appData.pgnEventHeader );
15486         gameInfo.site = StrSave(HostName());
15487         gameInfo.date = PGNDate();
15488         gameInfo.round = StrSave("-");
15489         gameInfo.white = StrSave(first.tidy);
15490         gameInfo.black = StrSave(UserName());
15491         gameInfo.timeControl = TimeControlTagValue();
15492         break;
15493
15494       case MachinePlaysBlack:
15495         gameInfo.event = StrSave( appData.pgnEventHeader );
15496         gameInfo.site = StrSave(HostName());
15497         gameInfo.date = PGNDate();
15498         gameInfo.round = StrSave("-");
15499         gameInfo.white = StrSave(UserName());
15500         gameInfo.black = StrSave(first.tidy);
15501         gameInfo.timeControl = TimeControlTagValue();
15502         break;
15503
15504       case TwoMachinesPlay:
15505         gameInfo.event = StrSave( appData.pgnEventHeader );
15506         gameInfo.site = StrSave(HostName());
15507         gameInfo.date = PGNDate();
15508         if (roundNr > 0) {
15509             char buf[MSG_SIZ];
15510             snprintf(buf, MSG_SIZ, "%d", roundNr);
15511             gameInfo.round = StrSave(buf);
15512         } else {
15513             gameInfo.round = StrSave("-");
15514         }
15515         if (first.twoMachinesColor[0] == 'w') {
15516             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15517             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15518         } else {
15519             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15520             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15521         }
15522         gameInfo.timeControl = TimeControlTagValue();
15523         break;
15524
15525       case EditGame:
15526         gameInfo.event = StrSave("Edited game");
15527         gameInfo.site = StrSave(HostName());
15528         gameInfo.date = PGNDate();
15529         gameInfo.round = StrSave("-");
15530         gameInfo.white = StrSave("-");
15531         gameInfo.black = StrSave("-");
15532         gameInfo.result = r;
15533         gameInfo.resultDetails = p;
15534         break;
15535
15536       case EditPosition:
15537         gameInfo.event = StrSave("Edited position");
15538         gameInfo.site = StrSave(HostName());
15539         gameInfo.date = PGNDate();
15540         gameInfo.round = StrSave("-");
15541         gameInfo.white = StrSave("-");
15542         gameInfo.black = StrSave("-");
15543         break;
15544
15545       case IcsPlayingWhite:
15546       case IcsPlayingBlack:
15547       case IcsObserving:
15548       case IcsExamining:
15549         break;
15550
15551       case PlayFromGameFile:
15552         gameInfo.event = StrSave("Game from non-PGN file");
15553         gameInfo.site = StrSave(HostName());
15554         gameInfo.date = PGNDate();
15555         gameInfo.round = StrSave("-");
15556         gameInfo.white = StrSave("?");
15557         gameInfo.black = StrSave("?");
15558         break;
15559
15560       default:
15561         break;
15562     }
15563 }
15564
15565 void
15566 ReplaceComment (int index, char *text)
15567 {
15568     int len;
15569     char *p;
15570     float score;
15571
15572     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15573        pvInfoList[index-1].depth == len &&
15574        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15575        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15576     while (*text == '\n') text++;
15577     len = strlen(text);
15578     while (len > 0 && text[len - 1] == '\n') len--;
15579
15580     if (commentList[index] != NULL)
15581       free(commentList[index]);
15582
15583     if (len == 0) {
15584         commentList[index] = NULL;
15585         return;
15586     }
15587   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15588       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15589       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15590     commentList[index] = (char *) malloc(len + 2);
15591     strncpy(commentList[index], text, len);
15592     commentList[index][len] = '\n';
15593     commentList[index][len + 1] = NULLCHAR;
15594   } else {
15595     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15596     char *p;
15597     commentList[index] = (char *) malloc(len + 7);
15598     safeStrCpy(commentList[index], "{\n", 3);
15599     safeStrCpy(commentList[index]+2, text, len+1);
15600     commentList[index][len+2] = NULLCHAR;
15601     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15602     strcat(commentList[index], "\n}\n");
15603   }
15604 }
15605
15606 void
15607 CrushCRs (char *text)
15608 {
15609   char *p = text;
15610   char *q = text;
15611   char ch;
15612
15613   do {
15614     ch = *p++;
15615     if (ch == '\r') continue;
15616     *q++ = ch;
15617   } while (ch != '\0');
15618 }
15619
15620 void
15621 AppendComment (int index, char *text, Boolean addBraces)
15622 /* addBraces  tells if we should add {} */
15623 {
15624     int oldlen, len;
15625     char *old;
15626
15627 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15628     if(addBraces == 3) addBraces = 0; else // force appending literally
15629     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15630
15631     CrushCRs(text);
15632     while (*text == '\n') text++;
15633     len = strlen(text);
15634     while (len > 0 && text[len - 1] == '\n') len--;
15635     text[len] = NULLCHAR;
15636
15637     if (len == 0) return;
15638
15639     if (commentList[index] != NULL) {
15640       Boolean addClosingBrace = addBraces;
15641         old = commentList[index];
15642         oldlen = strlen(old);
15643         while(commentList[index][oldlen-1] ==  '\n')
15644           commentList[index][--oldlen] = NULLCHAR;
15645         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15646         safeStrCpy(commentList[index], old, oldlen + len + 6);
15647         free(old);
15648         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15649         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15650           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15651           while (*text == '\n') { text++; len--; }
15652           commentList[index][--oldlen] = NULLCHAR;
15653       }
15654         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15655         else          strcat(commentList[index], "\n");
15656         strcat(commentList[index], text);
15657         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15658         else          strcat(commentList[index], "\n");
15659     } else {
15660         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15661         if(addBraces)
15662           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15663         else commentList[index][0] = NULLCHAR;
15664         strcat(commentList[index], text);
15665         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15666         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15667     }
15668 }
15669
15670 static char *
15671 FindStr (char * text, char * sub_text)
15672 {
15673     char * result = strstr( text, sub_text );
15674
15675     if( result != NULL ) {
15676         result += strlen( sub_text );
15677     }
15678
15679     return result;
15680 }
15681
15682 /* [AS] Try to extract PV info from PGN comment */
15683 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15684 char *
15685 GetInfoFromComment (int index, char * text)
15686 {
15687     char * sep = text, *p;
15688
15689     if( text != NULL && index > 0 ) {
15690         int score = 0;
15691         int depth = 0;
15692         int time = -1, sec = 0, deci;
15693         char * s_eval = FindStr( text, "[%eval " );
15694         char * s_emt = FindStr( text, "[%emt " );
15695 #if 0
15696         if( s_eval != NULL || s_emt != NULL ) {
15697 #else
15698         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15699 #endif
15700             /* New style */
15701             char delim;
15702
15703             if( s_eval != NULL ) {
15704                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15705                     return text;
15706                 }
15707
15708                 if( delim != ']' ) {
15709                     return text;
15710                 }
15711             }
15712
15713             if( s_emt != NULL ) {
15714             }
15715                 return text;
15716         }
15717         else {
15718             /* We expect something like: [+|-]nnn.nn/dd */
15719             int score_lo = 0;
15720
15721             if(*text != '{') return text; // [HGM] braces: must be normal comment
15722
15723             sep = strchr( text, '/' );
15724             if( sep == NULL || sep < (text+4) ) {
15725                 return text;
15726             }
15727
15728             p = text;
15729             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15730             if(p[1] == '(') { // comment starts with PV
15731                p = strchr(p, ')'); // locate end of PV
15732                if(p == NULL || sep < p+5) return text;
15733                // at this point we have something like "{(.*) +0.23/6 ..."
15734                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15735                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15736                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15737             }
15738             time = -1; sec = -1; deci = -1;
15739             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15740                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15741                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15742                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15743                 return text;
15744             }
15745
15746             if( score_lo < 0 || score_lo >= 100 ) {
15747                 return text;
15748             }
15749
15750             if(sec >= 0) time = 600*time + 10*sec; else
15751             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15752
15753             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15754
15755             /* [HGM] PV time: now locate end of PV info */
15756             while( *++sep >= '0' && *sep <= '9'); // strip depth
15757             if(time >= 0)
15758             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15759             if(sec >= 0)
15760             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15761             if(deci >= 0)
15762             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15763             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15764         }
15765
15766         if( depth <= 0 ) {
15767             return text;
15768         }
15769
15770         if( time < 0 ) {
15771             time = -1;
15772         }
15773
15774         pvInfoList[index-1].depth = depth;
15775         pvInfoList[index-1].score = score;
15776         pvInfoList[index-1].time  = 10*time; // centi-sec
15777         if(*sep == '}') *sep = 0; else *--sep = '{';
15778         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15779     }
15780     return sep;
15781 }
15782
15783 void
15784 SendToProgram (char *message, ChessProgramState *cps)
15785 {
15786     int count, outCount, error;
15787     char buf[MSG_SIZ];
15788
15789     if (cps->pr == NoProc) return;
15790     Attention(cps);
15791
15792     if (appData.debugMode) {
15793         TimeMark now;
15794         GetTimeMark(&now);
15795         fprintf(debugFP, "%ld >%-6s: %s",
15796                 SubtractTimeMarks(&now, &programStartTime),
15797                 cps->which, message);
15798         if(serverFP)
15799             fprintf(serverFP, "%ld >%-6s: %s",
15800                 SubtractTimeMarks(&now, &programStartTime),
15801                 cps->which, message), fflush(serverFP);
15802     }
15803
15804     count = strlen(message);
15805     outCount = OutputToProcess(cps->pr, message, count, &error);
15806     if (outCount < count && !exiting
15807                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15808       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15809       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15810         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15811             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15812                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15813                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15814                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15815             } else {
15816                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15817                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15818                 gameInfo.result = res;
15819             }
15820             gameInfo.resultDetails = StrSave(buf);
15821         }
15822         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15823         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15824     }
15825 }
15826
15827 void
15828 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15829 {
15830     char *end_str;
15831     char buf[MSG_SIZ];
15832     ChessProgramState *cps = (ChessProgramState *)closure;
15833
15834     if (isr != cps->isr) return; /* Killed intentionally */
15835     if (count <= 0) {
15836         if (count == 0) {
15837             RemoveInputSource(cps->isr);
15838             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15839                     _(cps->which), cps->program);
15840             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15841             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15842                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15843                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15844                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15845                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15846                 } else {
15847                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15848                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15849                     gameInfo.result = res;
15850                 }
15851                 gameInfo.resultDetails = StrSave(buf);
15852             }
15853             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15854             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15855         } else {
15856             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15857                     _(cps->which), cps->program);
15858             RemoveInputSource(cps->isr);
15859
15860             /* [AS] Program is misbehaving badly... kill it */
15861             if( count == -2 ) {
15862                 DestroyChildProcess( cps->pr, 9 );
15863                 cps->pr = NoProc;
15864             }
15865
15866             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15867         }
15868         return;
15869     }
15870
15871     if ((end_str = strchr(message, '\r')) != NULL)
15872       *end_str = NULLCHAR;
15873     if ((end_str = strchr(message, '\n')) != NULL)
15874       *end_str = NULLCHAR;
15875
15876     if (appData.debugMode) {
15877         TimeMark now; int print = 1;
15878         char *quote = ""; char c; int i;
15879
15880         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15881                 char start = message[0];
15882                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15883                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15884                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15885                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15886                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15887                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15888                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15889                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15890                    sscanf(message, "hint: %c", &c)!=1 &&
15891                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15892                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15893                     print = (appData.engineComments >= 2);
15894                 }
15895                 message[0] = start; // restore original message
15896         }
15897         if(print) {
15898                 GetTimeMark(&now);
15899                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15900                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15901                         quote,
15902                         message);
15903                 if(serverFP)
15904                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15905                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15906                         quote,
15907                         message), fflush(serverFP);
15908         }
15909     }
15910
15911     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15912     if (appData.icsEngineAnalyze) {
15913         if (strstr(message, "whisper") != NULL ||
15914              strstr(message, "kibitz") != NULL ||
15915             strstr(message, "tellics") != NULL) return;
15916     }
15917
15918     HandleMachineMove(message, cps);
15919 }
15920
15921
15922 void
15923 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15924 {
15925     char buf[MSG_SIZ];
15926     int seconds;
15927
15928     if( timeControl_2 > 0 ) {
15929         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15930             tc = timeControl_2;
15931         }
15932     }
15933     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15934     inc /= cps->timeOdds;
15935     st  /= cps->timeOdds;
15936
15937     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15938
15939     if (st > 0) {
15940       /* Set exact time per move, normally using st command */
15941       if (cps->stKludge) {
15942         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15943         seconds = st % 60;
15944         if (seconds == 0) {
15945           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15946         } else {
15947           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15948         }
15949       } else {
15950         snprintf(buf, MSG_SIZ, "st %d\n", st);
15951       }
15952     } else {
15953       /* Set conventional or incremental time control, using level command */
15954       if (seconds == 0) {
15955         /* Note old gnuchess bug -- minutes:seconds used to not work.
15956            Fixed in later versions, but still avoid :seconds
15957            when seconds is 0. */
15958         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15959       } else {
15960         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15961                  seconds, inc/1000.);
15962       }
15963     }
15964     SendToProgram(buf, cps);
15965
15966     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15967     /* Orthogonally, limit search to given depth */
15968     if (sd > 0) {
15969       if (cps->sdKludge) {
15970         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15971       } else {
15972         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15973       }
15974       SendToProgram(buf, cps);
15975     }
15976
15977     if(cps->nps >= 0) { /* [HGM] nps */
15978         if(cps->supportsNPS == FALSE)
15979           cps->nps = -1; // don't use if engine explicitly says not supported!
15980         else {
15981           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15982           SendToProgram(buf, cps);
15983         }
15984     }
15985 }
15986
15987 ChessProgramState *
15988 WhitePlayer ()
15989 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15990 {
15991     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15992        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15993         return &second;
15994     return &first;
15995 }
15996
15997 void
15998 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15999 {
16000     char message[MSG_SIZ];
16001     long time, otime;
16002
16003     /* Note: this routine must be called when the clocks are stopped
16004        or when they have *just* been set or switched; otherwise
16005        it will be off by the time since the current tick started.
16006     */
16007     if (machineWhite) {
16008         time = whiteTimeRemaining / 10;
16009         otime = blackTimeRemaining / 10;
16010     } else {
16011         time = blackTimeRemaining / 10;
16012         otime = whiteTimeRemaining / 10;
16013     }
16014     /* [HGM] translate opponent's time by time-odds factor */
16015     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16016
16017     if (time <= 0) time = 1;
16018     if (otime <= 0) otime = 1;
16019
16020     snprintf(message, MSG_SIZ, "time %ld\n", time);
16021     SendToProgram(message, cps);
16022
16023     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16024     SendToProgram(message, cps);
16025 }
16026
16027 int
16028 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16029 {
16030   char buf[MSG_SIZ];
16031   int len = strlen(name);
16032   int val;
16033
16034   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16035     (*p) += len + 1;
16036     sscanf(*p, "%d", &val);
16037     *loc = (val != 0);
16038     while (**p && **p != ' ')
16039       (*p)++;
16040     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16041     SendToProgram(buf, cps);
16042     return TRUE;
16043   }
16044   return FALSE;
16045 }
16046
16047 int
16048 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16049 {
16050   char buf[MSG_SIZ];
16051   int len = strlen(name);
16052   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16053     (*p) += len + 1;
16054     sscanf(*p, "%d", loc);
16055     while (**p && **p != ' ') (*p)++;
16056     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16057     SendToProgram(buf, cps);
16058     return TRUE;
16059   }
16060   return FALSE;
16061 }
16062
16063 int
16064 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16065 {
16066   char buf[MSG_SIZ];
16067   int len = strlen(name);
16068   if (strncmp((*p), name, len) == 0
16069       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16070     (*p) += len + 2;
16071     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16072     sscanf(*p, "%[^\"]", *loc);
16073     while (**p && **p != '\"') (*p)++;
16074     if (**p == '\"') (*p)++;
16075     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16076     SendToProgram(buf, cps);
16077     return TRUE;
16078   }
16079   return FALSE;
16080 }
16081
16082 int
16083 ParseOption (Option *opt, ChessProgramState *cps)
16084 // [HGM] options: process the string that defines an engine option, and determine
16085 // name, type, default value, and allowed value range
16086 {
16087         char *p, *q, buf[MSG_SIZ];
16088         int n, min = (-1)<<31, max = 1<<31, def;
16089
16090         if(p = strstr(opt->name, " -spin ")) {
16091             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16092             if(max < min) max = min; // enforce consistency
16093             if(def < min) def = min;
16094             if(def > max) def = max;
16095             opt->value = def;
16096             opt->min = min;
16097             opt->max = max;
16098             opt->type = Spin;
16099         } else if((p = strstr(opt->name, " -slider "))) {
16100             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16101             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16102             if(max < min) max = min; // enforce consistency
16103             if(def < min) def = min;
16104             if(def > max) def = max;
16105             opt->value = def;
16106             opt->min = min;
16107             opt->max = max;
16108             opt->type = Spin; // Slider;
16109         } else if((p = strstr(opt->name, " -string "))) {
16110             opt->textValue = p+9;
16111             opt->type = TextBox;
16112         } else if((p = strstr(opt->name, " -file "))) {
16113             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16114             opt->textValue = p+7;
16115             opt->type = FileName; // FileName;
16116         } else if((p = strstr(opt->name, " -path "))) {
16117             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16118             opt->textValue = p+7;
16119             opt->type = PathName; // PathName;
16120         } else if(p = strstr(opt->name, " -check ")) {
16121             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16122             opt->value = (def != 0);
16123             opt->type = CheckBox;
16124         } else if(p = strstr(opt->name, " -combo ")) {
16125             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16126             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16127             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16128             opt->value = n = 0;
16129             while(q = StrStr(q, " /// ")) {
16130                 n++; *q = 0;    // count choices, and null-terminate each of them
16131                 q += 5;
16132                 if(*q == '*') { // remember default, which is marked with * prefix
16133                     q++;
16134                     opt->value = n;
16135                 }
16136                 cps->comboList[cps->comboCnt++] = q;
16137             }
16138             cps->comboList[cps->comboCnt++] = NULL;
16139             opt->max = n + 1;
16140             opt->type = ComboBox;
16141         } else if(p = strstr(opt->name, " -button")) {
16142             opt->type = Button;
16143         } else if(p = strstr(opt->name, " -save")) {
16144             opt->type = SaveButton;
16145         } else return FALSE;
16146         *p = 0; // terminate option name
16147         // now look if the command-line options define a setting for this engine option.
16148         if(cps->optionSettings && cps->optionSettings[0])
16149             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16150         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16151           snprintf(buf, MSG_SIZ, "option %s", p);
16152                 if(p = strstr(buf, ",")) *p = 0;
16153                 if(q = strchr(buf, '=')) switch(opt->type) {
16154                     case ComboBox:
16155                         for(n=0; n<opt->max; n++)
16156                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16157                         break;
16158                     case TextBox:
16159                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16160                         break;
16161                     case Spin:
16162                     case CheckBox:
16163                         opt->value = atoi(q+1);
16164                     default:
16165                         break;
16166                 }
16167                 strcat(buf, "\n");
16168                 SendToProgram(buf, cps);
16169         }
16170         return TRUE;
16171 }
16172
16173 void
16174 FeatureDone (ChessProgramState *cps, int val)
16175 {
16176   DelayedEventCallback cb = GetDelayedEvent();
16177   if ((cb == InitBackEnd3 && cps == &first) ||
16178       (cb == SettingsMenuIfReady && cps == &second) ||
16179       (cb == LoadEngine) ||
16180       (cb == TwoMachinesEventIfReady)) {
16181     CancelDelayedEvent();
16182     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16183   }
16184   cps->initDone = val;
16185   if(val) cps->reload = FALSE;
16186 }
16187
16188 /* Parse feature command from engine */
16189 void
16190 ParseFeatures (char *args, ChessProgramState *cps)
16191 {
16192   char *p = args;
16193   char *q = NULL;
16194   int val;
16195   char buf[MSG_SIZ];
16196
16197   for (;;) {
16198     while (*p == ' ') p++;
16199     if (*p == NULLCHAR) return;
16200
16201     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16202     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16203     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16204     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16205     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16206     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16207     if (BoolFeature(&p, "reuse", &val, cps)) {
16208       /* Engine can disable reuse, but can't enable it if user said no */
16209       if (!val) cps->reuse = FALSE;
16210       continue;
16211     }
16212     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16213     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16214       if (gameMode == TwoMachinesPlay) {
16215         DisplayTwoMachinesTitle();
16216       } else {
16217         DisplayTitle("");
16218       }
16219       continue;
16220     }
16221     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16222     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16223     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16224     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16225     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16226     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16227     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16228     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16229     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16230     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16231     if (IntFeature(&p, "done", &val, cps)) {
16232       FeatureDone(cps, val);
16233       continue;
16234     }
16235     /* Added by Tord: */
16236     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16237     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16238     /* End of additions by Tord */
16239
16240     /* [HGM] added features: */
16241     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16242     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16243     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16244     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16245     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16246     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16247     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16248     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16249         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16250         FREE(cps->option[cps->nrOptions].name);
16251         cps->option[cps->nrOptions].name = q; q = NULL;
16252         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16253           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16254             SendToProgram(buf, cps);
16255             continue;
16256         }
16257         if(cps->nrOptions >= MAX_OPTIONS) {
16258             cps->nrOptions--;
16259             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16260             DisplayError(buf, 0);
16261         }
16262         continue;
16263     }
16264     /* End of additions by HGM */
16265
16266     /* unknown feature: complain and skip */
16267     q = p;
16268     while (*q && *q != '=') q++;
16269     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16270     SendToProgram(buf, cps);
16271     p = q;
16272     if (*p == '=') {
16273       p++;
16274       if (*p == '\"') {
16275         p++;
16276         while (*p && *p != '\"') p++;
16277         if (*p == '\"') p++;
16278       } else {
16279         while (*p && *p != ' ') p++;
16280       }
16281     }
16282   }
16283
16284 }
16285
16286 void
16287 PeriodicUpdatesEvent (int newState)
16288 {
16289     if (newState == appData.periodicUpdates)
16290       return;
16291
16292     appData.periodicUpdates=newState;
16293
16294     /* Display type changes, so update it now */
16295 //    DisplayAnalysis();
16296
16297     /* Get the ball rolling again... */
16298     if (newState) {
16299         AnalysisPeriodicEvent(1);
16300         StartAnalysisClock();
16301     }
16302 }
16303
16304 void
16305 PonderNextMoveEvent (int newState)
16306 {
16307     if (newState == appData.ponderNextMove) return;
16308     if (gameMode == EditPosition) EditPositionDone(TRUE);
16309     if (newState) {
16310         SendToProgram("hard\n", &first);
16311         if (gameMode == TwoMachinesPlay) {
16312             SendToProgram("hard\n", &second);
16313         }
16314     } else {
16315         SendToProgram("easy\n", &first);
16316         thinkOutput[0] = NULLCHAR;
16317         if (gameMode == TwoMachinesPlay) {
16318             SendToProgram("easy\n", &second);
16319         }
16320     }
16321     appData.ponderNextMove = newState;
16322 }
16323
16324 void
16325 NewSettingEvent (int option, int *feature, char *command, int value)
16326 {
16327     char buf[MSG_SIZ];
16328
16329     if (gameMode == EditPosition) EditPositionDone(TRUE);
16330     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16331     if(feature == NULL || *feature) SendToProgram(buf, &first);
16332     if (gameMode == TwoMachinesPlay) {
16333         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16334     }
16335 }
16336
16337 void
16338 ShowThinkingEvent ()
16339 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16340 {
16341     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16342     int newState = appData.showThinking
16343         // [HGM] thinking: other features now need thinking output as well
16344         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16345
16346     if (oldState == newState) return;
16347     oldState = newState;
16348     if (gameMode == EditPosition) EditPositionDone(TRUE);
16349     if (oldState) {
16350         SendToProgram("post\n", &first);
16351         if (gameMode == TwoMachinesPlay) {
16352             SendToProgram("post\n", &second);
16353         }
16354     } else {
16355         SendToProgram("nopost\n", &first);
16356         thinkOutput[0] = NULLCHAR;
16357         if (gameMode == TwoMachinesPlay) {
16358             SendToProgram("nopost\n", &second);
16359         }
16360     }
16361 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16362 }
16363
16364 void
16365 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16366 {
16367   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16368   if (pr == NoProc) return;
16369   AskQuestion(title, question, replyPrefix, pr);
16370 }
16371
16372 void
16373 TypeInEvent (char firstChar)
16374 {
16375     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16376         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16377         gameMode == AnalyzeMode || gameMode == EditGame ||
16378         gameMode == EditPosition || gameMode == IcsExamining ||
16379         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16380         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16381                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16382                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16383         gameMode == Training) PopUpMoveDialog(firstChar);
16384 }
16385
16386 void
16387 TypeInDoneEvent (char *move)
16388 {
16389         Board board;
16390         int n, fromX, fromY, toX, toY;
16391         char promoChar;
16392         ChessMove moveType;
16393
16394         // [HGM] FENedit
16395         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16396                 EditPositionPasteFEN(move);
16397                 return;
16398         }
16399         // [HGM] movenum: allow move number to be typed in any mode
16400         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16401           ToNrEvent(2*n-1);
16402           return;
16403         }
16404         // undocumented kludge: allow command-line option to be typed in!
16405         // (potentially fatal, and does not implement the effect of the option.)
16406         // should only be used for options that are values on which future decisions will be made,
16407         // and definitely not on options that would be used during initialization.
16408         if(strstr(move, "!!! -") == move) {
16409             ParseArgsFromString(move+4);
16410             return;
16411         }
16412
16413       if (gameMode != EditGame && currentMove != forwardMostMove &&
16414         gameMode != Training) {
16415         DisplayMoveError(_("Displayed move is not current"));
16416       } else {
16417         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16418           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16419         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16420         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16421           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16422           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16423         } else {
16424           DisplayMoveError(_("Could not parse move"));
16425         }
16426       }
16427 }
16428
16429 void
16430 DisplayMove (int moveNumber)
16431 {
16432     char message[MSG_SIZ];
16433     char res[MSG_SIZ];
16434     char cpThinkOutput[MSG_SIZ];
16435
16436     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16437
16438     if (moveNumber == forwardMostMove - 1 ||
16439         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16440
16441         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16442
16443         if (strchr(cpThinkOutput, '\n')) {
16444             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16445         }
16446     } else {
16447         *cpThinkOutput = NULLCHAR;
16448     }
16449
16450     /* [AS] Hide thinking from human user */
16451     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16452         *cpThinkOutput = NULLCHAR;
16453         if( thinkOutput[0] != NULLCHAR ) {
16454             int i;
16455
16456             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16457                 cpThinkOutput[i] = '.';
16458             }
16459             cpThinkOutput[i] = NULLCHAR;
16460             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16461         }
16462     }
16463
16464     if (moveNumber == forwardMostMove - 1 &&
16465         gameInfo.resultDetails != NULL) {
16466         if (gameInfo.resultDetails[0] == NULLCHAR) {
16467           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16468         } else {
16469           snprintf(res, MSG_SIZ, " {%s} %s",
16470                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16471         }
16472     } else {
16473         res[0] = NULLCHAR;
16474     }
16475
16476     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16477         DisplayMessage(res, cpThinkOutput);
16478     } else {
16479       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16480                 WhiteOnMove(moveNumber) ? " " : ".. ",
16481                 parseList[moveNumber], res);
16482         DisplayMessage(message, cpThinkOutput);
16483     }
16484 }
16485
16486 void
16487 DisplayComment (int moveNumber, char *text)
16488 {
16489     char title[MSG_SIZ];
16490
16491     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16492       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16493     } else {
16494       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16495               WhiteOnMove(moveNumber) ? " " : ".. ",
16496               parseList[moveNumber]);
16497     }
16498     if (text != NULL && (appData.autoDisplayComment || commentUp))
16499         CommentPopUp(title, text);
16500 }
16501
16502 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16503  * might be busy thinking or pondering.  It can be omitted if your
16504  * gnuchess is configured to stop thinking immediately on any user
16505  * input.  However, that gnuchess feature depends on the FIONREAD
16506  * ioctl, which does not work properly on some flavors of Unix.
16507  */
16508 void
16509 Attention (ChessProgramState *cps)
16510 {
16511 #if ATTENTION
16512     if (!cps->useSigint) return;
16513     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16514     switch (gameMode) {
16515       case MachinePlaysWhite:
16516       case MachinePlaysBlack:
16517       case TwoMachinesPlay:
16518       case IcsPlayingWhite:
16519       case IcsPlayingBlack:
16520       case AnalyzeMode:
16521       case AnalyzeFile:
16522         /* Skip if we know it isn't thinking */
16523         if (!cps->maybeThinking) return;
16524         if (appData.debugMode)
16525           fprintf(debugFP, "Interrupting %s\n", cps->which);
16526         InterruptChildProcess(cps->pr);
16527         cps->maybeThinking = FALSE;
16528         break;
16529       default:
16530         break;
16531     }
16532 #endif /*ATTENTION*/
16533 }
16534
16535 int
16536 CheckFlags ()
16537 {
16538     if (whiteTimeRemaining <= 0) {
16539         if (!whiteFlag) {
16540             whiteFlag = TRUE;
16541             if (appData.icsActive) {
16542                 if (appData.autoCallFlag &&
16543                     gameMode == IcsPlayingBlack && !blackFlag) {
16544                   SendToICS(ics_prefix);
16545                   SendToICS("flag\n");
16546                 }
16547             } else {
16548                 if (blackFlag) {
16549                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16550                 } else {
16551                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16552                     if (appData.autoCallFlag) {
16553                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16554                         return TRUE;
16555                     }
16556                 }
16557             }
16558         }
16559     }
16560     if (blackTimeRemaining <= 0) {
16561         if (!blackFlag) {
16562             blackFlag = TRUE;
16563             if (appData.icsActive) {
16564                 if (appData.autoCallFlag &&
16565                     gameMode == IcsPlayingWhite && !whiteFlag) {
16566                   SendToICS(ics_prefix);
16567                   SendToICS("flag\n");
16568                 }
16569             } else {
16570                 if (whiteFlag) {
16571                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16572                 } else {
16573                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16574                     if (appData.autoCallFlag) {
16575                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16576                         return TRUE;
16577                     }
16578                 }
16579             }
16580         }
16581     }
16582     return FALSE;
16583 }
16584
16585 void
16586 CheckTimeControl ()
16587 {
16588     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16589         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16590
16591     /*
16592      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16593      */
16594     if ( !WhiteOnMove(forwardMostMove) ) {
16595         /* White made time control */
16596         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16597         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16598         /* [HGM] time odds: correct new time quota for time odds! */
16599                                             / WhitePlayer()->timeOdds;
16600         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16601     } else {
16602         lastBlack -= blackTimeRemaining;
16603         /* Black made time control */
16604         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16605                                             / WhitePlayer()->other->timeOdds;
16606         lastWhite = whiteTimeRemaining;
16607     }
16608 }
16609
16610 void
16611 DisplayBothClocks ()
16612 {
16613     int wom = gameMode == EditPosition ?
16614       !blackPlaysFirst : WhiteOnMove(currentMove);
16615     DisplayWhiteClock(whiteTimeRemaining, wom);
16616     DisplayBlackClock(blackTimeRemaining, !wom);
16617 }
16618
16619
16620 /* Timekeeping seems to be a portability nightmare.  I think everyone
16621    has ftime(), but I'm really not sure, so I'm including some ifdefs
16622    to use other calls if you don't.  Clocks will be less accurate if
16623    you have neither ftime nor gettimeofday.
16624 */
16625
16626 /* VS 2008 requires the #include outside of the function */
16627 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16628 #include <sys/timeb.h>
16629 #endif
16630
16631 /* Get the current time as a TimeMark */
16632 void
16633 GetTimeMark (TimeMark *tm)
16634 {
16635 #if HAVE_GETTIMEOFDAY
16636
16637     struct timeval timeVal;
16638     struct timezone timeZone;
16639
16640     gettimeofday(&timeVal, &timeZone);
16641     tm->sec = (long) timeVal.tv_sec;
16642     tm->ms = (int) (timeVal.tv_usec / 1000L);
16643
16644 #else /*!HAVE_GETTIMEOFDAY*/
16645 #if HAVE_FTIME
16646
16647 // include <sys/timeb.h> / moved to just above start of function
16648     struct timeb timeB;
16649
16650     ftime(&timeB);
16651     tm->sec = (long) timeB.time;
16652     tm->ms = (int) timeB.millitm;
16653
16654 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16655     tm->sec = (long) time(NULL);
16656     tm->ms = 0;
16657 #endif
16658 #endif
16659 }
16660
16661 /* Return the difference in milliseconds between two
16662    time marks.  We assume the difference will fit in a long!
16663 */
16664 long
16665 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16666 {
16667     return 1000L*(tm2->sec - tm1->sec) +
16668            (long) (tm2->ms - tm1->ms);
16669 }
16670
16671
16672 /*
16673  * Code to manage the game clocks.
16674  *
16675  * In tournament play, black starts the clock and then white makes a move.
16676  * We give the human user a slight advantage if he is playing white---the
16677  * clocks don't run until he makes his first move, so it takes zero time.
16678  * Also, we don't account for network lag, so we could get out of sync
16679  * with GNU Chess's clock -- but then, referees are always right.
16680  */
16681
16682 static TimeMark tickStartTM;
16683 static long intendedTickLength;
16684
16685 long
16686 NextTickLength (long timeRemaining)
16687 {
16688     long nominalTickLength, nextTickLength;
16689
16690     if (timeRemaining > 0L && timeRemaining <= 10000L)
16691       nominalTickLength = 100L;
16692     else
16693       nominalTickLength = 1000L;
16694     nextTickLength = timeRemaining % nominalTickLength;
16695     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16696
16697     return nextTickLength;
16698 }
16699
16700 /* Adjust clock one minute up or down */
16701 void
16702 AdjustClock (Boolean which, int dir)
16703 {
16704     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16705     if(which) blackTimeRemaining += 60000*dir;
16706     else      whiteTimeRemaining += 60000*dir;
16707     DisplayBothClocks();
16708     adjustedClock = TRUE;
16709 }
16710
16711 /* Stop clocks and reset to a fresh time control */
16712 void
16713 ResetClocks ()
16714 {
16715     (void) StopClockTimer();
16716     if (appData.icsActive) {
16717         whiteTimeRemaining = blackTimeRemaining = 0;
16718     } else if (searchTime) {
16719         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16720         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16721     } else { /* [HGM] correct new time quote for time odds */
16722         whiteTC = blackTC = fullTimeControlString;
16723         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16724         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16725     }
16726     if (whiteFlag || blackFlag) {
16727         DisplayTitle("");
16728         whiteFlag = blackFlag = FALSE;
16729     }
16730     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16731     DisplayBothClocks();
16732     adjustedClock = FALSE;
16733 }
16734
16735 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16736
16737 /* Decrement running clock by amount of time that has passed */
16738 void
16739 DecrementClocks ()
16740 {
16741     long timeRemaining;
16742     long lastTickLength, fudge;
16743     TimeMark now;
16744
16745     if (!appData.clockMode) return;
16746     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16747
16748     GetTimeMark(&now);
16749
16750     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16751
16752     /* Fudge if we woke up a little too soon */
16753     fudge = intendedTickLength - lastTickLength;
16754     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16755
16756     if (WhiteOnMove(forwardMostMove)) {
16757         if(whiteNPS >= 0) lastTickLength = 0;
16758         timeRemaining = whiteTimeRemaining -= lastTickLength;
16759         if(timeRemaining < 0 && !appData.icsActive) {
16760             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16761             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16762                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16763                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16764             }
16765         }
16766         DisplayWhiteClock(whiteTimeRemaining - fudge,
16767                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16768     } else {
16769         if(blackNPS >= 0) lastTickLength = 0;
16770         timeRemaining = blackTimeRemaining -= lastTickLength;
16771         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16772             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16773             if(suddenDeath) {
16774                 blackStartMove = forwardMostMove;
16775                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16776             }
16777         }
16778         DisplayBlackClock(blackTimeRemaining - fudge,
16779                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16780     }
16781     if (CheckFlags()) return;
16782
16783     if(twoBoards) { // count down secondary board's clocks as well
16784         activePartnerTime -= lastTickLength;
16785         partnerUp = 1;
16786         if(activePartner == 'W')
16787             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16788         else
16789             DisplayBlackClock(activePartnerTime, TRUE);
16790         partnerUp = 0;
16791     }
16792
16793     tickStartTM = now;
16794     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16795     StartClockTimer(intendedTickLength);
16796
16797     /* if the time remaining has fallen below the alarm threshold, sound the
16798      * alarm. if the alarm has sounded and (due to a takeback or time control
16799      * with increment) the time remaining has increased to a level above the
16800      * threshold, reset the alarm so it can sound again.
16801      */
16802
16803     if (appData.icsActive && appData.icsAlarm) {
16804
16805         /* make sure we are dealing with the user's clock */
16806         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16807                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16808            )) return;
16809
16810         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16811             alarmSounded = FALSE;
16812         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16813             PlayAlarmSound();
16814             alarmSounded = TRUE;
16815         }
16816     }
16817 }
16818
16819
16820 /* A player has just moved, so stop the previously running
16821    clock and (if in clock mode) start the other one.
16822    We redisplay both clocks in case we're in ICS mode, because
16823    ICS gives us an update to both clocks after every move.
16824    Note that this routine is called *after* forwardMostMove
16825    is updated, so the last fractional tick must be subtracted
16826    from the color that is *not* on move now.
16827 */
16828 void
16829 SwitchClocks (int newMoveNr)
16830 {
16831     long lastTickLength;
16832     TimeMark now;
16833     int flagged = FALSE;
16834
16835     GetTimeMark(&now);
16836
16837     if (StopClockTimer() && appData.clockMode) {
16838         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16839         if (!WhiteOnMove(forwardMostMove)) {
16840             if(blackNPS >= 0) lastTickLength = 0;
16841             blackTimeRemaining -= lastTickLength;
16842            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16843 //         if(pvInfoList[forwardMostMove].time == -1)
16844                  pvInfoList[forwardMostMove].time =               // use GUI time
16845                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16846         } else {
16847            if(whiteNPS >= 0) lastTickLength = 0;
16848            whiteTimeRemaining -= lastTickLength;
16849            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16850 //         if(pvInfoList[forwardMostMove].time == -1)
16851                  pvInfoList[forwardMostMove].time =
16852                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16853         }
16854         flagged = CheckFlags();
16855     }
16856     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16857     CheckTimeControl();
16858
16859     if (flagged || !appData.clockMode) return;
16860
16861     switch (gameMode) {
16862       case MachinePlaysBlack:
16863       case MachinePlaysWhite:
16864       case BeginningOfGame:
16865         if (pausing) return;
16866         break;
16867
16868       case EditGame:
16869       case PlayFromGameFile:
16870       case IcsExamining:
16871         return;
16872
16873       default:
16874         break;
16875     }
16876
16877     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16878         if(WhiteOnMove(forwardMostMove))
16879              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16880         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16881     }
16882
16883     tickStartTM = now;
16884     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16885       whiteTimeRemaining : blackTimeRemaining);
16886     StartClockTimer(intendedTickLength);
16887 }
16888
16889
16890 /* Stop both clocks */
16891 void
16892 StopClocks ()
16893 {
16894     long lastTickLength;
16895     TimeMark now;
16896
16897     if (!StopClockTimer()) return;
16898     if (!appData.clockMode) return;
16899
16900     GetTimeMark(&now);
16901
16902     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16903     if (WhiteOnMove(forwardMostMove)) {
16904         if(whiteNPS >= 0) lastTickLength = 0;
16905         whiteTimeRemaining -= lastTickLength;
16906         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16907     } else {
16908         if(blackNPS >= 0) lastTickLength = 0;
16909         blackTimeRemaining -= lastTickLength;
16910         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16911     }
16912     CheckFlags();
16913 }
16914
16915 /* Start clock of player on move.  Time may have been reset, so
16916    if clock is already running, stop and restart it. */
16917 void
16918 StartClocks ()
16919 {
16920     (void) StopClockTimer(); /* in case it was running already */
16921     DisplayBothClocks();
16922     if (CheckFlags()) return;
16923
16924     if (!appData.clockMode) return;
16925     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16926
16927     GetTimeMark(&tickStartTM);
16928     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16929       whiteTimeRemaining : blackTimeRemaining);
16930
16931    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16932     whiteNPS = blackNPS = -1;
16933     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16934        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16935         whiteNPS = first.nps;
16936     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16937        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16938         blackNPS = first.nps;
16939     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16940         whiteNPS = second.nps;
16941     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16942         blackNPS = second.nps;
16943     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16944
16945     StartClockTimer(intendedTickLength);
16946 }
16947
16948 char *
16949 TimeString (long ms)
16950 {
16951     long second, minute, hour, day;
16952     char *sign = "";
16953     static char buf[32];
16954
16955     if (ms > 0 && ms <= 9900) {
16956       /* convert milliseconds to tenths, rounding up */
16957       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16958
16959       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16960       return buf;
16961     }
16962
16963     /* convert milliseconds to seconds, rounding up */
16964     /* use floating point to avoid strangeness of integer division
16965        with negative dividends on many machines */
16966     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16967
16968     if (second < 0) {
16969         sign = "-";
16970         second = -second;
16971     }
16972
16973     day = second / (60 * 60 * 24);
16974     second = second % (60 * 60 * 24);
16975     hour = second / (60 * 60);
16976     second = second % (60 * 60);
16977     minute = second / 60;
16978     second = second % 60;
16979
16980     if (day > 0)
16981       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16982               sign, day, hour, minute, second);
16983     else if (hour > 0)
16984       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16985     else
16986       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16987
16988     return buf;
16989 }
16990
16991
16992 /*
16993  * This is necessary because some C libraries aren't ANSI C compliant yet.
16994  */
16995 char *
16996 StrStr (char *string, char *match)
16997 {
16998     int i, length;
16999
17000     length = strlen(match);
17001
17002     for (i = strlen(string) - length; i >= 0; i--, string++)
17003       if (!strncmp(match, string, length))
17004         return string;
17005
17006     return NULL;
17007 }
17008
17009 char *
17010 StrCaseStr (char *string, char *match)
17011 {
17012     int i, j, length;
17013
17014     length = strlen(match);
17015
17016     for (i = strlen(string) - length; i >= 0; i--, string++) {
17017         for (j = 0; j < length; j++) {
17018             if (ToLower(match[j]) != ToLower(string[j]))
17019               break;
17020         }
17021         if (j == length) return string;
17022     }
17023
17024     return NULL;
17025 }
17026
17027 #ifndef _amigados
17028 int
17029 StrCaseCmp (char *s1, char *s2)
17030 {
17031     char c1, c2;
17032
17033     for (;;) {
17034         c1 = ToLower(*s1++);
17035         c2 = ToLower(*s2++);
17036         if (c1 > c2) return 1;
17037         if (c1 < c2) return -1;
17038         if (c1 == NULLCHAR) return 0;
17039     }
17040 }
17041
17042
17043 int
17044 ToLower (int c)
17045 {
17046     return isupper(c) ? tolower(c) : c;
17047 }
17048
17049
17050 int
17051 ToUpper (int c)
17052 {
17053     return islower(c) ? toupper(c) : c;
17054 }
17055 #endif /* !_amigados    */
17056
17057 char *
17058 StrSave (char *s)
17059 {
17060   char *ret;
17061
17062   if ((ret = (char *) malloc(strlen(s) + 1)))
17063     {
17064       safeStrCpy(ret, s, strlen(s)+1);
17065     }
17066   return ret;
17067 }
17068
17069 char *
17070 StrSavePtr (char *s, char **savePtr)
17071 {
17072     if (*savePtr) {
17073         free(*savePtr);
17074     }
17075     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17076       safeStrCpy(*savePtr, s, strlen(s)+1);
17077     }
17078     return(*savePtr);
17079 }
17080
17081 char *
17082 PGNDate ()
17083 {
17084     time_t clock;
17085     struct tm *tm;
17086     char buf[MSG_SIZ];
17087
17088     clock = time((time_t *)NULL);
17089     tm = localtime(&clock);
17090     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17091             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17092     return StrSave(buf);
17093 }
17094
17095
17096 char *
17097 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17098 {
17099     int i, j, fromX, fromY, toX, toY;
17100     int whiteToPlay;
17101     char buf[MSG_SIZ];
17102     char *p, *q;
17103     int emptycount;
17104     ChessSquare piece;
17105
17106     whiteToPlay = (gameMode == EditPosition) ?
17107       !blackPlaysFirst : (move % 2 == 0);
17108     p = buf;
17109
17110     /* Piece placement data */
17111     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17112         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17113         emptycount = 0;
17114         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17115             if (boards[move][i][j] == EmptySquare) {
17116                 emptycount++;
17117             } else { ChessSquare piece = boards[move][i][j];
17118                 if (emptycount > 0) {
17119                     if(emptycount<10) /* [HGM] can be >= 10 */
17120                         *p++ = '0' + emptycount;
17121                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17122                     emptycount = 0;
17123                 }
17124                 if(PieceToChar(piece) == '+') {
17125                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17126                     *p++ = '+';
17127                     piece = (ChessSquare)(DEMOTED piece);
17128                 }
17129                 *p++ = PieceToChar(piece);
17130                 if(p[-1] == '~') {
17131                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17132                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17133                     *p++ = '~';
17134                 }
17135             }
17136         }
17137         if (emptycount > 0) {
17138             if(emptycount<10) /* [HGM] can be >= 10 */
17139                 *p++ = '0' + emptycount;
17140             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17141             emptycount = 0;
17142         }
17143         *p++ = '/';
17144     }
17145     *(p - 1) = ' ';
17146
17147     /* [HGM] print Crazyhouse or Shogi holdings */
17148     if( gameInfo.holdingsWidth ) {
17149         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17150         q = p;
17151         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17152             piece = boards[move][i][BOARD_WIDTH-1];
17153             if( piece != EmptySquare )
17154               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17155                   *p++ = PieceToChar(piece);
17156         }
17157         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17158             piece = boards[move][BOARD_HEIGHT-i-1][0];
17159             if( piece != EmptySquare )
17160               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17161                   *p++ = PieceToChar(piece);
17162         }
17163
17164         if( q == p ) *p++ = '-';
17165         *p++ = ']';
17166         *p++ = ' ';
17167     }
17168
17169     /* Active color */
17170     *p++ = whiteToPlay ? 'w' : 'b';
17171     *p++ = ' ';
17172
17173   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17174     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17175   } else {
17176   if(nrCastlingRights) {
17177      q = p;
17178      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17179        /* [HGM] write directly from rights */
17180            if(boards[move][CASTLING][2] != NoRights &&
17181               boards[move][CASTLING][0] != NoRights   )
17182                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17183            if(boards[move][CASTLING][2] != NoRights &&
17184               boards[move][CASTLING][1] != NoRights   )
17185                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17186            if(boards[move][CASTLING][5] != NoRights &&
17187               boards[move][CASTLING][3] != NoRights   )
17188                 *p++ = boards[move][CASTLING][3] + AAA;
17189            if(boards[move][CASTLING][5] != NoRights &&
17190               boards[move][CASTLING][4] != NoRights   )
17191                 *p++ = boards[move][CASTLING][4] + AAA;
17192      } else {
17193
17194         /* [HGM] write true castling rights */
17195         if( nrCastlingRights == 6 ) {
17196             int q, k=0;
17197             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17198                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17199             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17200                  boards[move][CASTLING][2] != NoRights  );
17201             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17202                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17203                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17204                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17205                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17206             }
17207             if(q) *p++ = 'Q';
17208             k = 0;
17209             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17210                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17211             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17212                  boards[move][CASTLING][5] != NoRights  );
17213             if(gameInfo.variant == VariantSChess) {
17214                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17215                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17216                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17217                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17218             }
17219             if(q) *p++ = 'q';
17220         }
17221      }
17222      if (q == p) *p++ = '-'; /* No castling rights */
17223      *p++ = ' ';
17224   }
17225
17226   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17227      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17228      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17229     /* En passant target square */
17230     if (move > backwardMostMove) {
17231         fromX = moveList[move - 1][0] - AAA;
17232         fromY = moveList[move - 1][1] - ONE;
17233         toX = moveList[move - 1][2] - AAA;
17234         toY = moveList[move - 1][3] - ONE;
17235         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17236             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17237             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17238             fromX == toX) {
17239             /* 2-square pawn move just happened */
17240             *p++ = toX + AAA;
17241             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17242         } else {
17243             *p++ = '-';
17244         }
17245     } else if(move == backwardMostMove) {
17246         // [HGM] perhaps we should always do it like this, and forget the above?
17247         if((signed char)boards[move][EP_STATUS] >= 0) {
17248             *p++ = boards[move][EP_STATUS] + AAA;
17249             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17250         } else {
17251             *p++ = '-';
17252         }
17253     } else {
17254         *p++ = '-';
17255     }
17256     *p++ = ' ';
17257   }
17258   }
17259
17260     if(moveCounts)
17261     {   int i = 0, j=move;
17262
17263         /* [HGM] find reversible plies */
17264         if (appData.debugMode) { int k;
17265             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17266             for(k=backwardMostMove; k<=forwardMostMove; k++)
17267                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17268
17269         }
17270
17271         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17272         if( j == backwardMostMove ) i += initialRulePlies;
17273         sprintf(p, "%d ", i);
17274         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17275
17276         /* Fullmove number */
17277         sprintf(p, "%d", (move / 2) + 1);
17278     } else *--p = NULLCHAR;
17279
17280     return StrSave(buf);
17281 }
17282
17283 Boolean
17284 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17285 {
17286     int i, j;
17287     char *p, c;
17288     int emptycount, virgin[BOARD_FILES];
17289     ChessSquare piece;
17290
17291     p = fen;
17292
17293     /* [HGM] by default clear Crazyhouse holdings, if present */
17294     if(gameInfo.holdingsWidth) {
17295        for(i=0; i<BOARD_HEIGHT; i++) {
17296            board[i][0]             = EmptySquare; /* black holdings */
17297            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17298            board[i][1]             = (ChessSquare) 0; /* black counts */
17299            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17300        }
17301     }
17302
17303     /* Piece placement data */
17304     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17305         j = 0;
17306         for (;;) {
17307             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17308                 if (*p == '/') p++;
17309                 emptycount = gameInfo.boardWidth - j;
17310                 while (emptycount--)
17311                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17312                 break;
17313 #if(BOARD_FILES >= 10)
17314             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17315                 p++; emptycount=10;
17316                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17317                 while (emptycount--)
17318                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17319 #endif
17320             } else if (isdigit(*p)) {
17321                 emptycount = *p++ - '0';
17322                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17323                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17324                 while (emptycount--)
17325                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17326             } else if (*p == '+' || isalpha(*p)) {
17327                 if (j >= gameInfo.boardWidth) return FALSE;
17328                 if(*p=='+') {
17329                     piece = CharToPiece(*++p);
17330                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17331                     piece = (ChessSquare) (PROMOTED piece ); p++;
17332                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17333                 } else piece = CharToPiece(*p++);
17334
17335                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17336                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17337                     piece = (ChessSquare) (PROMOTED piece);
17338                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17339                     p++;
17340                 }
17341                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17342             } else {
17343                 return FALSE;
17344             }
17345         }
17346     }
17347     while (*p == '/' || *p == ' ') p++;
17348
17349     /* [HGM] look for Crazyhouse holdings here */
17350     while(*p==' ') p++;
17351     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17352         if(*p == '[') p++;
17353         if(*p == '-' ) p++; /* empty holdings */ else {
17354             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17355             /* if we would allow FEN reading to set board size, we would   */
17356             /* have to add holdings and shift the board read so far here   */
17357             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17358                 p++;
17359                 if((int) piece >= (int) BlackPawn ) {
17360                     i = (int)piece - (int)BlackPawn;
17361                     i = PieceToNumber((ChessSquare)i);
17362                     if( i >= gameInfo.holdingsSize ) return FALSE;
17363                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17364                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17365                 } else {
17366                     i = (int)piece - (int)WhitePawn;
17367                     i = PieceToNumber((ChessSquare)i);
17368                     if( i >= gameInfo.holdingsSize ) return FALSE;
17369                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17370                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17371                 }
17372             }
17373         }
17374         if(*p == ']') p++;
17375     }
17376
17377     while(*p == ' ') p++;
17378
17379     /* Active color */
17380     c = *p++;
17381     if(appData.colorNickNames) {
17382       if( c == appData.colorNickNames[0] ) c = 'w'; else
17383       if( c == appData.colorNickNames[1] ) c = 'b';
17384     }
17385     switch (c) {
17386       case 'w':
17387         *blackPlaysFirst = FALSE;
17388         break;
17389       case 'b':
17390         *blackPlaysFirst = TRUE;
17391         break;
17392       default:
17393         return FALSE;
17394     }
17395
17396     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17397     /* return the extra info in global variiables             */
17398
17399     /* set defaults in case FEN is incomplete */
17400     board[EP_STATUS] = EP_UNKNOWN;
17401     for(i=0; i<nrCastlingRights; i++ ) {
17402         board[CASTLING][i] =
17403             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17404     }   /* assume possible unless obviously impossible */
17405     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17406     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17407     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17408                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17409     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17410     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17411     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17412                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17413     FENrulePlies = 0;
17414
17415     while(*p==' ') p++;
17416     if(nrCastlingRights) {
17417       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17418       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17419           /* castling indicator present, so default becomes no castlings */
17420           for(i=0; i<nrCastlingRights; i++ ) {
17421                  board[CASTLING][i] = NoRights;
17422           }
17423       }
17424       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17425              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17426              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17427              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17428         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17429
17430         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17431             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17432             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17433         }
17434         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17435             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17436         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17437                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17438         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17439                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17440         switch(c) {
17441           case'K':
17442               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17443               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17444               board[CASTLING][2] = whiteKingFile;
17445               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17446               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17447               break;
17448           case'Q':
17449               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17450               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17451               board[CASTLING][2] = whiteKingFile;
17452               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17453               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17454               break;
17455           case'k':
17456               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17457               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17458               board[CASTLING][5] = blackKingFile;
17459               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17460               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17461               break;
17462           case'q':
17463               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17464               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17465               board[CASTLING][5] = blackKingFile;
17466               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17467               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17468           case '-':
17469               break;
17470           default: /* FRC castlings */
17471               if(c >= 'a') { /* black rights */
17472                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17473                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17474                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17475                   if(i == BOARD_RGHT) break;
17476                   board[CASTLING][5] = i;
17477                   c -= AAA;
17478                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17479                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17480                   if(c > i)
17481                       board[CASTLING][3] = c;
17482                   else
17483                       board[CASTLING][4] = c;
17484               } else { /* white rights */
17485                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17486                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17487                     if(board[0][i] == WhiteKing) break;
17488                   if(i == BOARD_RGHT) break;
17489                   board[CASTLING][2] = i;
17490                   c -= AAA - 'a' + 'A';
17491                   if(board[0][c] >= WhiteKing) break;
17492                   if(c > i)
17493                       board[CASTLING][0] = c;
17494                   else
17495                       board[CASTLING][1] = c;
17496               }
17497         }
17498       }
17499       for(i=0; i<nrCastlingRights; i++)
17500         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17501       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17502     if (appData.debugMode) {
17503         fprintf(debugFP, "FEN castling rights:");
17504         for(i=0; i<nrCastlingRights; i++)
17505         fprintf(debugFP, " %d", board[CASTLING][i]);
17506         fprintf(debugFP, "\n");
17507     }
17508
17509       while(*p==' ') p++;
17510     }
17511
17512     /* read e.p. field in games that know e.p. capture */
17513     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17514        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17515        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17516       if(*p=='-') {
17517         p++; board[EP_STATUS] = EP_NONE;
17518       } else {
17519          char c = *p++ - AAA;
17520
17521          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17522          if(*p >= '0' && *p <='9') p++;
17523          board[EP_STATUS] = c;
17524       }
17525     }
17526
17527
17528     if(sscanf(p, "%d", &i) == 1) {
17529         FENrulePlies = i; /* 50-move ply counter */
17530         /* (The move number is still ignored)    */
17531     }
17532
17533     return TRUE;
17534 }
17535
17536 void
17537 EditPositionPasteFEN (char *fen)
17538 {
17539   if (fen != NULL) {
17540     Board initial_position;
17541
17542     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17543       DisplayError(_("Bad FEN position in clipboard"), 0);
17544       return ;
17545     } else {
17546       int savedBlackPlaysFirst = blackPlaysFirst;
17547       EditPositionEvent();
17548       blackPlaysFirst = savedBlackPlaysFirst;
17549       CopyBoard(boards[0], initial_position);
17550       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17551       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17552       DisplayBothClocks();
17553       DrawPosition(FALSE, boards[currentMove]);
17554     }
17555   }
17556 }
17557
17558 static char cseq[12] = "\\   ";
17559
17560 Boolean
17561 set_cont_sequence (char *new_seq)
17562 {
17563     int len;
17564     Boolean ret;
17565
17566     // handle bad attempts to set the sequence
17567         if (!new_seq)
17568                 return 0; // acceptable error - no debug
17569
17570     len = strlen(new_seq);
17571     ret = (len > 0) && (len < sizeof(cseq));
17572     if (ret)
17573       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17574     else if (appData.debugMode)
17575       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17576     return ret;
17577 }
17578
17579 /*
17580     reformat a source message so words don't cross the width boundary.  internal
17581     newlines are not removed.  returns the wrapped size (no null character unless
17582     included in source message).  If dest is NULL, only calculate the size required
17583     for the dest buffer.  lp argument indicats line position upon entry, and it's
17584     passed back upon exit.
17585 */
17586 int
17587 wrap (char *dest, char *src, int count, int width, int *lp)
17588 {
17589     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17590
17591     cseq_len = strlen(cseq);
17592     old_line = line = *lp;
17593     ansi = len = clen = 0;
17594
17595     for (i=0; i < count; i++)
17596     {
17597         if (src[i] == '\033')
17598             ansi = 1;
17599
17600         // if we hit the width, back up
17601         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17602         {
17603             // store i & len in case the word is too long
17604             old_i = i, old_len = len;
17605
17606             // find the end of the last word
17607             while (i && src[i] != ' ' && src[i] != '\n')
17608             {
17609                 i--;
17610                 len--;
17611             }
17612
17613             // word too long?  restore i & len before splitting it
17614             if ((old_i-i+clen) >= width)
17615             {
17616                 i = old_i;
17617                 len = old_len;
17618             }
17619
17620             // extra space?
17621             if (i && src[i-1] == ' ')
17622                 len--;
17623
17624             if (src[i] != ' ' && src[i] != '\n')
17625             {
17626                 i--;
17627                 if (len)
17628                     len--;
17629             }
17630
17631             // now append the newline and continuation sequence
17632             if (dest)
17633                 dest[len] = '\n';
17634             len++;
17635             if (dest)
17636                 strncpy(dest+len, cseq, cseq_len);
17637             len += cseq_len;
17638             line = cseq_len;
17639             clen = cseq_len;
17640             continue;
17641         }
17642
17643         if (dest)
17644             dest[len] = src[i];
17645         len++;
17646         if (!ansi)
17647             line++;
17648         if (src[i] == '\n')
17649             line = 0;
17650         if (src[i] == 'm')
17651             ansi = 0;
17652     }
17653     if (dest && appData.debugMode)
17654     {
17655         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17656             count, width, line, len, *lp);
17657         show_bytes(debugFP, src, count);
17658         fprintf(debugFP, "\ndest: ");
17659         show_bytes(debugFP, dest, len);
17660         fprintf(debugFP, "\n");
17661     }
17662     *lp = dest ? line : old_line;
17663
17664     return len;
17665 }
17666
17667 // [HGM] vari: routines for shelving variations
17668 Boolean modeRestore = FALSE;
17669
17670 void
17671 PushInner (int firstMove, int lastMove)
17672 {
17673         int i, j, nrMoves = lastMove - firstMove;
17674
17675         // push current tail of game on stack
17676         savedResult[storedGames] = gameInfo.result;
17677         savedDetails[storedGames] = gameInfo.resultDetails;
17678         gameInfo.resultDetails = NULL;
17679         savedFirst[storedGames] = firstMove;
17680         savedLast [storedGames] = lastMove;
17681         savedFramePtr[storedGames] = framePtr;
17682         framePtr -= nrMoves; // reserve space for the boards
17683         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17684             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17685             for(j=0; j<MOVE_LEN; j++)
17686                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17687             for(j=0; j<2*MOVE_LEN; j++)
17688                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17689             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17690             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17691             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17692             pvInfoList[firstMove+i-1].depth = 0;
17693             commentList[framePtr+i] = commentList[firstMove+i];
17694             commentList[firstMove+i] = NULL;
17695         }
17696
17697         storedGames++;
17698         forwardMostMove = firstMove; // truncate game so we can start variation
17699 }
17700
17701 void
17702 PushTail (int firstMove, int lastMove)
17703 {
17704         if(appData.icsActive) { // only in local mode
17705                 forwardMostMove = currentMove; // mimic old ICS behavior
17706                 return;
17707         }
17708         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17709
17710         PushInner(firstMove, lastMove);
17711         if(storedGames == 1) GreyRevert(FALSE);
17712         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17713 }
17714
17715 void
17716 PopInner (Boolean annotate)
17717 {
17718         int i, j, nrMoves;
17719         char buf[8000], moveBuf[20];
17720
17721         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17722         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17723         nrMoves = savedLast[storedGames] - currentMove;
17724         if(annotate) {
17725                 int cnt = 10;
17726                 if(!WhiteOnMove(currentMove))
17727                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17728                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17729                 for(i=currentMove; i<forwardMostMove; i++) {
17730                         if(WhiteOnMove(i))
17731                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17732                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17733                         strcat(buf, moveBuf);
17734                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17735                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17736                 }
17737                 strcat(buf, ")");
17738         }
17739         for(i=1; i<=nrMoves; i++) { // copy last variation back
17740             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17741             for(j=0; j<MOVE_LEN; j++)
17742                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17743             for(j=0; j<2*MOVE_LEN; j++)
17744                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17745             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17746             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17747             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17748             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17749             commentList[currentMove+i] = commentList[framePtr+i];
17750             commentList[framePtr+i] = NULL;
17751         }
17752         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17753         framePtr = savedFramePtr[storedGames];
17754         gameInfo.result = savedResult[storedGames];
17755         if(gameInfo.resultDetails != NULL) {
17756             free(gameInfo.resultDetails);
17757       }
17758         gameInfo.resultDetails = savedDetails[storedGames];
17759         forwardMostMove = currentMove + nrMoves;
17760 }
17761
17762 Boolean
17763 PopTail (Boolean annotate)
17764 {
17765         if(appData.icsActive) return FALSE; // only in local mode
17766         if(!storedGames) return FALSE; // sanity
17767         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17768
17769         PopInner(annotate);
17770         if(currentMove < forwardMostMove) ForwardEvent(); else
17771         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17772
17773         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17774         return TRUE;
17775 }
17776
17777 void
17778 CleanupTail ()
17779 {       // remove all shelved variations
17780         int i;
17781         for(i=0; i<storedGames; i++) {
17782             if(savedDetails[i])
17783                 free(savedDetails[i]);
17784             savedDetails[i] = NULL;
17785         }
17786         for(i=framePtr; i<MAX_MOVES; i++) {
17787                 if(commentList[i]) free(commentList[i]);
17788                 commentList[i] = NULL;
17789         }
17790         framePtr = MAX_MOVES-1;
17791         storedGames = 0;
17792 }
17793
17794 void
17795 LoadVariation (int index, char *text)
17796 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17797         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17798         int level = 0, move;
17799
17800         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17801         // first find outermost bracketing variation
17802         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17803             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17804                 if(*p == '{') wait = '}'; else
17805                 if(*p == '[') wait = ']'; else
17806                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17807                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17808             }
17809             if(*p == wait) wait = NULLCHAR; // closing ]} found
17810             p++;
17811         }
17812         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17813         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17814         end[1] = NULLCHAR; // clip off comment beyond variation
17815         ToNrEvent(currentMove-1);
17816         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17817         // kludge: use ParsePV() to append variation to game
17818         move = currentMove;
17819         ParsePV(start, TRUE, TRUE);
17820         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17821         ClearPremoveHighlights();
17822         CommentPopDown();
17823         ToNrEvent(currentMove+1);
17824 }
17825
17826 void
17827 LoadTheme ()
17828 {
17829     char *p, *q, buf[MSG_SIZ];
17830     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17831         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17832         ParseArgsFromString(buf);
17833         ActivateTheme(TRUE); // also redo colors
17834         return;
17835     }
17836     p = nickName;
17837     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17838     {
17839         int len;
17840         q = appData.themeNames;
17841         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17842       if(appData.useBitmaps) {
17843         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17844                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17845                 appData.liteBackTextureMode,
17846                 appData.darkBackTextureMode );
17847       } else {
17848         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17849                 Col2Text(2),   // lightSquareColor
17850                 Col2Text(3) ); // darkSquareColor
17851       }
17852       if(appData.useBorder) {
17853         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17854                 appData.border);
17855       } else {
17856         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17857       }
17858       if(appData.useFont) {
17859         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17860                 appData.renderPiecesWithFont,
17861                 appData.fontToPieceTable,
17862                 Col2Text(9),    // appData.fontBackColorWhite
17863                 Col2Text(10) ); // appData.fontForeColorBlack
17864       } else {
17865         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17866                 appData.pieceDirectory);
17867         if(!appData.pieceDirectory[0])
17868           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17869                 Col2Text(0),   // whitePieceColor
17870                 Col2Text(1) ); // blackPieceColor
17871       }
17872       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17873                 Col2Text(4),   // highlightSquareColor
17874                 Col2Text(5) ); // premoveHighlightColor
17875         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17876         if(insert != q) insert[-1] = NULLCHAR;
17877         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17878         if(q)   free(q);
17879     }
17880     ActivateTheme(FALSE);
17881 }