Don't add PV moves on board clicking in AnalyzeMode
[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
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 Boolean abortMatch;
245
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 int endPV = -1;
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
253 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
257 Boolean partnerUp;
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
269 int chattingPartner;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 char lastMsg[MSG_SIZ];
272 ChessSquare pieceSweep = EmptySquare;
273 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
274 int promoDefaultAltered;
275 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
276
277 /* States for ics_getting_history */
278 #define H_FALSE 0
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
284
285 /* whosays values for GameEnds */
286 #define GE_ICS 0
287 #define GE_ENGINE 1
288 #define GE_PLAYER 2
289 #define GE_FILE 3
290 #define GE_XBOARD 4
291 #define GE_ENGINE1 5
292 #define GE_ENGINE2 6
293
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
296
297 /* Different types of move when calling RegisterMove */
298 #define CMAIL_MOVE   0
299 #define CMAIL_RESIGN 1
300 #define CMAIL_DRAW   2
301 #define CMAIL_ACCEPT 3
302
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
307
308 /* Telnet protocol constants */
309 #define TN_WILL 0373
310 #define TN_WONT 0374
311 #define TN_DO   0375
312 #define TN_DONT 0376
313 #define TN_IAC  0377
314 #define TN_ECHO 0001
315 #define TN_SGA  0003
316 #define TN_PORT 23
317
318 char*
319 safeStrCpy (char *dst, const char *src, size_t count)
320 { // [HGM] made safe
321   int i;
322   assert( dst != NULL );
323   assert( src != NULL );
324   assert( count > 0 );
325
326   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327   if(  i == count && dst[count-1] != NULLCHAR)
328     {
329       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330       if(appData.debugMode)
331       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
332     }
333
334   return dst;
335 }
336
337 /* Some compiler can't cast u64 to double
338  * This function do the job for us:
339
340  * We use the highest bit for cast, this only
341  * works if the highest bit is not
342  * in use (This should not happen)
343  *
344  * We used this for all compiler
345  */
346 double
347 u64ToDouble (u64 value)
348 {
349   double r;
350   u64 tmp = value & u64Const(0x7fffffffffffffff);
351   r = (double)(s64)tmp;
352   if (value & u64Const(0x8000000000000000))
353        r +=  9.2233720368547758080e18; /* 2^63 */
354  return r;
355 }
356
357 /* Fake up flags for now, as we aren't keeping track of castling
358    availability yet. [HGM] Change of logic: the flag now only
359    indicates the type of castlings allowed by the rule of the game.
360    The actual rights themselves are maintained in the array
361    castlingRights, as part of the game history, and are not probed
362    by this function.
363  */
364 int
365 PosFlags (index)
366 {
367   int flags = F_ALL_CASTLE_OK;
368   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369   switch (gameInfo.variant) {
370   case VariantSuicide:
371     flags &= ~F_ALL_CASTLE_OK;
372   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373     flags |= F_IGNORE_CHECK;
374   case VariantLosers:
375     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
376     break;
377   case VariantAtomic:
378     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
379     break;
380   case VariantKriegspiel:
381     flags |= F_KRIEGSPIEL_CAPTURE;
382     break;
383   case VariantCapaRandom:
384   case VariantFischeRandom:
385     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386   case VariantNoCastle:
387   case VariantShatranj:
388   case VariantCourier:
389   case VariantMakruk:
390   case VariantGrand:
391     flags &= ~F_ALL_CASTLE_OK;
392     break;
393   default:
394     break;
395   }
396   return flags;
397 }
398
399 FILE *gameFileFP, *debugFP, *serverFP;
400 char *currentDebugFile; // [HGM] debug split: to remember name
401
402 /*
403     [AS] Note: sometimes, the sscanf() function is used to parse the input
404     into a fixed-size buffer. Because of this, we must be prepared to
405     receive strings as long as the size of the input buffer, which is currently
406     set to 4K for Windows and 8K for the rest.
407     So, we must either allocate sufficiently large buffers here, or
408     reduce the size of the input buffer in the input reading part.
409 */
410
411 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
412 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
413 char thinkOutput1[MSG_SIZ*10];
414
415 ChessProgramState first, second, pairing;
416
417 /* premove variables */
418 int premoveToX = 0;
419 int premoveToY = 0;
420 int premoveFromX = 0;
421 int premoveFromY = 0;
422 int premovePromoChar = 0;
423 int gotPremove = 0;
424 Boolean alarmSounded;
425 /* end premove variables */
426
427 char *ics_prefix = "$";
428 enum ICS_TYPE ics_type = ICS_GENERIC;
429
430 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
431 int pauseExamForwardMostMove = 0;
432 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
433 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
434 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
435 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
436 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
437 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
438 int whiteFlag = FALSE, blackFlag = FALSE;
439 int userOfferedDraw = FALSE;
440 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
441 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
442 int cmailMoveType[CMAIL_MAX_GAMES];
443 long ics_clock_paused = 0;
444 ProcRef icsPR = NoProc, cmailPR = NoProc;
445 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
446 GameMode gameMode = BeginningOfGame;
447 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
448 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
449 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
450 int hiddenThinkOutputState = 0; /* [AS] */
451 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
452 int adjudicateLossPlies = 6;
453 char white_holding[64], black_holding[64];
454 TimeMark lastNodeCountTime;
455 long lastNodeCount=0;
456 int shiftKey, controlKey; // [HGM] set by mouse handler
457
458 int have_sent_ICS_logon = 0;
459 int movesPerSession;
460 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
461 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
462 Boolean adjustedClock;
463 long timeControl_2; /* [AS] Allow separate time controls */
464 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
465 long timeRemaining[2][MAX_MOVES];
466 int matchGame = 0, nextGame = 0, roundNr = 0;
467 Boolean waitingForGame = FALSE, startingEngine = FALSE;
468 TimeMark programStartTime, pauseStart;
469 char ics_handle[MSG_SIZ];
470 int have_set_title = 0;
471
472 /* animateTraining preserves the state of appData.animate
473  * when Training mode is activated. This allows the
474  * response to be animated when appData.animate == TRUE and
475  * appData.animateDragging == TRUE.
476  */
477 Boolean animateTraining;
478
479 GameInfo gameInfo;
480
481 AppData appData;
482
483 Board boards[MAX_MOVES];
484 /* [HGM] Following 7 needed for accurate legality tests: */
485 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
486 signed char  initialRights[BOARD_FILES];
487 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
488 int   initialRulePlies, FENrulePlies;
489 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
490 int loadFlag = 0;
491 Boolean shuffleOpenings;
492 int mute; // mute all sounds
493
494 // [HGM] vari: next 12 to save and restore variations
495 #define MAX_VARIATIONS 10
496 int framePtr = MAX_MOVES-1; // points to free stack entry
497 int storedGames = 0;
498 int savedFirst[MAX_VARIATIONS];
499 int savedLast[MAX_VARIATIONS];
500 int savedFramePtr[MAX_VARIATIONS];
501 char *savedDetails[MAX_VARIATIONS];
502 ChessMove savedResult[MAX_VARIATIONS];
503
504 void PushTail P((int firstMove, int lastMove));
505 Boolean PopTail P((Boolean annotate));
506 void PushInner P((int firstMove, int lastMove));
507 void PopInner P((Boolean annotate));
508 void CleanupTail P((void));
509
510 ChessSquare  FIDEArray[2][BOARD_FILES] = {
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514         BlackKing, BlackBishop, BlackKnight, BlackRook }
515 };
516
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521         BlackKing, BlackKing, BlackKnight, BlackRook }
522 };
523
524 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
525     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527     { BlackRook, BlackMan, BlackBishop, BlackQueen,
528         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
529 };
530
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
536 };
537
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
543 };
544
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
550 };
551
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackMan, BlackFerz,
556         BlackKing, BlackMan, BlackKnight, BlackRook }
557 };
558
559
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
566 };
567
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
573 };
574
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
580 };
581
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
587 };
588
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
594 };
595
596 ChessSquare GrandArray[2][BOARD_FILES] = {
597     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
598         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
599     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
600         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
601 };
602
603 #ifdef GOTHIC
604 ChessSquare GothicArray[2][BOARD_FILES] = {
605     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
606         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
607     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
608         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 };
610 #else // !GOTHIC
611 #define GothicArray CapablancaArray
612 #endif // !GOTHIC
613
614 #ifdef FALCON
615 ChessSquare FalconArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
617         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
619         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
620 };
621 #else // !FALCON
622 #define FalconArray CapablancaArray
623 #endif // !FALCON
624
625 #else // !(BOARD_FILES>=10)
626 #define XiangqiPosition FIDEArray
627 #define CapablancaArray FIDEArray
628 #define GothicArray FIDEArray
629 #define GreatArray FIDEArray
630 #endif // !(BOARD_FILES>=10)
631
632 #if (BOARD_FILES>=12)
633 ChessSquare CourierArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
635         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
636     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
637         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
638 };
639 #else // !(BOARD_FILES>=12)
640 #define CourierArray CapablancaArray
641 #endif // !(BOARD_FILES>=12)
642
643
644 Board initialPosition;
645
646
647 /* Convert str to a rating. Checks for special cases of "----",
648
649    "++++", etc. Also strips ()'s */
650 int
651 string_to_rating (char *str)
652 {
653   while(*str && !isdigit(*str)) ++str;
654   if (!*str)
655     return 0;   /* One of the special "no rating" cases */
656   else
657     return atoi(str);
658 }
659
660 void
661 ClearProgramStats ()
662 {
663     /* Init programStats */
664     programStats.movelist[0] = 0;
665     programStats.depth = 0;
666     programStats.nr_moves = 0;
667     programStats.moves_left = 0;
668     programStats.nodes = 0;
669     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
670     programStats.score = 0;
671     programStats.got_only_move = 0;
672     programStats.got_fail = 0;
673     programStats.line_is_book = 0;
674 }
675
676 void
677 CommonEngineInit ()
678 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679     if (appData.firstPlaysBlack) {
680         first.twoMachinesColor = "black\n";
681         second.twoMachinesColor = "white\n";
682     } else {
683         first.twoMachinesColor = "white\n";
684         second.twoMachinesColor = "black\n";
685     }
686
687     first.other = &second;
688     second.other = &first;
689
690     { float norm = 1;
691         if(appData.timeOddsMode) {
692             norm = appData.timeOdds[0];
693             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694         }
695         first.timeOdds  = appData.timeOdds[0]/norm;
696         second.timeOdds = appData.timeOdds[1]/norm;
697     }
698
699     if(programVersion) free(programVersion);
700     if (appData.noChessProgram) {
701         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702         sprintf(programVersion, "%s", PACKAGE_STRING);
703     } else {
704       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707     }
708 }
709
710 void
711 UnloadEngine (ChessProgramState *cps)
712 {
713         /* Kill off first chess program */
714         if (cps->isr != NULL)
715           RemoveInputSource(cps->isr);
716         cps->isr = NULL;
717
718         if (cps->pr != NoProc) {
719             ExitAnalyzeMode();
720             DoSleep( appData.delayBeforeQuit );
721             SendToProgram("quit\n", cps);
722             DoSleep( appData.delayAfterQuit );
723             DestroyChildProcess(cps->pr, cps->useSigterm);
724         }
725         cps->pr = NoProc;
726         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 }
728
729 void
730 ClearOptions (ChessProgramState *cps)
731 {
732     int i;
733     cps->nrOptions = cps->comboCnt = 0;
734     for(i=0; i<MAX_OPTIONS; i++) {
735         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736         cps->option[i].textValue = 0;
737     }
738 }
739
740 char *engineNames[] = {
741   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("first"),
744   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
745      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
746 N_("second")
747 };
748
749 void
750 InitEngine (ChessProgramState *cps, int n)
751 {   // [HGM] all engine initialiation put in a function that does one engine
752
753     ClearOptions(cps);
754
755     cps->which = engineNames[n];
756     cps->maybeThinking = FALSE;
757     cps->pr = NoProc;
758     cps->isr = NULL;
759     cps->sendTime = 2;
760     cps->sendDrawOffers = 1;
761
762     cps->program = appData.chessProgram[n];
763     cps->host = appData.host[n];
764     cps->dir = appData.directory[n];
765     cps->initString = appData.engInitString[n];
766     cps->computerString = appData.computerString[n];
767     cps->useSigint  = TRUE;
768     cps->useSigterm = TRUE;
769     cps->reuse = appData.reuse[n];
770     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
771     cps->useSetboard = FALSE;
772     cps->useSAN = FALSE;
773     cps->usePing = FALSE;
774     cps->lastPing = 0;
775     cps->lastPong = 0;
776     cps->usePlayother = FALSE;
777     cps->useColors = TRUE;
778     cps->useUsermove = FALSE;
779     cps->sendICS = FALSE;
780     cps->sendName = appData.icsActive;
781     cps->sdKludge = FALSE;
782     cps->stKludge = FALSE;
783     TidyProgramName(cps->program, cps->host, cps->tidy);
784     cps->matchWins = 0;
785     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786     cps->analysisSupport = 2; /* detect */
787     cps->analyzing = FALSE;
788     cps->initDone = FALSE;
789     cps->reload = FALSE;
790
791     /* New features added by Tord: */
792     cps->useFEN960 = FALSE;
793     cps->useOOCastle = TRUE;
794     /* End of new features added by Tord. */
795     cps->fenOverride  = appData.fenOverride[n];
796
797     /* [HGM] time odds: set factor for each machine */
798     cps->timeOdds  = appData.timeOdds[n];
799
800     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
801     cps->accumulateTC = appData.accumulateTC[n];
802     cps->maxNrOfSessions = 1;
803
804     /* [HGM] debug */
805     cps->debug = FALSE;
806
807     cps->supportsNPS = UNKNOWN;
808     cps->memSize = FALSE;
809     cps->maxCores = FALSE;
810     cps->egtFormats[0] = NULLCHAR;
811
812     /* [HGM] options */
813     cps->optionSettings  = appData.engOptions[n];
814
815     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
816     cps->isUCI = appData.isUCI[n]; /* [AS] */
817     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
818
819     if (appData.protocolVersion[n] > PROTOVER
820         || appData.protocolVersion[n] < 1)
821       {
822         char buf[MSG_SIZ];
823         int len;
824
825         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
826                        appData.protocolVersion[n]);
827         if( (len >= MSG_SIZ) && appData.debugMode )
828           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
829
830         DisplayFatalError(buf, 0, 2);
831       }
832     else
833       {
834         cps->protocolVersion = appData.protocolVersion[n];
835       }
836
837     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
838     ParseFeatures(appData.featureDefaults, cps);
839 }
840
841 ChessProgramState *savCps;
842
843 GameMode oldMode;
844
845 void
846 LoadEngine ()
847 {
848     int i;
849     if(WaitForEngine(savCps, LoadEngine)) return;
850     CommonEngineInit(); // recalculate time odds
851     if(gameInfo.variant != StringToVariant(appData.variant)) {
852         // we changed variant when loading the engine; this forces us to reset
853         Reset(TRUE, savCps != &first);
854         oldMode = BeginningOfGame; // to prevent restoring old mode
855     }
856     InitChessProgram(savCps, FALSE);
857     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
858     DisplayMessage("", "");
859     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
860     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
861     ThawUI();
862     SetGNUMode();
863     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
864 }
865
866 void
867 ReplaceEngine (ChessProgramState *cps, int n)
868 {
869     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
870     keepInfo = 1;
871     if(oldMode != BeginningOfGame) EditGameEvent();
872     keepInfo = 0;
873     UnloadEngine(cps);
874     appData.noChessProgram = FALSE;
875     appData.clockMode = TRUE;
876     InitEngine(cps, n);
877     UpdateLogos(TRUE);
878     if(n) return; // only startup first engine immediately; second can wait
879     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
880     LoadEngine();
881 }
882
883 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
884 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
885
886 static char resetOptions[] =
887         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
888         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
889         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
890         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
891
892 void
893 FloatToFront(char **list, char *engineLine)
894 {
895     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
896     int i=0;
897     if(appData.recentEngines <= 0) return;
898     TidyProgramName(engineLine, "localhost", tidy+1);
899     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
900     strncpy(buf+1, *list, MSG_SIZ-50);
901     if(p = strstr(buf, tidy)) { // tidy name appears in list
902         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
903         while(*p++ = *++q); // squeeze out
904     }
905     strcat(tidy, buf+1); // put list behind tidy name
906     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
907     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
908     ASSIGN(*list, tidy+1);
909 }
910
911 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
912
913 void
914 Load (ChessProgramState *cps, int i)
915 {
916     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
917     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
918         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
919         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
920         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
921         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
922         appData.firstProtocolVersion = PROTOVER;
923         ParseArgsFromString(buf);
924         SwapEngines(i);
925         ReplaceEngine(cps, i);
926         FloatToFront(&appData.recentEngineList, engineLine);
927         return;
928     }
929     p = engineName;
930     while(q = strchr(p, SLASH)) p = q+1;
931     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
932     if(engineDir[0] != NULLCHAR) {
933         ASSIGN(appData.directory[i], engineDir); p = engineName;
934     } else if(p != engineName) { // derive directory from engine path, when not given
935         p[-1] = 0;
936         ASSIGN(appData.directory[i], engineName);
937         p[-1] = SLASH;
938         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
939     } else { ASSIGN(appData.directory[i], "."); }
940     if(params[0]) {
941         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
942         snprintf(command, MSG_SIZ, "%s %s", p, params);
943         p = command;
944     }
945     ASSIGN(appData.chessProgram[i], p);
946     appData.isUCI[i] = isUCI;
947     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
948     appData.hasOwnBookUCI[i] = hasBook;
949     if(!nickName[0]) useNick = FALSE;
950     if(useNick) ASSIGN(appData.pgnName[i], nickName);
951     if(addToList) {
952         int len;
953         char quote;
954         q = firstChessProgramNames;
955         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
956         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
957         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
958                         quote, p, quote, appData.directory[i],
959                         useNick ? " -fn \"" : "",
960                         useNick ? nickName : "",
961                         useNick ? "\"" : "",
962                         v1 ? " -firstProtocolVersion 1" : "",
963                         hasBook ? "" : " -fNoOwnBookUCI",
964                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
965                         storeVariant ? " -variant " : "",
966                         storeVariant ? VariantName(gameInfo.variant) : "");
967         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
968         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
969         if(insert != q) insert[-1] = NULLCHAR;
970         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
971         if(q)   free(q);
972         FloatToFront(&appData.recentEngineList, buf);
973     }
974     ReplaceEngine(cps, i);
975 }
976
977 void
978 InitTimeControls ()
979 {
980     int matched, min, sec;
981     /*
982      * Parse timeControl resource
983      */
984     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
985                           appData.movesPerSession)) {
986         char buf[MSG_SIZ];
987         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
988         DisplayFatalError(buf, 0, 2);
989     }
990
991     /*
992      * Parse searchTime resource
993      */
994     if (*appData.searchTime != NULLCHAR) {
995         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
996         if (matched == 1) {
997             searchTime = min * 60;
998         } else if (matched == 2) {
999             searchTime = min * 60 + sec;
1000         } else {
1001             char buf[MSG_SIZ];
1002             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1003             DisplayFatalError(buf, 0, 2);
1004         }
1005     }
1006 }
1007
1008 void
1009 InitBackEnd1 ()
1010 {
1011
1012     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1013     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1014
1015     GetTimeMark(&programStartTime);
1016     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1017     appData.seedBase = random() + (random()<<15);
1018     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1019
1020     ClearProgramStats();
1021     programStats.ok_to_send = 1;
1022     programStats.seen_stat = 0;
1023
1024     /*
1025      * Initialize game list
1026      */
1027     ListNew(&gameList);
1028
1029
1030     /*
1031      * Internet chess server status
1032      */
1033     if (appData.icsActive) {
1034         appData.matchMode = FALSE;
1035         appData.matchGames = 0;
1036 #if ZIPPY
1037         appData.noChessProgram = !appData.zippyPlay;
1038 #else
1039         appData.zippyPlay = FALSE;
1040         appData.zippyTalk = FALSE;
1041         appData.noChessProgram = TRUE;
1042 #endif
1043         if (*appData.icsHelper != NULLCHAR) {
1044             appData.useTelnet = TRUE;
1045             appData.telnetProgram = appData.icsHelper;
1046         }
1047     } else {
1048         appData.zippyTalk = appData.zippyPlay = FALSE;
1049     }
1050
1051     /* [AS] Initialize pv info list [HGM] and game state */
1052     {
1053         int i, j;
1054
1055         for( i=0; i<=framePtr; i++ ) {
1056             pvInfoList[i].depth = -1;
1057             boards[i][EP_STATUS] = EP_NONE;
1058             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1059         }
1060     }
1061
1062     InitTimeControls();
1063
1064     /* [AS] Adjudication threshold */
1065     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1066
1067     InitEngine(&first, 0);
1068     InitEngine(&second, 1);
1069     CommonEngineInit();
1070
1071     pairing.which = "pairing"; // pairing engine
1072     pairing.pr = NoProc;
1073     pairing.isr = NULL;
1074     pairing.program = appData.pairingEngine;
1075     pairing.host = "localhost";
1076     pairing.dir = ".";
1077
1078     if (appData.icsActive) {
1079         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1080     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1081         appData.clockMode = FALSE;
1082         first.sendTime = second.sendTime = 0;
1083     }
1084
1085 #if ZIPPY
1086     /* Override some settings from environment variables, for backward
1087        compatibility.  Unfortunately it's not feasible to have the env
1088        vars just set defaults, at least in xboard.  Ugh.
1089     */
1090     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1091       ZippyInit();
1092     }
1093 #endif
1094
1095     if (!appData.icsActive) {
1096       char buf[MSG_SIZ];
1097       int len;
1098
1099       /* Check for variants that are supported only in ICS mode,
1100          or not at all.  Some that are accepted here nevertheless
1101          have bugs; see comments below.
1102       */
1103       VariantClass variant = StringToVariant(appData.variant);
1104       switch (variant) {
1105       case VariantBughouse:     /* need four players and two boards */
1106       case VariantKriegspiel:   /* need to hide pieces and move details */
1107         /* case VariantFischeRandom: (Fabien: moved below) */
1108         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1109         if( (len >= MSG_SIZ) && appData.debugMode )
1110           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1111
1112         DisplayFatalError(buf, 0, 2);
1113         return;
1114
1115       case VariantUnknown:
1116       case VariantLoadable:
1117       case Variant29:
1118       case Variant30:
1119       case Variant31:
1120       case Variant32:
1121       case Variant33:
1122       case Variant34:
1123       case Variant35:
1124       case Variant36:
1125       default:
1126         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1127         if( (len >= MSG_SIZ) && appData.debugMode )
1128           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1129
1130         DisplayFatalError(buf, 0, 2);
1131         return;
1132
1133       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1134       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1135       case VariantGothic:     /* [HGM] should work */
1136       case VariantCapablanca: /* [HGM] should work */
1137       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1138       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1139       case VariantKnightmate: /* [HGM] should work */
1140       case VariantCylinder:   /* [HGM] untested */
1141       case VariantFalcon:     /* [HGM] untested */
1142       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1143                                  offboard interposition not understood */
1144       case VariantNormal:     /* definitely works! */
1145       case VariantWildCastle: /* pieces not automatically shuffled */
1146       case VariantNoCastle:   /* pieces not automatically shuffled */
1147       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1148       case VariantLosers:     /* should work except for win condition,
1149                                  and doesn't know captures are mandatory */
1150       case VariantSuicide:    /* should work except for win condition,
1151                                  and doesn't know captures are mandatory */
1152       case VariantGiveaway:   /* should work except for win condition,
1153                                  and doesn't know captures are mandatory */
1154       case VariantTwoKings:   /* should work */
1155       case VariantAtomic:     /* should work except for win condition */
1156       case Variant3Check:     /* should work except for win condition */
1157       case VariantShatranj:   /* should work except for all win conditions */
1158       case VariantMakruk:     /* should work except for draw countdown */
1159       case VariantBerolina:   /* might work if TestLegality is off */
1160       case VariantCapaRandom: /* should work */
1161       case VariantJanus:      /* should work */
1162       case VariantSuper:      /* experimental */
1163       case VariantGreat:      /* experimental, requires legality testing to be off */
1164       case VariantSChess:     /* S-Chess, should work */
1165       case VariantGrand:      /* should work */
1166       case VariantSpartan:    /* should work */
1167         break;
1168       }
1169     }
1170
1171 }
1172
1173 int
1174 NextIntegerFromString (char ** str, long * value)
1175 {
1176     int result = -1;
1177     char * s = *str;
1178
1179     while( *s == ' ' || *s == '\t' ) {
1180         s++;
1181     }
1182
1183     *value = 0;
1184
1185     if( *s >= '0' && *s <= '9' ) {
1186         while( *s >= '0' && *s <= '9' ) {
1187             *value = *value * 10 + (*s - '0');
1188             s++;
1189         }
1190
1191         result = 0;
1192     }
1193
1194     *str = s;
1195
1196     return result;
1197 }
1198
1199 int
1200 NextTimeControlFromString (char ** str, long * value)
1201 {
1202     long temp;
1203     int result = NextIntegerFromString( str, &temp );
1204
1205     if( result == 0 ) {
1206         *value = temp * 60; /* Minutes */
1207         if( **str == ':' ) {
1208             (*str)++;
1209             result = NextIntegerFromString( str, &temp );
1210             *value += temp; /* Seconds */
1211         }
1212     }
1213
1214     return result;
1215 }
1216
1217 int
1218 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1219 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1220     int result = -1, type = 0; long temp, temp2;
1221
1222     if(**str != ':') return -1; // old params remain in force!
1223     (*str)++;
1224     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1225     if( NextIntegerFromString( str, &temp ) ) return -1;
1226     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1227
1228     if(**str != '/') {
1229         /* time only: incremental or sudden-death time control */
1230         if(**str == '+') { /* increment follows; read it */
1231             (*str)++;
1232             if(**str == '!') type = *(*str)++; // Bronstein TC
1233             if(result = NextIntegerFromString( str, &temp2)) return -1;
1234             *inc = temp2 * 1000;
1235             if(**str == '.') { // read fraction of increment
1236                 char *start = ++(*str);
1237                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1238                 temp2 *= 1000;
1239                 while(start++ < *str) temp2 /= 10;
1240                 *inc += temp2;
1241             }
1242         } else *inc = 0;
1243         *moves = 0; *tc = temp * 1000; *incType = type;
1244         return 0;
1245     }
1246
1247     (*str)++; /* classical time control */
1248     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1249
1250     if(result == 0) {
1251         *moves = temp;
1252         *tc    = temp2 * 1000;
1253         *inc   = 0;
1254         *incType = type;
1255     }
1256     return result;
1257 }
1258
1259 int
1260 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1261 {   /* [HGM] get time to add from the multi-session time-control string */
1262     int incType, moves=1; /* kludge to force reading of first session */
1263     long time, increment;
1264     char *s = tcString;
1265
1266     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1267     do {
1268         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1269         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1270         if(movenr == -1) return time;    /* last move before new session     */
1271         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1272         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1273         if(!moves) return increment;     /* current session is incremental   */
1274         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1275     } while(movenr >= -1);               /* try again for next session       */
1276
1277     return 0; // no new time quota on this move
1278 }
1279
1280 int
1281 ParseTimeControl (char *tc, float ti, int mps)
1282 {
1283   long tc1;
1284   long tc2;
1285   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1286   int min, sec=0;
1287
1288   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1289   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1290       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1291   if(ti > 0) {
1292
1293     if(mps)
1294       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1295     else
1296       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1297   } else {
1298     if(mps)
1299       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1300     else
1301       snprintf(buf, MSG_SIZ, ":%s", mytc);
1302   }
1303   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1304
1305   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1306     return FALSE;
1307   }
1308
1309   if( *tc == '/' ) {
1310     /* Parse second time control */
1311     tc++;
1312
1313     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1314       return FALSE;
1315     }
1316
1317     if( tc2 == 0 ) {
1318       return FALSE;
1319     }
1320
1321     timeControl_2 = tc2 * 1000;
1322   }
1323   else {
1324     timeControl_2 = 0;
1325   }
1326
1327   if( tc1 == 0 ) {
1328     return FALSE;
1329   }
1330
1331   timeControl = tc1 * 1000;
1332
1333   if (ti >= 0) {
1334     timeIncrement = ti * 1000;  /* convert to ms */
1335     movesPerSession = 0;
1336   } else {
1337     timeIncrement = 0;
1338     movesPerSession = mps;
1339   }
1340   return TRUE;
1341 }
1342
1343 void
1344 InitBackEnd2 ()
1345 {
1346     if (appData.debugMode) {
1347         fprintf(debugFP, "%s\n", programVersion);
1348     }
1349     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1350
1351     set_cont_sequence(appData.wrapContSeq);
1352     if (appData.matchGames > 0) {
1353         appData.matchMode = TRUE;
1354     } else if (appData.matchMode) {
1355         appData.matchGames = 1;
1356     }
1357     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1358         appData.matchGames = appData.sameColorGames;
1359     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1360         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1361         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1362     }
1363     Reset(TRUE, FALSE);
1364     if (appData.noChessProgram || first.protocolVersion == 1) {
1365       InitBackEnd3();
1366     } else {
1367       /* kludge: allow timeout for initial "feature" commands */
1368       FreezeUI();
1369       DisplayMessage("", _("Starting chess program"));
1370       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1371     }
1372 }
1373
1374 int
1375 CalculateIndex (int index, int gameNr)
1376 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1377     int res;
1378     if(index > 0) return index; // fixed nmber
1379     if(index == 0) return 1;
1380     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1381     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1382     return res;
1383 }
1384
1385 int
1386 LoadGameOrPosition (int gameNr)
1387 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1388     if (*appData.loadGameFile != NULLCHAR) {
1389         if (!LoadGameFromFile(appData.loadGameFile,
1390                 CalculateIndex(appData.loadGameIndex, gameNr),
1391                               appData.loadGameFile, FALSE)) {
1392             DisplayFatalError(_("Bad game file"), 0, 1);
1393             return 0;
1394         }
1395     } else if (*appData.loadPositionFile != NULLCHAR) {
1396         if (!LoadPositionFromFile(appData.loadPositionFile,
1397                 CalculateIndex(appData.loadPositionIndex, gameNr),
1398                                   appData.loadPositionFile)) {
1399             DisplayFatalError(_("Bad position file"), 0, 1);
1400             return 0;
1401         }
1402     }
1403     return 1;
1404 }
1405
1406 void
1407 ReserveGame (int gameNr, char resChar)
1408 {
1409     FILE *tf = fopen(appData.tourneyFile, "r+");
1410     char *p, *q, c, buf[MSG_SIZ];
1411     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1412     safeStrCpy(buf, lastMsg, MSG_SIZ);
1413     DisplayMessage(_("Pick new game"), "");
1414     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1415     ParseArgsFromFile(tf);
1416     p = q = appData.results;
1417     if(appData.debugMode) {
1418       char *r = appData.participants;
1419       fprintf(debugFP, "results = '%s'\n", p);
1420       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1421       fprintf(debugFP, "\n");
1422     }
1423     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1424     nextGame = q - p;
1425     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1426     safeStrCpy(q, p, strlen(p) + 2);
1427     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1428     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1429     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1430         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1431         q[nextGame] = '*';
1432     }
1433     fseek(tf, -(strlen(p)+4), SEEK_END);
1434     c = fgetc(tf);
1435     if(c != '"') // depending on DOS or Unix line endings we can be one off
1436          fseek(tf, -(strlen(p)+2), SEEK_END);
1437     else fseek(tf, -(strlen(p)+3), SEEK_END);
1438     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1439     DisplayMessage(buf, "");
1440     free(p); appData.results = q;
1441     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1442        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1443       int round = appData.defaultMatchGames * appData.tourneyType;
1444       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1445          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1446         UnloadEngine(&first);  // next game belongs to other pairing;
1447         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1448     }
1449     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1450 }
1451
1452 void
1453 MatchEvent (int mode)
1454 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1455         int dummy;
1456         if(matchMode) { // already in match mode: switch it off
1457             abortMatch = TRUE;
1458             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1459             return;
1460         }
1461 //      if(gameMode != BeginningOfGame) {
1462 //          DisplayError(_("You can only start a match from the initial position."), 0);
1463 //          return;
1464 //      }
1465         abortMatch = FALSE;
1466         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1467         /* Set up machine vs. machine match */
1468         nextGame = 0;
1469         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1470         if(appData.tourneyFile[0]) {
1471             ReserveGame(-1, 0);
1472             if(nextGame > appData.matchGames) {
1473                 char buf[MSG_SIZ];
1474                 if(strchr(appData.results, '*') == NULL) {
1475                     FILE *f;
1476                     appData.tourneyCycles++;
1477                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1478                         fclose(f);
1479                         NextTourneyGame(-1, &dummy);
1480                         ReserveGame(-1, 0);
1481                         if(nextGame <= appData.matchGames) {
1482                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1483                             matchMode = mode;
1484                             ScheduleDelayedEvent(NextMatchGame, 10000);
1485                             return;
1486                         }
1487                     }
1488                 }
1489                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1490                 DisplayError(buf, 0);
1491                 appData.tourneyFile[0] = 0;
1492                 return;
1493             }
1494         } else
1495         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1496             DisplayFatalError(_("Can't have a match with no chess programs"),
1497                               0, 2);
1498             return;
1499         }
1500         matchMode = mode;
1501         matchGame = roundNr = 1;
1502         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1503         NextMatchGame();
1504 }
1505
1506 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1507
1508 void
1509 InitBackEnd3 P((void))
1510 {
1511     GameMode initialMode;
1512     char buf[MSG_SIZ];
1513     int err, len;
1514
1515     InitChessProgram(&first, startedFromSetupPosition);
1516
1517     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1518         free(programVersion);
1519         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1520         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1521         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1522     }
1523
1524     if (appData.icsActive) {
1525 #ifdef WIN32
1526         /* [DM] Make a console window if needed [HGM] merged ifs */
1527         ConsoleCreate();
1528 #endif
1529         err = establish();
1530         if (err != 0)
1531           {
1532             if (*appData.icsCommPort != NULLCHAR)
1533               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1534                              appData.icsCommPort);
1535             else
1536               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1537                         appData.icsHost, appData.icsPort);
1538
1539             if( (len >= MSG_SIZ) && appData.debugMode )
1540               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1541
1542             DisplayFatalError(buf, err, 1);
1543             return;
1544         }
1545         SetICSMode();
1546         telnetISR =
1547           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1548         fromUserISR =
1549           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1550         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1551             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1552     } else if (appData.noChessProgram) {
1553         SetNCPMode();
1554     } else {
1555         SetGNUMode();
1556     }
1557
1558     if (*appData.cmailGameName != NULLCHAR) {
1559         SetCmailMode();
1560         OpenLoopback(&cmailPR);
1561         cmailISR =
1562           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1563     }
1564
1565     ThawUI();
1566     DisplayMessage("", "");
1567     if (StrCaseCmp(appData.initialMode, "") == 0) {
1568       initialMode = BeginningOfGame;
1569       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1570         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1571         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1572         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1573         ModeHighlight();
1574       }
1575     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1576       initialMode = TwoMachinesPlay;
1577     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1578       initialMode = AnalyzeFile;
1579     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1580       initialMode = AnalyzeMode;
1581     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1582       initialMode = MachinePlaysWhite;
1583     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1584       initialMode = MachinePlaysBlack;
1585     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1586       initialMode = EditGame;
1587     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1588       initialMode = EditPosition;
1589     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1590       initialMode = Training;
1591     } else {
1592       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1593       if( (len >= MSG_SIZ) && appData.debugMode )
1594         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1595
1596       DisplayFatalError(buf, 0, 2);
1597       return;
1598     }
1599
1600     if (appData.matchMode) {
1601         if(appData.tourneyFile[0]) { // start tourney from command line
1602             FILE *f;
1603             if(f = fopen(appData.tourneyFile, "r")) {
1604                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1605                 fclose(f);
1606                 appData.clockMode = TRUE;
1607                 SetGNUMode();
1608             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1609         }
1610         MatchEvent(TRUE);
1611     } else if (*appData.cmailGameName != NULLCHAR) {
1612         /* Set up cmail mode */
1613         ReloadCmailMsgEvent(TRUE);
1614     } else {
1615         /* Set up other modes */
1616         if (initialMode == AnalyzeFile) {
1617           if (*appData.loadGameFile == NULLCHAR) {
1618             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1619             return;
1620           }
1621         }
1622         if (*appData.loadGameFile != NULLCHAR) {
1623             (void) LoadGameFromFile(appData.loadGameFile,
1624                                     appData.loadGameIndex,
1625                                     appData.loadGameFile, TRUE);
1626         } else if (*appData.loadPositionFile != NULLCHAR) {
1627             (void) LoadPositionFromFile(appData.loadPositionFile,
1628                                         appData.loadPositionIndex,
1629                                         appData.loadPositionFile);
1630             /* [HGM] try to make self-starting even after FEN load */
1631             /* to allow automatic setup of fairy variants with wtm */
1632             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1633                 gameMode = BeginningOfGame;
1634                 setboardSpoiledMachineBlack = 1;
1635             }
1636             /* [HGM] loadPos: make that every new game uses the setup */
1637             /* from file as long as we do not switch variant          */
1638             if(!blackPlaysFirst) {
1639                 startedFromPositionFile = TRUE;
1640                 CopyBoard(filePosition, boards[0]);
1641             }
1642         }
1643         if (initialMode == AnalyzeMode) {
1644           if (appData.noChessProgram) {
1645             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1646             return;
1647           }
1648           if (appData.icsActive) {
1649             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1650             return;
1651           }
1652           AnalyzeModeEvent();
1653         } else if (initialMode == AnalyzeFile) {
1654           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1655           ShowThinkingEvent();
1656           AnalyzeFileEvent();
1657           AnalysisPeriodicEvent(1);
1658         } else if (initialMode == MachinePlaysWhite) {
1659           if (appData.noChessProgram) {
1660             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1661                               0, 2);
1662             return;
1663           }
1664           if (appData.icsActive) {
1665             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1666                               0, 2);
1667             return;
1668           }
1669           MachineWhiteEvent();
1670         } else if (initialMode == MachinePlaysBlack) {
1671           if (appData.noChessProgram) {
1672             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1673                               0, 2);
1674             return;
1675           }
1676           if (appData.icsActive) {
1677             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1678                               0, 2);
1679             return;
1680           }
1681           MachineBlackEvent();
1682         } else if (initialMode == TwoMachinesPlay) {
1683           if (appData.noChessProgram) {
1684             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1685                               0, 2);
1686             return;
1687           }
1688           if (appData.icsActive) {
1689             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1690                               0, 2);
1691             return;
1692           }
1693           TwoMachinesEvent();
1694         } else if (initialMode == EditGame) {
1695           EditGameEvent();
1696         } else if (initialMode == EditPosition) {
1697           EditPositionEvent();
1698         } else if (initialMode == Training) {
1699           if (*appData.loadGameFile == NULLCHAR) {
1700             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1701             return;
1702           }
1703           TrainingEvent();
1704         }
1705     }
1706 }
1707
1708 void
1709 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1710 {
1711     DisplayBook(current+1);
1712
1713     MoveHistorySet( movelist, first, last, current, pvInfoList );
1714
1715     EvalGraphSet( first, last, current, pvInfoList );
1716
1717     MakeEngineOutputTitle();
1718 }
1719
1720 /*
1721  * Establish will establish a contact to a remote host.port.
1722  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1723  *  used to talk to the host.
1724  * Returns 0 if okay, error code if not.
1725  */
1726 int
1727 establish ()
1728 {
1729     char buf[MSG_SIZ];
1730
1731     if (*appData.icsCommPort != NULLCHAR) {
1732         /* Talk to the host through a serial comm port */
1733         return OpenCommPort(appData.icsCommPort, &icsPR);
1734
1735     } else if (*appData.gateway != NULLCHAR) {
1736         if (*appData.remoteShell == NULLCHAR) {
1737             /* Use the rcmd protocol to run telnet program on a gateway host */
1738             snprintf(buf, sizeof(buf), "%s %s %s",
1739                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1740             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1741
1742         } else {
1743             /* Use the rsh program to run telnet program on a gateway host */
1744             if (*appData.remoteUser == NULLCHAR) {
1745                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1746                         appData.gateway, appData.telnetProgram,
1747                         appData.icsHost, appData.icsPort);
1748             } else {
1749                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1750                         appData.remoteShell, appData.gateway,
1751                         appData.remoteUser, appData.telnetProgram,
1752                         appData.icsHost, appData.icsPort);
1753             }
1754             return StartChildProcess(buf, "", &icsPR);
1755
1756         }
1757     } else if (appData.useTelnet) {
1758         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1759
1760     } else {
1761         /* TCP socket interface differs somewhat between
1762            Unix and NT; handle details in the front end.
1763            */
1764         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1765     }
1766 }
1767
1768 void
1769 EscapeExpand (char *p, char *q)
1770 {       // [HGM] initstring: routine to shape up string arguments
1771         while(*p++ = *q++) if(p[-1] == '\\')
1772             switch(*q++) {
1773                 case 'n': p[-1] = '\n'; break;
1774                 case 'r': p[-1] = '\r'; break;
1775                 case 't': p[-1] = '\t'; break;
1776                 case '\\': p[-1] = '\\'; break;
1777                 case 0: *p = 0; return;
1778                 default: p[-1] = q[-1]; break;
1779             }
1780 }
1781
1782 void
1783 show_bytes (FILE *fp, char *buf, int count)
1784 {
1785     while (count--) {
1786         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1787             fprintf(fp, "\\%03o", *buf & 0xff);
1788         } else {
1789             putc(*buf, fp);
1790         }
1791         buf++;
1792     }
1793     fflush(fp);
1794 }
1795
1796 /* Returns an errno value */
1797 int
1798 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1799 {
1800     char buf[8192], *p, *q, *buflim;
1801     int left, newcount, outcount;
1802
1803     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1804         *appData.gateway != NULLCHAR) {
1805         if (appData.debugMode) {
1806             fprintf(debugFP, ">ICS: ");
1807             show_bytes(debugFP, message, count);
1808             fprintf(debugFP, "\n");
1809         }
1810         return OutputToProcess(pr, message, count, outError);
1811     }
1812
1813     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1814     p = message;
1815     q = buf;
1816     left = count;
1817     newcount = 0;
1818     while (left) {
1819         if (q >= buflim) {
1820             if (appData.debugMode) {
1821                 fprintf(debugFP, ">ICS: ");
1822                 show_bytes(debugFP, buf, newcount);
1823                 fprintf(debugFP, "\n");
1824             }
1825             outcount = OutputToProcess(pr, buf, newcount, outError);
1826             if (outcount < newcount) return -1; /* to be sure */
1827             q = buf;
1828             newcount = 0;
1829         }
1830         if (*p == '\n') {
1831             *q++ = '\r';
1832             newcount++;
1833         } else if (((unsigned char) *p) == TN_IAC) {
1834             *q++ = (char) TN_IAC;
1835             newcount ++;
1836         }
1837         *q++ = *p++;
1838         newcount++;
1839         left--;
1840     }
1841     if (appData.debugMode) {
1842         fprintf(debugFP, ">ICS: ");
1843         show_bytes(debugFP, buf, newcount);
1844         fprintf(debugFP, "\n");
1845     }
1846     outcount = OutputToProcess(pr, buf, newcount, outError);
1847     if (outcount < newcount) return -1; /* to be sure */
1848     return count;
1849 }
1850
1851 void
1852 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1853 {
1854     int outError, outCount;
1855     static int gotEof = 0;
1856     static FILE *ini;
1857
1858     /* Pass data read from player on to ICS */
1859     if (count > 0) {
1860         gotEof = 0;
1861         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1862         if (outCount < count) {
1863             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1864         }
1865         if(have_sent_ICS_logon == 2) {
1866           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1867             fprintf(ini, "%s", message);
1868             have_sent_ICS_logon = 3;
1869           } else
1870             have_sent_ICS_logon = 1;
1871         } else if(have_sent_ICS_logon == 3) {
1872             fprintf(ini, "%s", message);
1873             fclose(ini);
1874           have_sent_ICS_logon = 1;
1875         }
1876     } else if (count < 0) {
1877         RemoveInputSource(isr);
1878         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1879     } else if (gotEof++ > 0) {
1880         RemoveInputSource(isr);
1881         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1882     }
1883 }
1884
1885 void
1886 KeepAlive ()
1887 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1888     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1889     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1890     SendToICS("date\n");
1891     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1892 }
1893
1894 /* added routine for printf style output to ics */
1895 void
1896 ics_printf (char *format, ...)
1897 {
1898     char buffer[MSG_SIZ];
1899     va_list args;
1900
1901     va_start(args, format);
1902     vsnprintf(buffer, sizeof(buffer), format, args);
1903     buffer[sizeof(buffer)-1] = '\0';
1904     SendToICS(buffer);
1905     va_end(args);
1906 }
1907
1908 void
1909 SendToICS (char *s)
1910 {
1911     int count, outCount, outError;
1912
1913     if (icsPR == NoProc) return;
1914
1915     count = strlen(s);
1916     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1917     if (outCount < count) {
1918         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1919     }
1920 }
1921
1922 /* This is used for sending logon scripts to the ICS. Sending
1923    without a delay causes problems when using timestamp on ICC
1924    (at least on my machine). */
1925 void
1926 SendToICSDelayed (char *s, long msdelay)
1927 {
1928     int count, outCount, outError;
1929
1930     if (icsPR == NoProc) return;
1931
1932     count = strlen(s);
1933     if (appData.debugMode) {
1934         fprintf(debugFP, ">ICS: ");
1935         show_bytes(debugFP, s, count);
1936         fprintf(debugFP, "\n");
1937     }
1938     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1939                                       msdelay);
1940     if (outCount < count) {
1941         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1942     }
1943 }
1944
1945
1946 /* Remove all highlighting escape sequences in s
1947    Also deletes any suffix starting with '('
1948    */
1949 char *
1950 StripHighlightAndTitle (char *s)
1951 {
1952     static char retbuf[MSG_SIZ];
1953     char *p = retbuf;
1954
1955     while (*s != NULLCHAR) {
1956         while (*s == '\033') {
1957             while (*s != NULLCHAR && !isalpha(*s)) s++;
1958             if (*s != NULLCHAR) s++;
1959         }
1960         while (*s != NULLCHAR && *s != '\033') {
1961             if (*s == '(' || *s == '[') {
1962                 *p = NULLCHAR;
1963                 return retbuf;
1964             }
1965             *p++ = *s++;
1966         }
1967     }
1968     *p = NULLCHAR;
1969     return retbuf;
1970 }
1971
1972 /* Remove all highlighting escape sequences in s */
1973 char *
1974 StripHighlight (char *s)
1975 {
1976     static char retbuf[MSG_SIZ];
1977     char *p = retbuf;
1978
1979     while (*s != NULLCHAR) {
1980         while (*s == '\033') {
1981             while (*s != NULLCHAR && !isalpha(*s)) s++;
1982             if (*s != NULLCHAR) s++;
1983         }
1984         while (*s != NULLCHAR && *s != '\033') {
1985             *p++ = *s++;
1986         }
1987     }
1988     *p = NULLCHAR;
1989     return retbuf;
1990 }
1991
1992 char *variantNames[] = VARIANT_NAMES;
1993 char *
1994 VariantName (VariantClass v)
1995 {
1996     return variantNames[v];
1997 }
1998
1999
2000 /* Identify a variant from the strings the chess servers use or the
2001    PGN Variant tag names we use. */
2002 VariantClass
2003 StringToVariant (char *e)
2004 {
2005     char *p;
2006     int wnum = -1;
2007     VariantClass v = VariantNormal;
2008     int i, found = FALSE;
2009     char buf[MSG_SIZ];
2010     int len;
2011
2012     if (!e) return v;
2013
2014     /* [HGM] skip over optional board-size prefixes */
2015     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2016         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2017         while( *e++ != '_');
2018     }
2019
2020     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2021         v = VariantNormal;
2022         found = TRUE;
2023     } else
2024     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2025       if (StrCaseStr(e, variantNames[i])) {
2026         v = (VariantClass) i;
2027         found = TRUE;
2028         break;
2029       }
2030     }
2031
2032     if (!found) {
2033       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2034           || StrCaseStr(e, "wild/fr")
2035           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2036         v = VariantFischeRandom;
2037       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2038                  (i = 1, p = StrCaseStr(e, "w"))) {
2039         p += i;
2040         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2041         if (isdigit(*p)) {
2042           wnum = atoi(p);
2043         } else {
2044           wnum = -1;
2045         }
2046         switch (wnum) {
2047         case 0: /* FICS only, actually */
2048         case 1:
2049           /* Castling legal even if K starts on d-file */
2050           v = VariantWildCastle;
2051           break;
2052         case 2:
2053         case 3:
2054         case 4:
2055           /* Castling illegal even if K & R happen to start in
2056              normal positions. */
2057           v = VariantNoCastle;
2058           break;
2059         case 5:
2060         case 7:
2061         case 8:
2062         case 10:
2063         case 11:
2064         case 12:
2065         case 13:
2066         case 14:
2067         case 15:
2068         case 18:
2069         case 19:
2070           /* Castling legal iff K & R start in normal positions */
2071           v = VariantNormal;
2072           break;
2073         case 6:
2074         case 20:
2075         case 21:
2076           /* Special wilds for position setup; unclear what to do here */
2077           v = VariantLoadable;
2078           break;
2079         case 9:
2080           /* Bizarre ICC game */
2081           v = VariantTwoKings;
2082           break;
2083         case 16:
2084           v = VariantKriegspiel;
2085           break;
2086         case 17:
2087           v = VariantLosers;
2088           break;
2089         case 22:
2090           v = VariantFischeRandom;
2091           break;
2092         case 23:
2093           v = VariantCrazyhouse;
2094           break;
2095         case 24:
2096           v = VariantBughouse;
2097           break;
2098         case 25:
2099           v = Variant3Check;
2100           break;
2101         case 26:
2102           /* Not quite the same as FICS suicide! */
2103           v = VariantGiveaway;
2104           break;
2105         case 27:
2106           v = VariantAtomic;
2107           break;
2108         case 28:
2109           v = VariantShatranj;
2110           break;
2111
2112         /* Temporary names for future ICC types.  The name *will* change in
2113            the next xboard/WinBoard release after ICC defines it. */
2114         case 29:
2115           v = Variant29;
2116           break;
2117         case 30:
2118           v = Variant30;
2119           break;
2120         case 31:
2121           v = Variant31;
2122           break;
2123         case 32:
2124           v = Variant32;
2125           break;
2126         case 33:
2127           v = Variant33;
2128           break;
2129         case 34:
2130           v = Variant34;
2131           break;
2132         case 35:
2133           v = Variant35;
2134           break;
2135         case 36:
2136           v = Variant36;
2137           break;
2138         case 37:
2139           v = VariantShogi;
2140           break;
2141         case 38:
2142           v = VariantXiangqi;
2143           break;
2144         case 39:
2145           v = VariantCourier;
2146           break;
2147         case 40:
2148           v = VariantGothic;
2149           break;
2150         case 41:
2151           v = VariantCapablanca;
2152           break;
2153         case 42:
2154           v = VariantKnightmate;
2155           break;
2156         case 43:
2157           v = VariantFairy;
2158           break;
2159         case 44:
2160           v = VariantCylinder;
2161           break;
2162         case 45:
2163           v = VariantFalcon;
2164           break;
2165         case 46:
2166           v = VariantCapaRandom;
2167           break;
2168         case 47:
2169           v = VariantBerolina;
2170           break;
2171         case 48:
2172           v = VariantJanus;
2173           break;
2174         case 49:
2175           v = VariantSuper;
2176           break;
2177         case 50:
2178           v = VariantGreat;
2179           break;
2180         case -1:
2181           /* Found "wild" or "w" in the string but no number;
2182              must assume it's normal chess. */
2183           v = VariantNormal;
2184           break;
2185         default:
2186           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2187           if( (len >= MSG_SIZ) && appData.debugMode )
2188             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2189
2190           DisplayError(buf, 0);
2191           v = VariantUnknown;
2192           break;
2193         }
2194       }
2195     }
2196     if (appData.debugMode) {
2197       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2198               e, wnum, VariantName(v));
2199     }
2200     return v;
2201 }
2202
2203 static int leftover_start = 0, leftover_len = 0;
2204 char star_match[STAR_MATCH_N][MSG_SIZ];
2205
2206 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2207    advance *index beyond it, and set leftover_start to the new value of
2208    *index; else return FALSE.  If pattern contains the character '*', it
2209    matches any sequence of characters not containing '\r', '\n', or the
2210    character following the '*' (if any), and the matched sequence(s) are
2211    copied into star_match.
2212    */
2213 int
2214 looking_at ( char *buf, int *index, char *pattern)
2215 {
2216     char *bufp = &buf[*index], *patternp = pattern;
2217     int star_count = 0;
2218     char *matchp = star_match[0];
2219
2220     for (;;) {
2221         if (*patternp == NULLCHAR) {
2222             *index = leftover_start = bufp - buf;
2223             *matchp = NULLCHAR;
2224             return TRUE;
2225         }
2226         if (*bufp == NULLCHAR) return FALSE;
2227         if (*patternp == '*') {
2228             if (*bufp == *(patternp + 1)) {
2229                 *matchp = NULLCHAR;
2230                 matchp = star_match[++star_count];
2231                 patternp += 2;
2232                 bufp++;
2233                 continue;
2234             } else if (*bufp == '\n' || *bufp == '\r') {
2235                 patternp++;
2236                 if (*patternp == NULLCHAR)
2237                   continue;
2238                 else
2239                   return FALSE;
2240             } else {
2241                 *matchp++ = *bufp++;
2242                 continue;
2243             }
2244         }
2245         if (*patternp != *bufp) return FALSE;
2246         patternp++;
2247         bufp++;
2248     }
2249 }
2250
2251 void
2252 SendToPlayer (char *data, int length)
2253 {
2254     int error, outCount;
2255     outCount = OutputToProcess(NoProc, data, length, &error);
2256     if (outCount < length) {
2257         DisplayFatalError(_("Error writing to display"), error, 1);
2258     }
2259 }
2260
2261 void
2262 PackHolding (char packed[], char *holding)
2263 {
2264     char *p = holding;
2265     char *q = packed;
2266     int runlength = 0;
2267     int curr = 9999;
2268     do {
2269         if (*p == curr) {
2270             runlength++;
2271         } else {
2272             switch (runlength) {
2273               case 0:
2274                 break;
2275               case 1:
2276                 *q++ = curr;
2277                 break;
2278               case 2:
2279                 *q++ = curr;
2280                 *q++ = curr;
2281                 break;
2282               default:
2283                 sprintf(q, "%d", runlength);
2284                 while (*q) q++;
2285                 *q++ = curr;
2286                 break;
2287             }
2288             runlength = 1;
2289             curr = *p;
2290         }
2291     } while (*p++);
2292     *q = NULLCHAR;
2293 }
2294
2295 /* Telnet protocol requests from the front end */
2296 void
2297 TelnetRequest (unsigned char ddww, unsigned char option)
2298 {
2299     unsigned char msg[3];
2300     int outCount, outError;
2301
2302     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2303
2304     if (appData.debugMode) {
2305         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2306         switch (ddww) {
2307           case TN_DO:
2308             ddwwStr = "DO";
2309             break;
2310           case TN_DONT:
2311             ddwwStr = "DONT";
2312             break;
2313           case TN_WILL:
2314             ddwwStr = "WILL";
2315             break;
2316           case TN_WONT:
2317             ddwwStr = "WONT";
2318             break;
2319           default:
2320             ddwwStr = buf1;
2321             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2322             break;
2323         }
2324         switch (option) {
2325           case TN_ECHO:
2326             optionStr = "ECHO";
2327             break;
2328           default:
2329             optionStr = buf2;
2330             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2331             break;
2332         }
2333         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2334     }
2335     msg[0] = TN_IAC;
2336     msg[1] = ddww;
2337     msg[2] = option;
2338     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2339     if (outCount < 3) {
2340         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2341     }
2342 }
2343
2344 void
2345 DoEcho ()
2346 {
2347     if (!appData.icsActive) return;
2348     TelnetRequest(TN_DO, TN_ECHO);
2349 }
2350
2351 void
2352 DontEcho ()
2353 {
2354     if (!appData.icsActive) return;
2355     TelnetRequest(TN_DONT, TN_ECHO);
2356 }
2357
2358 void
2359 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2360 {
2361     /* put the holdings sent to us by the server on the board holdings area */
2362     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2363     char p;
2364     ChessSquare piece;
2365
2366     if(gameInfo.holdingsWidth < 2)  return;
2367     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2368         return; // prevent overwriting by pre-board holdings
2369
2370     if( (int)lowestPiece >= BlackPawn ) {
2371         holdingsColumn = 0;
2372         countsColumn = 1;
2373         holdingsStartRow = BOARD_HEIGHT-1;
2374         direction = -1;
2375     } else {
2376         holdingsColumn = BOARD_WIDTH-1;
2377         countsColumn = BOARD_WIDTH-2;
2378         holdingsStartRow = 0;
2379         direction = 1;
2380     }
2381
2382     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2383         board[i][holdingsColumn] = EmptySquare;
2384         board[i][countsColumn]   = (ChessSquare) 0;
2385     }
2386     while( (p=*holdings++) != NULLCHAR ) {
2387         piece = CharToPiece( ToUpper(p) );
2388         if(piece == EmptySquare) continue;
2389         /*j = (int) piece - (int) WhitePawn;*/
2390         j = PieceToNumber(piece);
2391         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2392         if(j < 0) continue;               /* should not happen */
2393         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2394         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2395         board[holdingsStartRow+j*direction][countsColumn]++;
2396     }
2397 }
2398
2399
2400 void
2401 VariantSwitch (Board board, VariantClass newVariant)
2402 {
2403    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2404    static Board oldBoard;
2405
2406    startedFromPositionFile = FALSE;
2407    if(gameInfo.variant == newVariant) return;
2408
2409    /* [HGM] This routine is called each time an assignment is made to
2410     * gameInfo.variant during a game, to make sure the board sizes
2411     * are set to match the new variant. If that means adding or deleting
2412     * holdings, we shift the playing board accordingly
2413     * This kludge is needed because in ICS observe mode, we get boards
2414     * of an ongoing game without knowing the variant, and learn about the
2415     * latter only later. This can be because of the move list we requested,
2416     * in which case the game history is refilled from the beginning anyway,
2417     * but also when receiving holdings of a crazyhouse game. In the latter
2418     * case we want to add those holdings to the already received position.
2419     */
2420
2421
2422    if (appData.debugMode) {
2423      fprintf(debugFP, "Switch board from %s to %s\n",
2424              VariantName(gameInfo.variant), VariantName(newVariant));
2425      setbuf(debugFP, NULL);
2426    }
2427    shuffleOpenings = 0;       /* [HGM] shuffle */
2428    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2429    switch(newVariant)
2430      {
2431      case VariantShogi:
2432        newWidth = 9;  newHeight = 9;
2433        gameInfo.holdingsSize = 7;
2434      case VariantBughouse:
2435      case VariantCrazyhouse:
2436        newHoldingsWidth = 2; break;
2437      case VariantGreat:
2438        newWidth = 10;
2439      case VariantSuper:
2440        newHoldingsWidth = 2;
2441        gameInfo.holdingsSize = 8;
2442        break;
2443      case VariantGothic:
2444      case VariantCapablanca:
2445      case VariantCapaRandom:
2446        newWidth = 10;
2447      default:
2448        newHoldingsWidth = gameInfo.holdingsSize = 0;
2449      };
2450
2451    if(newWidth  != gameInfo.boardWidth  ||
2452       newHeight != gameInfo.boardHeight ||
2453       newHoldingsWidth != gameInfo.holdingsWidth ) {
2454
2455      /* shift position to new playing area, if needed */
2456      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2457        for(i=0; i<BOARD_HEIGHT; i++)
2458          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2459            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2460              board[i][j];
2461        for(i=0; i<newHeight; i++) {
2462          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2463          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2464        }
2465      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2466        for(i=0; i<BOARD_HEIGHT; i++)
2467          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2468            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2469              board[i][j];
2470      }
2471      board[HOLDINGS_SET] = 0;
2472      gameInfo.boardWidth  = newWidth;
2473      gameInfo.boardHeight = newHeight;
2474      gameInfo.holdingsWidth = newHoldingsWidth;
2475      gameInfo.variant = newVariant;
2476      InitDrawingSizes(-2, 0);
2477    } else gameInfo.variant = newVariant;
2478    CopyBoard(oldBoard, board);   // remember correctly formatted board
2479      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2480    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2481 }
2482
2483 static int loggedOn = FALSE;
2484
2485 /*-- Game start info cache: --*/
2486 int gs_gamenum;
2487 char gs_kind[MSG_SIZ];
2488 static char player1Name[128] = "";
2489 static char player2Name[128] = "";
2490 static char cont_seq[] = "\n\\   ";
2491 static int player1Rating = -1;
2492 static int player2Rating = -1;
2493 /*----------------------------*/
2494
2495 ColorClass curColor = ColorNormal;
2496 int suppressKibitz = 0;
2497
2498 // [HGM] seekgraph
2499 Boolean soughtPending = FALSE;
2500 Boolean seekGraphUp;
2501 #define MAX_SEEK_ADS 200
2502 #define SQUARE 0x80
2503 char *seekAdList[MAX_SEEK_ADS];
2504 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2505 float tcList[MAX_SEEK_ADS];
2506 char colorList[MAX_SEEK_ADS];
2507 int nrOfSeekAds = 0;
2508 int minRating = 1010, maxRating = 2800;
2509 int hMargin = 10, vMargin = 20, h, w;
2510 extern int squareSize, lineGap;
2511
2512 void
2513 PlotSeekAd (int i)
2514 {
2515         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2516         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2517         if(r < minRating+100 && r >=0 ) r = minRating+100;
2518         if(r > maxRating) r = maxRating;
2519         if(tc < 1.f) tc = 1.f;
2520         if(tc > 95.f) tc = 95.f;
2521         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2522         y = ((double)r - minRating)/(maxRating - minRating)
2523             * (h-vMargin-squareSize/8-1) + vMargin;
2524         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2525         if(strstr(seekAdList[i], " u ")) color = 1;
2526         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2527            !strstr(seekAdList[i], "bullet") &&
2528            !strstr(seekAdList[i], "blitz") &&
2529            !strstr(seekAdList[i], "standard") ) color = 2;
2530         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2531         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2532 }
2533
2534 void
2535 PlotSingleSeekAd (int i)
2536 {
2537         PlotSeekAd(i);
2538 }
2539
2540 void
2541 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2542 {
2543         char buf[MSG_SIZ], *ext = "";
2544         VariantClass v = StringToVariant(type);
2545         if(strstr(type, "wild")) {
2546             ext = type + 4; // append wild number
2547             if(v == VariantFischeRandom) type = "chess960"; else
2548             if(v == VariantLoadable) type = "setup"; else
2549             type = VariantName(v);
2550         }
2551         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2552         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2553             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2554             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2555             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2556             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2557             seekNrList[nrOfSeekAds] = nr;
2558             zList[nrOfSeekAds] = 0;
2559             seekAdList[nrOfSeekAds++] = StrSave(buf);
2560             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2561         }
2562 }
2563
2564 void
2565 EraseSeekDot (int i)
2566 {
2567     int x = xList[i], y = yList[i], d=squareSize/4, k;
2568     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2569     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2570     // now replot every dot that overlapped
2571     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2572         int xx = xList[k], yy = yList[k];
2573         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2574             DrawSeekDot(xx, yy, colorList[k]);
2575     }
2576 }
2577
2578 void
2579 RemoveSeekAd (int nr)
2580 {
2581         int i;
2582         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2583             EraseSeekDot(i);
2584             if(seekAdList[i]) free(seekAdList[i]);
2585             seekAdList[i] = seekAdList[--nrOfSeekAds];
2586             seekNrList[i] = seekNrList[nrOfSeekAds];
2587             ratingList[i] = ratingList[nrOfSeekAds];
2588             colorList[i]  = colorList[nrOfSeekAds];
2589             tcList[i] = tcList[nrOfSeekAds];
2590             xList[i]  = xList[nrOfSeekAds];
2591             yList[i]  = yList[nrOfSeekAds];
2592             zList[i]  = zList[nrOfSeekAds];
2593             seekAdList[nrOfSeekAds] = NULL;
2594             break;
2595         }
2596 }
2597
2598 Boolean
2599 MatchSoughtLine (char *line)
2600 {
2601     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2602     int nr, base, inc, u=0; char dummy;
2603
2604     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2605        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2606        (u=1) &&
2607        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2608         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2609         // match: compact and save the line
2610         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2611         return TRUE;
2612     }
2613     return FALSE;
2614 }
2615
2616 int
2617 DrawSeekGraph ()
2618 {
2619     int i;
2620     if(!seekGraphUp) return FALSE;
2621     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2622     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2623
2624     DrawSeekBackground(0, 0, w, h);
2625     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2626     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2627     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2628         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2629         yy = h-1-yy;
2630         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2631         if(i%500 == 0) {
2632             char buf[MSG_SIZ];
2633             snprintf(buf, MSG_SIZ, "%d", i);
2634             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2635         }
2636     }
2637     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2638     for(i=1; i<100; i+=(i<10?1:5)) {
2639         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2640         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2641         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2642             char buf[MSG_SIZ];
2643             snprintf(buf, MSG_SIZ, "%d", i);
2644             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2645         }
2646     }
2647     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2648     return TRUE;
2649 }
2650
2651 int
2652 SeekGraphClick (ClickType click, int x, int y, int moving)
2653 {
2654     static int lastDown = 0, displayed = 0, lastSecond;
2655     if(y < 0) return FALSE;
2656     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2657         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2658         if(!seekGraphUp) return FALSE;
2659         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2660         DrawPosition(TRUE, NULL);
2661         return TRUE;
2662     }
2663     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2664         if(click == Release || moving) return FALSE;
2665         nrOfSeekAds = 0;
2666         soughtPending = TRUE;
2667         SendToICS(ics_prefix);
2668         SendToICS("sought\n"); // should this be "sought all"?
2669     } else { // issue challenge based on clicked ad
2670         int dist = 10000; int i, closest = 0, second = 0;
2671         for(i=0; i<nrOfSeekAds; i++) {
2672             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2673             if(d < dist) { dist = d; closest = i; }
2674             second += (d - zList[i] < 120); // count in-range ads
2675             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2676         }
2677         if(dist < 120) {
2678             char buf[MSG_SIZ];
2679             second = (second > 1);
2680             if(displayed != closest || second != lastSecond) {
2681                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2682                 lastSecond = second; displayed = closest;
2683             }
2684             if(click == Press) {
2685                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2686                 lastDown = closest;
2687                 return TRUE;
2688             } // on press 'hit', only show info
2689             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2690             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2691             SendToICS(ics_prefix);
2692             SendToICS(buf);
2693             return TRUE; // let incoming board of started game pop down the graph
2694         } else if(click == Release) { // release 'miss' is ignored
2695             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2696             if(moving == 2) { // right up-click
2697                 nrOfSeekAds = 0; // refresh graph
2698                 soughtPending = TRUE;
2699                 SendToICS(ics_prefix);
2700                 SendToICS("sought\n"); // should this be "sought all"?
2701             }
2702             return TRUE;
2703         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2704         // press miss or release hit 'pop down' seek graph
2705         seekGraphUp = FALSE;
2706         DrawPosition(TRUE, NULL);
2707     }
2708     return TRUE;
2709 }
2710
2711 void
2712 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2713 {
2714 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2715 #define STARTED_NONE 0
2716 #define STARTED_MOVES 1
2717 #define STARTED_BOARD 2
2718 #define STARTED_OBSERVE 3
2719 #define STARTED_HOLDINGS 4
2720 #define STARTED_CHATTER 5
2721 #define STARTED_COMMENT 6
2722 #define STARTED_MOVES_NOHIDE 7
2723
2724     static int started = STARTED_NONE;
2725     static char parse[20000];
2726     static int parse_pos = 0;
2727     static char buf[BUF_SIZE + 1];
2728     static int firstTime = TRUE, intfSet = FALSE;
2729     static ColorClass prevColor = ColorNormal;
2730     static int savingComment = FALSE;
2731     static int cmatch = 0; // continuation sequence match
2732     char *bp;
2733     char str[MSG_SIZ];
2734     int i, oldi;
2735     int buf_len;
2736     int next_out;
2737     int tkind;
2738     int backup;    /* [DM] For zippy color lines */
2739     char *p;
2740     char talker[MSG_SIZ]; // [HGM] chat
2741     int channel;
2742
2743     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2744
2745     if (appData.debugMode) {
2746       if (!error) {
2747         fprintf(debugFP, "<ICS: ");
2748         show_bytes(debugFP, data, count);
2749         fprintf(debugFP, "\n");
2750       }
2751     }
2752
2753     if (appData.debugMode) { int f = forwardMostMove;
2754         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2755                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2756                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2757     }
2758     if (count > 0) {
2759         /* If last read ended with a partial line that we couldn't parse,
2760            prepend it to the new read and try again. */
2761         if (leftover_len > 0) {
2762             for (i=0; i<leftover_len; i++)
2763               buf[i] = buf[leftover_start + i];
2764         }
2765
2766     /* copy new characters into the buffer */
2767     bp = buf + leftover_len;
2768     buf_len=leftover_len;
2769     for (i=0; i<count; i++)
2770     {
2771         // ignore these
2772         if (data[i] == '\r')
2773             continue;
2774
2775         // join lines split by ICS?
2776         if (!appData.noJoin)
2777         {
2778             /*
2779                 Joining just consists of finding matches against the
2780                 continuation sequence, and discarding that sequence
2781                 if found instead of copying it.  So, until a match
2782                 fails, there's nothing to do since it might be the
2783                 complete sequence, and thus, something we don't want
2784                 copied.
2785             */
2786             if (data[i] == cont_seq[cmatch])
2787             {
2788                 cmatch++;
2789                 if (cmatch == strlen(cont_seq))
2790                 {
2791                     cmatch = 0; // complete match.  just reset the counter
2792
2793                     /*
2794                         it's possible for the ICS to not include the space
2795                         at the end of the last word, making our [correct]
2796                         join operation fuse two separate words.  the server
2797                         does this when the space occurs at the width setting.
2798                     */
2799                     if (!buf_len || buf[buf_len-1] != ' ')
2800                     {
2801                         *bp++ = ' ';
2802                         buf_len++;
2803                     }
2804                 }
2805                 continue;
2806             }
2807             else if (cmatch)
2808             {
2809                 /*
2810                     match failed, so we have to copy what matched before
2811                     falling through and copying this character.  In reality,
2812                     this will only ever be just the newline character, but
2813                     it doesn't hurt to be precise.
2814                 */
2815                 strncpy(bp, cont_seq, cmatch);
2816                 bp += cmatch;
2817                 buf_len += cmatch;
2818                 cmatch = 0;
2819             }
2820         }
2821
2822         // copy this char
2823         *bp++ = data[i];
2824         buf_len++;
2825     }
2826
2827         buf[buf_len] = NULLCHAR;
2828 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2829         next_out = 0;
2830         leftover_start = 0;
2831
2832         i = 0;
2833         while (i < buf_len) {
2834             /* Deal with part of the TELNET option negotiation
2835                protocol.  We refuse to do anything beyond the
2836                defaults, except that we allow the WILL ECHO option,
2837                which ICS uses to turn off password echoing when we are
2838                directly connected to it.  We reject this option
2839                if localLineEditing mode is on (always on in xboard)
2840                and we are talking to port 23, which might be a real
2841                telnet server that will try to keep WILL ECHO on permanently.
2842              */
2843             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2844                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2845                 unsigned char option;
2846                 oldi = i;
2847                 switch ((unsigned char) buf[++i]) {
2848                   case TN_WILL:
2849                     if (appData.debugMode)
2850                       fprintf(debugFP, "\n<WILL ");
2851                     switch (option = (unsigned char) buf[++i]) {
2852                       case TN_ECHO:
2853                         if (appData.debugMode)
2854                           fprintf(debugFP, "ECHO ");
2855                         /* Reply only if this is a change, according
2856                            to the protocol rules. */
2857                         if (remoteEchoOption) break;
2858                         if (appData.localLineEditing &&
2859                             atoi(appData.icsPort) == TN_PORT) {
2860                             TelnetRequest(TN_DONT, TN_ECHO);
2861                         } else {
2862                             EchoOff();
2863                             TelnetRequest(TN_DO, TN_ECHO);
2864                             remoteEchoOption = TRUE;
2865                         }
2866                         break;
2867                       default:
2868                         if (appData.debugMode)
2869                           fprintf(debugFP, "%d ", option);
2870                         /* Whatever this is, we don't want it. */
2871                         TelnetRequest(TN_DONT, option);
2872                         break;
2873                     }
2874                     break;
2875                   case TN_WONT:
2876                     if (appData.debugMode)
2877                       fprintf(debugFP, "\n<WONT ");
2878                     switch (option = (unsigned char) buf[++i]) {
2879                       case TN_ECHO:
2880                         if (appData.debugMode)
2881                           fprintf(debugFP, "ECHO ");
2882                         /* Reply only if this is a change, according
2883                            to the protocol rules. */
2884                         if (!remoteEchoOption) break;
2885                         EchoOn();
2886                         TelnetRequest(TN_DONT, TN_ECHO);
2887                         remoteEchoOption = FALSE;
2888                         break;
2889                       default:
2890                         if (appData.debugMode)
2891                           fprintf(debugFP, "%d ", (unsigned char) option);
2892                         /* Whatever this is, it must already be turned
2893                            off, because we never agree to turn on
2894                            anything non-default, so according to the
2895                            protocol rules, we don't reply. */
2896                         break;
2897                     }
2898                     break;
2899                   case TN_DO:
2900                     if (appData.debugMode)
2901                       fprintf(debugFP, "\n<DO ");
2902                     switch (option = (unsigned char) buf[++i]) {
2903                       default:
2904                         /* Whatever this is, we refuse to do it. */
2905                         if (appData.debugMode)
2906                           fprintf(debugFP, "%d ", option);
2907                         TelnetRequest(TN_WONT, option);
2908                         break;
2909                     }
2910                     break;
2911                   case TN_DONT:
2912                     if (appData.debugMode)
2913                       fprintf(debugFP, "\n<DONT ");
2914                     switch (option = (unsigned char) buf[++i]) {
2915                       default:
2916                         if (appData.debugMode)
2917                           fprintf(debugFP, "%d ", option);
2918                         /* Whatever this is, we are already not doing
2919                            it, because we never agree to do anything
2920                            non-default, so according to the protocol
2921                            rules, we don't reply. */
2922                         break;
2923                     }
2924                     break;
2925                   case TN_IAC:
2926                     if (appData.debugMode)
2927                       fprintf(debugFP, "\n<IAC ");
2928                     /* Doubled IAC; pass it through */
2929                     i--;
2930                     break;
2931                   default:
2932                     if (appData.debugMode)
2933                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2934                     /* Drop all other telnet commands on the floor */
2935                     break;
2936                 }
2937                 if (oldi > next_out)
2938                   SendToPlayer(&buf[next_out], oldi - next_out);
2939                 if (++i > next_out)
2940                   next_out = i;
2941                 continue;
2942             }
2943
2944             /* OK, this at least will *usually* work */
2945             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2946                 loggedOn = TRUE;
2947             }
2948
2949             if (loggedOn && !intfSet) {
2950                 if (ics_type == ICS_ICC) {
2951                   snprintf(str, MSG_SIZ,
2952                           "/set-quietly interface %s\n/set-quietly style 12\n",
2953                           programVersion);
2954                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2955                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2956                 } else if (ics_type == ICS_CHESSNET) {
2957                   snprintf(str, MSG_SIZ, "/style 12\n");
2958                 } else {
2959                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2960                   strcat(str, programVersion);
2961                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2962                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2963                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2964 #ifdef WIN32
2965                   strcat(str, "$iset nohighlight 1\n");
2966 #endif
2967                   strcat(str, "$iset lock 1\n$style 12\n");
2968                 }
2969                 SendToICS(str);
2970                 NotifyFrontendLogin();
2971                 intfSet = TRUE;
2972             }
2973
2974             if (started == STARTED_COMMENT) {
2975                 /* Accumulate characters in comment */
2976                 parse[parse_pos++] = buf[i];
2977                 if (buf[i] == '\n') {
2978                     parse[parse_pos] = NULLCHAR;
2979                     if(chattingPartner>=0) {
2980                         char mess[MSG_SIZ];
2981                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2982                         OutputChatMessage(chattingPartner, mess);
2983                         chattingPartner = -1;
2984                         next_out = i+1; // [HGM] suppress printing in ICS window
2985                     } else
2986                     if(!suppressKibitz) // [HGM] kibitz
2987                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2988                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2989                         int nrDigit = 0, nrAlph = 0, j;
2990                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2991                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2992                         parse[parse_pos] = NULLCHAR;
2993                         // try to be smart: if it does not look like search info, it should go to
2994                         // ICS interaction window after all, not to engine-output window.
2995                         for(j=0; j<parse_pos; j++) { // count letters and digits
2996                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2997                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2998                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2999                         }
3000                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3001                             int depth=0; float score;
3002                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3003                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3004                                 pvInfoList[forwardMostMove-1].depth = depth;
3005                                 pvInfoList[forwardMostMove-1].score = 100*score;
3006                             }
3007                             OutputKibitz(suppressKibitz, parse);
3008                         } else {
3009                             char tmp[MSG_SIZ];
3010                             if(gameMode == IcsObserving) // restore original ICS messages
3011                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3012                             else
3013                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3014                             SendToPlayer(tmp, strlen(tmp));
3015                         }
3016                         next_out = i+1; // [HGM] suppress printing in ICS window
3017                     }
3018                     started = STARTED_NONE;
3019                 } else {
3020                     /* Don't match patterns against characters in comment */
3021                     i++;
3022                     continue;
3023                 }
3024             }
3025             if (started == STARTED_CHATTER) {
3026                 if (buf[i] != '\n') {
3027                     /* Don't match patterns against characters in chatter */
3028                     i++;
3029                     continue;
3030                 }
3031                 started = STARTED_NONE;
3032                 if(suppressKibitz) next_out = i+1;
3033             }
3034
3035             /* Kludge to deal with rcmd protocol */
3036             if (firstTime && looking_at(buf, &i, "\001*")) {
3037                 DisplayFatalError(&buf[1], 0, 1);
3038                 continue;
3039             } else {
3040                 firstTime = FALSE;
3041             }
3042
3043             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3044                 ics_type = ICS_ICC;
3045                 ics_prefix = "/";
3046                 if (appData.debugMode)
3047                   fprintf(debugFP, "ics_type %d\n", ics_type);
3048                 continue;
3049             }
3050             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3051                 ics_type = ICS_FICS;
3052                 ics_prefix = "$";
3053                 if (appData.debugMode)
3054                   fprintf(debugFP, "ics_type %d\n", ics_type);
3055                 continue;
3056             }
3057             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3058                 ics_type = ICS_CHESSNET;
3059                 ics_prefix = "/";
3060                 if (appData.debugMode)
3061                   fprintf(debugFP, "ics_type %d\n", ics_type);
3062                 continue;
3063             }
3064
3065             if (!loggedOn &&
3066                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3067                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3068                  looking_at(buf, &i, "will be \"*\""))) {
3069               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3070               continue;
3071             }
3072
3073             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3074               char buf[MSG_SIZ];
3075               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3076               DisplayIcsInteractionTitle(buf);
3077               have_set_title = TRUE;
3078             }
3079
3080             /* skip finger notes */
3081             if (started == STARTED_NONE &&
3082                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3083                  (buf[i] == '1' && buf[i+1] == '0')) &&
3084                 buf[i+2] == ':' && buf[i+3] == ' ') {
3085               started = STARTED_CHATTER;
3086               i += 3;
3087               continue;
3088             }
3089
3090             oldi = i;
3091             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3092             if(appData.seekGraph) {
3093                 if(soughtPending && MatchSoughtLine(buf+i)) {
3094                     i = strstr(buf+i, "rated") - buf;
3095                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3096                     next_out = leftover_start = i;
3097                     started = STARTED_CHATTER;
3098                     suppressKibitz = TRUE;
3099                     continue;
3100                 }
3101                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3102                         && looking_at(buf, &i, "* ads displayed")) {
3103                     soughtPending = FALSE;
3104                     seekGraphUp = TRUE;
3105                     DrawSeekGraph();
3106                     continue;
3107                 }
3108                 if(appData.autoRefresh) {
3109                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3110                         int s = (ics_type == ICS_ICC); // ICC format differs
3111                         if(seekGraphUp)
3112                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3113                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3114                         looking_at(buf, &i, "*% "); // eat prompt
3115                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3116                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117                         next_out = i; // suppress
3118                         continue;
3119                     }
3120                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3121                         char *p = star_match[0];
3122                         while(*p) {
3123                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3124                             while(*p && *p++ != ' '); // next
3125                         }
3126                         looking_at(buf, &i, "*% "); // eat prompt
3127                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3128                         next_out = i;
3129                         continue;
3130                     }
3131                 }
3132             }
3133
3134             /* skip formula vars */
3135             if (started == STARTED_NONE &&
3136                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3137               started = STARTED_CHATTER;
3138               i += 3;
3139               continue;
3140             }
3141
3142             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3143             if (appData.autoKibitz && started == STARTED_NONE &&
3144                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3145                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3146                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3147                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3148                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3149                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3150                         suppressKibitz = TRUE;
3151                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3152                         next_out = i;
3153                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3154                                 && (gameMode == IcsPlayingWhite)) ||
3155                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3156                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3157                             started = STARTED_CHATTER; // own kibitz we simply discard
3158                         else {
3159                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3160                             parse_pos = 0; parse[0] = NULLCHAR;
3161                             savingComment = TRUE;
3162                             suppressKibitz = gameMode != IcsObserving ? 2 :
3163                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3164                         }
3165                         continue;
3166                 } else
3167                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3168                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3169                          && atoi(star_match[0])) {
3170                     // suppress the acknowledgements of our own autoKibitz
3171                     char *p;
3172                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3173                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3174                     SendToPlayer(star_match[0], strlen(star_match[0]));
3175                     if(looking_at(buf, &i, "*% ")) // eat prompt
3176                         suppressKibitz = FALSE;
3177                     next_out = i;
3178                     continue;
3179                 }
3180             } // [HGM] kibitz: end of patch
3181
3182             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3183
3184             // [HGM] chat: intercept tells by users for which we have an open chat window
3185             channel = -1;
3186             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3187                                            looking_at(buf, &i, "* whispers:") ||
3188                                            looking_at(buf, &i, "* kibitzes:") ||
3189                                            looking_at(buf, &i, "* shouts:") ||
3190                                            looking_at(buf, &i, "* c-shouts:") ||
3191                                            looking_at(buf, &i, "--> * ") ||
3192                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3193                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3194                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3195                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3196                 int p;
3197                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3198                 chattingPartner = -1;
3199
3200                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3201                 for(p=0; p<MAX_CHAT; p++) {
3202                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3203                     talker[0] = '['; strcat(talker, "] ");
3204                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3205                     chattingPartner = p; break;
3206                     }
3207                 } else
3208                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3209                 for(p=0; p<MAX_CHAT; p++) {
3210                     if(!strcmp("kibitzes", chatPartner[p])) {
3211                         talker[0] = '['; strcat(talker, "] ");
3212                         chattingPartner = p; break;
3213                     }
3214                 } else
3215                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3216                 for(p=0; p<MAX_CHAT; p++) {
3217                     if(!strcmp("whispers", chatPartner[p])) {
3218                         talker[0] = '['; strcat(talker, "] ");
3219                         chattingPartner = p; break;
3220                     }
3221                 } else
3222                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3223                   if(buf[i-8] == '-' && buf[i-3] == 't')
3224                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3225                     if(!strcmp("c-shouts", chatPartner[p])) {
3226                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3227                         chattingPartner = p; break;
3228                     }
3229                   }
3230                   if(chattingPartner < 0)
3231                   for(p=0; p<MAX_CHAT; p++) {
3232                     if(!strcmp("shouts", chatPartner[p])) {
3233                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3234                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3235                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3236                         chattingPartner = p; break;
3237                     }
3238                   }
3239                 }
3240                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3241                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3242                     talker[0] = 0; Colorize(ColorTell, FALSE);
3243                     chattingPartner = p; break;
3244                 }
3245                 if(chattingPartner<0) i = oldi; else {
3246                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3247                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3248                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3249                     started = STARTED_COMMENT;
3250                     parse_pos = 0; parse[0] = NULLCHAR;
3251                     savingComment = 3 + chattingPartner; // counts as TRUE
3252                     suppressKibitz = TRUE;
3253                     continue;
3254                 }
3255             } // [HGM] chat: end of patch
3256
3257           backup = i;
3258             if (appData.zippyTalk || appData.zippyPlay) {
3259                 /* [DM] Backup address for color zippy lines */
3260 #if ZIPPY
3261                if (loggedOn == TRUE)
3262                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3263                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3264 #endif
3265             } // [DM] 'else { ' deleted
3266                 if (
3267                     /* Regular tells and says */
3268                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3269                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3270                     looking_at(buf, &i, "* says: ") ||
3271                     /* Don't color "message" or "messages" output */
3272                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3273                     looking_at(buf, &i, "*. * at *:*: ") ||
3274                     looking_at(buf, &i, "--* (*:*): ") ||
3275                     /* Message notifications (same color as tells) */
3276                     looking_at(buf, &i, "* has left a message ") ||
3277                     looking_at(buf, &i, "* just sent you a message:\n") ||
3278                     /* Whispers and kibitzes */
3279                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3280                     looking_at(buf, &i, "* kibitzes: ") ||
3281                     /* Channel tells */
3282                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3283
3284                   if (tkind == 1 && strchr(star_match[0], ':')) {
3285                       /* Avoid "tells you:" spoofs in channels */
3286                      tkind = 3;
3287                   }
3288                   if (star_match[0][0] == NULLCHAR ||
3289                       strchr(star_match[0], ' ') ||
3290                       (tkind == 3 && strchr(star_match[1], ' '))) {
3291                     /* Reject bogus matches */
3292                     i = oldi;
3293                   } else {
3294                     if (appData.colorize) {
3295                       if (oldi > next_out) {
3296                         SendToPlayer(&buf[next_out], oldi - next_out);
3297                         next_out = oldi;
3298                       }
3299                       switch (tkind) {
3300                       case 1:
3301                         Colorize(ColorTell, FALSE);
3302                         curColor = ColorTell;
3303                         break;
3304                       case 2:
3305                         Colorize(ColorKibitz, FALSE);
3306                         curColor = ColorKibitz;
3307                         break;
3308                       case 3:
3309                         p = strrchr(star_match[1], '(');
3310                         if (p == NULL) {
3311                           p = star_match[1];
3312                         } else {
3313                           p++;
3314                         }
3315                         if (atoi(p) == 1) {
3316                           Colorize(ColorChannel1, FALSE);
3317                           curColor = ColorChannel1;
3318                         } else {
3319                           Colorize(ColorChannel, FALSE);
3320                           curColor = ColorChannel;
3321                         }
3322                         break;
3323                       case 5:
3324                         curColor = ColorNormal;
3325                         break;
3326                       }
3327                     }
3328                     if (started == STARTED_NONE && appData.autoComment &&
3329                         (gameMode == IcsObserving ||
3330                          gameMode == IcsPlayingWhite ||
3331                          gameMode == IcsPlayingBlack)) {
3332                       parse_pos = i - oldi;
3333                       memcpy(parse, &buf[oldi], parse_pos);
3334                       parse[parse_pos] = NULLCHAR;
3335                       started = STARTED_COMMENT;
3336                       savingComment = TRUE;
3337                     } else {
3338                       started = STARTED_CHATTER;
3339                       savingComment = FALSE;
3340                     }
3341                     loggedOn = TRUE;
3342                     continue;
3343                   }
3344                 }
3345
3346                 if (looking_at(buf, &i, "* s-shouts: ") ||
3347                     looking_at(buf, &i, "* c-shouts: ")) {
3348                     if (appData.colorize) {
3349                         if (oldi > next_out) {
3350                             SendToPlayer(&buf[next_out], oldi - next_out);
3351                             next_out = oldi;
3352                         }
3353                         Colorize(ColorSShout, FALSE);
3354                         curColor = ColorSShout;
3355                     }
3356                     loggedOn = TRUE;
3357                     started = STARTED_CHATTER;
3358                     continue;
3359                 }
3360
3361                 if (looking_at(buf, &i, "--->")) {
3362                     loggedOn = TRUE;
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "* shouts: ") ||
3367                     looking_at(buf, &i, "--> ")) {
3368                     if (appData.colorize) {
3369                         if (oldi > next_out) {
3370                             SendToPlayer(&buf[next_out], oldi - next_out);
3371                             next_out = oldi;
3372                         }
3373                         Colorize(ColorShout, FALSE);
3374                         curColor = ColorShout;
3375                     }
3376                     loggedOn = TRUE;
3377                     started = STARTED_CHATTER;
3378                     continue;
3379                 }
3380
3381                 if (looking_at( buf, &i, "Challenge:")) {
3382                     if (appData.colorize) {
3383                         if (oldi > next_out) {
3384                             SendToPlayer(&buf[next_out], oldi - next_out);
3385                             next_out = oldi;
3386                         }
3387                         Colorize(ColorChallenge, FALSE);
3388                         curColor = ColorChallenge;
3389                     }
3390                     loggedOn = TRUE;
3391                     continue;
3392                 }
3393
3394                 if (looking_at(buf, &i, "* offers you") ||
3395                     looking_at(buf, &i, "* offers to be") ||
3396                     looking_at(buf, &i, "* would like to") ||
3397                     looking_at(buf, &i, "* requests to") ||
3398                     looking_at(buf, &i, "Your opponent offers") ||
3399                     looking_at(buf, &i, "Your opponent requests")) {
3400
3401                     if (appData.colorize) {
3402                         if (oldi > next_out) {
3403                             SendToPlayer(&buf[next_out], oldi - next_out);
3404                             next_out = oldi;
3405                         }
3406                         Colorize(ColorRequest, FALSE);
3407                         curColor = ColorRequest;
3408                     }
3409                     continue;
3410                 }
3411
3412                 if (looking_at(buf, &i, "* (*) seeking")) {
3413                     if (appData.colorize) {
3414                         if (oldi > next_out) {
3415                             SendToPlayer(&buf[next_out], oldi - next_out);
3416                             next_out = oldi;
3417                         }
3418                         Colorize(ColorSeek, FALSE);
3419                         curColor = ColorSeek;
3420                     }
3421                     continue;
3422             }
3423
3424           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3425
3426             if (looking_at(buf, &i, "\\   ")) {
3427                 if (prevColor != ColorNormal) {
3428                     if (oldi > next_out) {
3429                         SendToPlayer(&buf[next_out], oldi - next_out);
3430                         next_out = oldi;
3431                     }
3432                     Colorize(prevColor, TRUE);
3433                     curColor = prevColor;
3434                 }
3435                 if (savingComment) {
3436                     parse_pos = i - oldi;
3437                     memcpy(parse, &buf[oldi], parse_pos);
3438                     parse[parse_pos] = NULLCHAR;
3439                     started = STARTED_COMMENT;
3440                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3441                         chattingPartner = savingComment - 3; // kludge to remember the box
3442                 } else {
3443                     started = STARTED_CHATTER;
3444                 }
3445                 continue;
3446             }
3447
3448             if (looking_at(buf, &i, "Black Strength :") ||
3449                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3450                 looking_at(buf, &i, "<10>") ||
3451                 looking_at(buf, &i, "#@#")) {
3452                 /* Wrong board style */
3453                 loggedOn = TRUE;
3454                 SendToICS(ics_prefix);
3455                 SendToICS("set style 12\n");
3456                 SendToICS(ics_prefix);
3457                 SendToICS("refresh\n");
3458                 continue;
3459             }
3460
3461             if (looking_at(buf, &i, "login:")) {
3462               if (!have_sent_ICS_logon) {
3463                 if(ICSInitScript())
3464                   have_sent_ICS_logon = 1;
3465                 else // no init script was found
3466                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3467               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3468                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3469               }
3470                 continue;
3471             }
3472
3473             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3474                 (looking_at(buf, &i, "\n<12> ") ||
3475                  looking_at(buf, &i, "<12> "))) {
3476                 loggedOn = TRUE;
3477                 if (oldi > next_out) {
3478                     SendToPlayer(&buf[next_out], oldi - next_out);
3479                 }
3480                 next_out = i;
3481                 started = STARTED_BOARD;
3482                 parse_pos = 0;
3483                 continue;
3484             }
3485
3486             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3487                 looking_at(buf, &i, "<b1> ")) {
3488                 if (oldi > next_out) {
3489                     SendToPlayer(&buf[next_out], oldi - next_out);
3490                 }
3491                 next_out = i;
3492                 started = STARTED_HOLDINGS;
3493                 parse_pos = 0;
3494                 continue;
3495             }
3496
3497             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3498                 loggedOn = TRUE;
3499                 /* Header for a move list -- first line */
3500
3501                 switch (ics_getting_history) {
3502                   case H_FALSE:
3503                     switch (gameMode) {
3504                       case IcsIdle:
3505                       case BeginningOfGame:
3506                         /* User typed "moves" or "oldmoves" while we
3507                            were idle.  Pretend we asked for these
3508                            moves and soak them up so user can step
3509                            through them and/or save them.
3510                            */
3511                         Reset(FALSE, TRUE);
3512                         gameMode = IcsObserving;
3513                         ModeHighlight();
3514                         ics_gamenum = -1;
3515                         ics_getting_history = H_GOT_UNREQ_HEADER;
3516                         break;
3517                       case EditGame: /*?*/
3518                       case EditPosition: /*?*/
3519                         /* Should above feature work in these modes too? */
3520                         /* For now it doesn't */
3521                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3522                         break;
3523                       default:
3524                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3525                         break;
3526                     }
3527                     break;
3528                   case H_REQUESTED:
3529                     /* Is this the right one? */
3530                     if (gameInfo.white && gameInfo.black &&
3531                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3532                         strcmp(gameInfo.black, star_match[2]) == 0) {
3533                         /* All is well */
3534                         ics_getting_history = H_GOT_REQ_HEADER;
3535                     }
3536                     break;
3537                   case H_GOT_REQ_HEADER:
3538                   case H_GOT_UNREQ_HEADER:
3539                   case H_GOT_UNWANTED_HEADER:
3540                   case H_GETTING_MOVES:
3541                     /* Should not happen */
3542                     DisplayError(_("Error gathering move list: two headers"), 0);
3543                     ics_getting_history = H_FALSE;
3544                     break;
3545                 }
3546
3547                 /* Save player ratings into gameInfo if needed */
3548                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3549                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3550                     (gameInfo.whiteRating == -1 ||
3551                      gameInfo.blackRating == -1)) {
3552
3553                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3554                     gameInfo.blackRating = string_to_rating(star_match[3]);
3555                     if (appData.debugMode)
3556                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3557                               gameInfo.whiteRating, gameInfo.blackRating);
3558                 }
3559                 continue;
3560             }
3561
3562             if (looking_at(buf, &i,
3563               "* * match, initial time: * minute*, increment: * second")) {
3564                 /* Header for a move list -- second line */
3565                 /* Initial board will follow if this is a wild game */
3566                 if (gameInfo.event != NULL) free(gameInfo.event);
3567                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3568                 gameInfo.event = StrSave(str);
3569                 /* [HGM] we switched variant. Translate boards if needed. */
3570                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3571                 continue;
3572             }
3573
3574             if (looking_at(buf, &i, "Move  ")) {
3575                 /* Beginning of a move list */
3576                 switch (ics_getting_history) {
3577                   case H_FALSE:
3578                     /* Normally should not happen */
3579                     /* Maybe user hit reset while we were parsing */
3580                     break;
3581                   case H_REQUESTED:
3582                     /* Happens if we are ignoring a move list that is not
3583                      * the one we just requested.  Common if the user
3584                      * tries to observe two games without turning off
3585                      * getMoveList */
3586                     break;
3587                   case H_GETTING_MOVES:
3588                     /* Should not happen */
3589                     DisplayError(_("Error gathering move list: nested"), 0);
3590                     ics_getting_history = H_FALSE;
3591                     break;
3592                   case H_GOT_REQ_HEADER:
3593                     ics_getting_history = H_GETTING_MOVES;
3594                     started = STARTED_MOVES;
3595                     parse_pos = 0;
3596                     if (oldi > next_out) {
3597                         SendToPlayer(&buf[next_out], oldi - next_out);
3598                     }
3599                     break;
3600                   case H_GOT_UNREQ_HEADER:
3601                     ics_getting_history = H_GETTING_MOVES;
3602                     started = STARTED_MOVES_NOHIDE;
3603                     parse_pos = 0;
3604                     break;
3605                   case H_GOT_UNWANTED_HEADER:
3606                     ics_getting_history = H_FALSE;
3607                     break;
3608                 }
3609                 continue;
3610             }
3611
3612             if (looking_at(buf, &i, "% ") ||
3613                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3614                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3615                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3616                     soughtPending = FALSE;
3617                     seekGraphUp = TRUE;
3618                     DrawSeekGraph();
3619                 }
3620                 if(suppressKibitz) next_out = i;
3621                 savingComment = FALSE;
3622                 suppressKibitz = 0;
3623                 switch (started) {
3624                   case STARTED_MOVES:
3625                   case STARTED_MOVES_NOHIDE:
3626                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3627                     parse[parse_pos + i - oldi] = NULLCHAR;
3628                     ParseGameHistory(parse);
3629 #if ZIPPY
3630                     if (appData.zippyPlay && first.initDone) {
3631                         FeedMovesToProgram(&first, forwardMostMove);
3632                         if (gameMode == IcsPlayingWhite) {
3633                             if (WhiteOnMove(forwardMostMove)) {
3634                                 if (first.sendTime) {
3635                                   if (first.useColors) {
3636                                     SendToProgram("black\n", &first);
3637                                   }
3638                                   SendTimeRemaining(&first, TRUE);
3639                                 }
3640                                 if (first.useColors) {
3641                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3642                                 }
3643                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3644                                 first.maybeThinking = TRUE;
3645                             } else {
3646                                 if (first.usePlayother) {
3647                                   if (first.sendTime) {
3648                                     SendTimeRemaining(&first, TRUE);
3649                                   }
3650                                   SendToProgram("playother\n", &first);
3651                                   firstMove = FALSE;
3652                                 } else {
3653                                   firstMove = TRUE;
3654                                 }
3655                             }
3656                         } else if (gameMode == IcsPlayingBlack) {
3657                             if (!WhiteOnMove(forwardMostMove)) {
3658                                 if (first.sendTime) {
3659                                   if (first.useColors) {
3660                                     SendToProgram("white\n", &first);
3661                                   }
3662                                   SendTimeRemaining(&first, FALSE);
3663                                 }
3664                                 if (first.useColors) {
3665                                   SendToProgram("black\n", &first);
3666                                 }
3667                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3668                                 first.maybeThinking = TRUE;
3669                             } else {
3670                                 if (first.usePlayother) {
3671                                   if (first.sendTime) {
3672                                     SendTimeRemaining(&first, FALSE);
3673                                   }
3674                                   SendToProgram("playother\n", &first);
3675                                   firstMove = FALSE;
3676                                 } else {
3677                                   firstMove = TRUE;
3678                                 }
3679                             }
3680                         }
3681                     }
3682 #endif
3683                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3684                         /* Moves came from oldmoves or moves command
3685                            while we weren't doing anything else.
3686                            */
3687                         currentMove = forwardMostMove;
3688                         ClearHighlights();/*!!could figure this out*/
3689                         flipView = appData.flipView;
3690                         DrawPosition(TRUE, boards[currentMove]);
3691                         DisplayBothClocks();
3692                         snprintf(str, MSG_SIZ, "%s %s %s",
3693                                 gameInfo.white, _("vs."),  gameInfo.black);
3694                         DisplayTitle(str);
3695                         gameMode = IcsIdle;
3696                     } else {
3697                         /* Moves were history of an active game */
3698                         if (gameInfo.resultDetails != NULL) {
3699                             free(gameInfo.resultDetails);
3700                             gameInfo.resultDetails = NULL;
3701                         }
3702                     }
3703                     HistorySet(parseList, backwardMostMove,
3704                                forwardMostMove, currentMove-1);
3705                     DisplayMove(currentMove - 1);
3706                     if (started == STARTED_MOVES) next_out = i;
3707                     started = STARTED_NONE;
3708                     ics_getting_history = H_FALSE;
3709                     break;
3710
3711                   case STARTED_OBSERVE:
3712                     started = STARTED_NONE;
3713                     SendToICS(ics_prefix);
3714                     SendToICS("refresh\n");
3715                     break;
3716
3717                   default:
3718                     break;
3719                 }
3720                 if(bookHit) { // [HGM] book: simulate book reply
3721                     static char bookMove[MSG_SIZ]; // a bit generous?
3722
3723                     programStats.nodes = programStats.depth = programStats.time =
3724                     programStats.score = programStats.got_only_move = 0;
3725                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3726
3727                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3728                     strcat(bookMove, bookHit);
3729                     HandleMachineMove(bookMove, &first);
3730                 }
3731                 continue;
3732             }
3733
3734             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3735                  started == STARTED_HOLDINGS ||
3736                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3737                 /* Accumulate characters in move list or board */
3738                 parse[parse_pos++] = buf[i];
3739             }
3740
3741             /* Start of game messages.  Mostly we detect start of game
3742                when the first board image arrives.  On some versions
3743                of the ICS, though, we need to do a "refresh" after starting
3744                to observe in order to get the current board right away. */
3745             if (looking_at(buf, &i, "Adding game * to observation list")) {
3746                 started = STARTED_OBSERVE;
3747                 continue;
3748             }
3749
3750             /* Handle auto-observe */
3751             if (appData.autoObserve &&
3752                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3753                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3754                 char *player;
3755                 /* Choose the player that was highlighted, if any. */
3756                 if (star_match[0][0] == '\033' ||
3757                     star_match[1][0] != '\033') {
3758                     player = star_match[0];
3759                 } else {
3760                     player = star_match[2];
3761                 }
3762                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3763                         ics_prefix, StripHighlightAndTitle(player));
3764                 SendToICS(str);
3765
3766                 /* Save ratings from notify string */
3767                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3768                 player1Rating = string_to_rating(star_match[1]);
3769                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3770                 player2Rating = string_to_rating(star_match[3]);
3771
3772                 if (appData.debugMode)
3773                   fprintf(debugFP,
3774                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3775                           player1Name, player1Rating,
3776                           player2Name, player2Rating);
3777
3778                 continue;
3779             }
3780
3781             /* Deal with automatic examine mode after a game,
3782                and with IcsObserving -> IcsExamining transition */
3783             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3784                 looking_at(buf, &i, "has made you an examiner of game *")) {
3785
3786                 int gamenum = atoi(star_match[0]);
3787                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3788                     gamenum == ics_gamenum) {
3789                     /* We were already playing or observing this game;
3790                        no need to refetch history */
3791                     gameMode = IcsExamining;
3792                     if (pausing) {
3793                         pauseExamForwardMostMove = forwardMostMove;
3794                     } else if (currentMove < forwardMostMove) {
3795                         ForwardInner(forwardMostMove);
3796                     }
3797                 } else {
3798                     /* I don't think this case really can happen */
3799                     SendToICS(ics_prefix);
3800                     SendToICS("refresh\n");
3801                 }
3802                 continue;
3803             }
3804
3805             /* Error messages */
3806 //          if (ics_user_moved) {
3807             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3808                 if (looking_at(buf, &i, "Illegal move") ||
3809                     looking_at(buf, &i, "Not a legal move") ||
3810                     looking_at(buf, &i, "Your king is in check") ||
3811                     looking_at(buf, &i, "It isn't your turn") ||
3812                     looking_at(buf, &i, "It is not your move")) {
3813                     /* Illegal move */
3814                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3815                         currentMove = forwardMostMove-1;
3816                         DisplayMove(currentMove - 1); /* before DMError */
3817                         DrawPosition(FALSE, boards[currentMove]);
3818                         SwitchClocks(forwardMostMove-1); // [HGM] race
3819                         DisplayBothClocks();
3820                     }
3821                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3822                     ics_user_moved = 0;
3823                     continue;
3824                 }
3825             }
3826
3827             if (looking_at(buf, &i, "still have time") ||
3828                 looking_at(buf, &i, "not out of time") ||
3829                 looking_at(buf, &i, "either player is out of time") ||
3830                 looking_at(buf, &i, "has timeseal; checking")) {
3831                 /* We must have called his flag a little too soon */
3832                 whiteFlag = blackFlag = FALSE;
3833                 continue;
3834             }
3835
3836             if (looking_at(buf, &i, "added * seconds to") ||
3837                 looking_at(buf, &i, "seconds were added to")) {
3838                 /* Update the clocks */
3839                 SendToICS(ics_prefix);
3840                 SendToICS("refresh\n");
3841                 continue;
3842             }
3843
3844             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3845                 ics_clock_paused = TRUE;
3846                 StopClocks();
3847                 continue;
3848             }
3849
3850             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3851                 ics_clock_paused = FALSE;
3852                 StartClocks();
3853                 continue;
3854             }
3855
3856             /* Grab player ratings from the Creating: message.
3857                Note we have to check for the special case when
3858                the ICS inserts things like [white] or [black]. */
3859             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3860                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3861                 /* star_matches:
3862                    0    player 1 name (not necessarily white)
3863                    1    player 1 rating
3864                    2    empty, white, or black (IGNORED)
3865                    3    player 2 name (not necessarily black)
3866                    4    player 2 rating
3867
3868                    The names/ratings are sorted out when the game
3869                    actually starts (below).
3870                 */
3871                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3872                 player1Rating = string_to_rating(star_match[1]);
3873                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3874                 player2Rating = string_to_rating(star_match[4]);
3875
3876                 if (appData.debugMode)
3877                   fprintf(debugFP,
3878                           "Ratings from 'Creating:' %s %d, %s %d\n",
3879                           player1Name, player1Rating,
3880                           player2Name, player2Rating);
3881
3882                 continue;
3883             }
3884
3885             /* Improved generic start/end-of-game messages */
3886             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3887                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3888                 /* If tkind == 0: */
3889                 /* star_match[0] is the game number */
3890                 /*           [1] is the white player's name */
3891                 /*           [2] is the black player's name */
3892                 /* For end-of-game: */
3893                 /*           [3] is the reason for the game end */
3894                 /*           [4] is a PGN end game-token, preceded by " " */
3895                 /* For start-of-game: */
3896                 /*           [3] begins with "Creating" or "Continuing" */
3897                 /*           [4] is " *" or empty (don't care). */
3898                 int gamenum = atoi(star_match[0]);
3899                 char *whitename, *blackname, *why, *endtoken;
3900                 ChessMove endtype = EndOfFile;
3901
3902                 if (tkind == 0) {
3903                   whitename = star_match[1];
3904                   blackname = star_match[2];
3905                   why = star_match[3];
3906                   endtoken = star_match[4];
3907                 } else {
3908                   whitename = star_match[1];
3909                   blackname = star_match[3];
3910                   why = star_match[5];
3911                   endtoken = star_match[6];
3912                 }
3913
3914                 /* Game start messages */
3915                 if (strncmp(why, "Creating ", 9) == 0 ||
3916                     strncmp(why, "Continuing ", 11) == 0) {
3917                     gs_gamenum = gamenum;
3918                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3919                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3920                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3921 #if ZIPPY
3922                     if (appData.zippyPlay) {
3923                         ZippyGameStart(whitename, blackname);
3924                     }
3925 #endif /*ZIPPY*/
3926                     partnerBoardValid = FALSE; // [HGM] bughouse
3927                     continue;
3928                 }
3929
3930                 /* Game end messages */
3931                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3932                     ics_gamenum != gamenum) {
3933                     continue;
3934                 }
3935                 while (endtoken[0] == ' ') endtoken++;
3936                 switch (endtoken[0]) {
3937                   case '*':
3938                   default:
3939                     endtype = GameUnfinished;
3940                     break;
3941                   case '0':
3942                     endtype = BlackWins;
3943                     break;
3944                   case '1':
3945                     if (endtoken[1] == '/')
3946                       endtype = GameIsDrawn;
3947                     else
3948                       endtype = WhiteWins;
3949                     break;
3950                 }
3951                 GameEnds(endtype, why, GE_ICS);
3952 #if ZIPPY
3953                 if (appData.zippyPlay && first.initDone) {
3954                     ZippyGameEnd(endtype, why);
3955                     if (first.pr == NoProc) {
3956                       /* Start the next process early so that we'll
3957                          be ready for the next challenge */
3958                       StartChessProgram(&first);
3959                     }
3960                     /* Send "new" early, in case this command takes
3961                        a long time to finish, so that we'll be ready
3962                        for the next challenge. */
3963                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3964                     Reset(TRUE, TRUE);
3965                 }
3966 #endif /*ZIPPY*/
3967                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3968                 continue;
3969             }
3970
3971             if (looking_at(buf, &i, "Removing game * from observation") ||
3972                 looking_at(buf, &i, "no longer observing game *") ||
3973                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3974                 if (gameMode == IcsObserving &&
3975                     atoi(star_match[0]) == ics_gamenum)
3976                   {
3977                       /* icsEngineAnalyze */
3978                       if (appData.icsEngineAnalyze) {
3979                             ExitAnalyzeMode();
3980                             ModeHighlight();
3981                       }
3982                       StopClocks();
3983                       gameMode = IcsIdle;
3984                       ics_gamenum = -1;
3985                       ics_user_moved = FALSE;
3986                   }
3987                 continue;
3988             }
3989
3990             if (looking_at(buf, &i, "no longer examining game *")) {
3991                 if (gameMode == IcsExamining &&
3992                     atoi(star_match[0]) == ics_gamenum)
3993                   {
3994                       gameMode = IcsIdle;
3995                       ics_gamenum = -1;
3996                       ics_user_moved = FALSE;
3997                   }
3998                 continue;
3999             }
4000
4001             /* Advance leftover_start past any newlines we find,
4002                so only partial lines can get reparsed */
4003             if (looking_at(buf, &i, "\n")) {
4004                 prevColor = curColor;
4005                 if (curColor != ColorNormal) {
4006                     if (oldi > next_out) {
4007                         SendToPlayer(&buf[next_out], oldi - next_out);
4008                         next_out = oldi;
4009                     }
4010                     Colorize(ColorNormal, FALSE);
4011                     curColor = ColorNormal;
4012                 }
4013                 if (started == STARTED_BOARD) {
4014                     started = STARTED_NONE;
4015                     parse[parse_pos] = NULLCHAR;
4016                     ParseBoard12(parse);
4017                     ics_user_moved = 0;
4018
4019                     /* Send premove here */
4020                     if (appData.premove) {
4021                       char str[MSG_SIZ];
4022                       if (currentMove == 0 &&
4023                           gameMode == IcsPlayingWhite &&
4024                           appData.premoveWhite) {
4025                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4026                         if (appData.debugMode)
4027                           fprintf(debugFP, "Sending premove:\n");
4028                         SendToICS(str);
4029                       } else if (currentMove == 1 &&
4030                                  gameMode == IcsPlayingBlack &&
4031                                  appData.premoveBlack) {
4032                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4033                         if (appData.debugMode)
4034                           fprintf(debugFP, "Sending premove:\n");
4035                         SendToICS(str);
4036                       } else if (gotPremove) {
4037                         gotPremove = 0;
4038                         ClearPremoveHighlights();
4039                         if (appData.debugMode)
4040                           fprintf(debugFP, "Sending premove:\n");
4041                           UserMoveEvent(premoveFromX, premoveFromY,
4042                                         premoveToX, premoveToY,
4043                                         premovePromoChar);
4044                       }
4045                     }
4046
4047                     /* Usually suppress following prompt */
4048                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4049                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4050                         if (looking_at(buf, &i, "*% ")) {
4051                             savingComment = FALSE;
4052                             suppressKibitz = 0;
4053                         }
4054                     }
4055                     next_out = i;
4056                 } else if (started == STARTED_HOLDINGS) {
4057                     int gamenum;
4058                     char new_piece[MSG_SIZ];
4059                     started = STARTED_NONE;
4060                     parse[parse_pos] = NULLCHAR;
4061                     if (appData.debugMode)
4062                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4063                                                         parse, currentMove);
4064                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4065                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4066                         if (gameInfo.variant == VariantNormal) {
4067                           /* [HGM] We seem to switch variant during a game!
4068                            * Presumably no holdings were displayed, so we have
4069                            * to move the position two files to the right to
4070                            * create room for them!
4071                            */
4072                           VariantClass newVariant;
4073                           switch(gameInfo.boardWidth) { // base guess on board width
4074                                 case 9:  newVariant = VariantShogi; break;
4075                                 case 10: newVariant = VariantGreat; break;
4076                                 default: newVariant = VariantCrazyhouse; break;
4077                           }
4078                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4079                           /* Get a move list just to see the header, which
4080                              will tell us whether this is really bug or zh */
4081                           if (ics_getting_history == H_FALSE) {
4082                             ics_getting_history = H_REQUESTED;
4083                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4084                             SendToICS(str);
4085                           }
4086                         }
4087                         new_piece[0] = NULLCHAR;
4088                         sscanf(parse, "game %d white [%s black [%s <- %s",
4089                                &gamenum, white_holding, black_holding,
4090                                new_piece);
4091                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4092                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4093                         /* [HGM] copy holdings to board holdings area */
4094                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4095                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4096                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4097 #if ZIPPY
4098                         if (appData.zippyPlay && first.initDone) {
4099                             ZippyHoldings(white_holding, black_holding,
4100                                           new_piece);
4101                         }
4102 #endif /*ZIPPY*/
4103                         if (tinyLayout || smallLayout) {
4104                             char wh[16], bh[16];
4105                             PackHolding(wh, white_holding);
4106                             PackHolding(bh, black_holding);
4107                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4108                                     gameInfo.white, gameInfo.black);
4109                         } else {
4110                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4111                                     gameInfo.white, white_holding, _("vs."),
4112                                     gameInfo.black, black_holding);
4113                         }
4114                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4115                         DrawPosition(FALSE, boards[currentMove]);
4116                         DisplayTitle(str);
4117                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4118                         sscanf(parse, "game %d white [%s black [%s <- %s",
4119                                &gamenum, white_holding, black_holding,
4120                                new_piece);
4121                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4122                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4123                         /* [HGM] copy holdings to partner-board holdings area */
4124                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4125                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4126                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4127                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4128                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4129                       }
4130                     }
4131                     /* Suppress following prompt */
4132                     if (looking_at(buf, &i, "*% ")) {
4133                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4134                         savingComment = FALSE;
4135                         suppressKibitz = 0;
4136                     }
4137                     next_out = i;
4138                 }
4139                 continue;
4140             }
4141
4142             i++;                /* skip unparsed character and loop back */
4143         }
4144
4145         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4146 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4147 //          SendToPlayer(&buf[next_out], i - next_out);
4148             started != STARTED_HOLDINGS && leftover_start > next_out) {
4149             SendToPlayer(&buf[next_out], leftover_start - next_out);
4150             next_out = i;
4151         }
4152
4153         leftover_len = buf_len - leftover_start;
4154         /* if buffer ends with something we couldn't parse,
4155            reparse it after appending the next read */
4156
4157     } else if (count == 0) {
4158         RemoveInputSource(isr);
4159         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4160     } else {
4161         DisplayFatalError(_("Error reading from ICS"), error, 1);
4162     }
4163 }
4164
4165
4166 /* Board style 12 looks like this:
4167
4168    <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
4169
4170  * The "<12> " is stripped before it gets to this routine.  The two
4171  * trailing 0's (flip state and clock ticking) are later addition, and
4172  * some chess servers may not have them, or may have only the first.
4173  * Additional trailing fields may be added in the future.
4174  */
4175
4176 #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"
4177
4178 #define RELATION_OBSERVING_PLAYED    0
4179 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4180 #define RELATION_PLAYING_MYMOVE      1
4181 #define RELATION_PLAYING_NOTMYMOVE  -1
4182 #define RELATION_EXAMINING           2
4183 #define RELATION_ISOLATED_BOARD     -3
4184 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4185
4186 void
4187 ParseBoard12 (char *string)
4188 {
4189 #if ZIPPY
4190     int i, takeback;
4191     char *bookHit = NULL; // [HGM] book
4192 #endif
4193     GameMode newGameMode;
4194     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4195     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4196     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4197     char to_play, board_chars[200];
4198     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4199     char black[32], white[32];
4200     Board board;
4201     int prevMove = currentMove;
4202     int ticking = 2;
4203     ChessMove moveType;
4204     int fromX, fromY, toX, toY;
4205     char promoChar;
4206     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4207     Boolean weird = FALSE, reqFlag = FALSE;
4208
4209     fromX = fromY = toX = toY = -1;
4210
4211     newGame = FALSE;
4212
4213     if (appData.debugMode)
4214       fprintf(debugFP, _("Parsing board: %s\n"), string);
4215
4216     move_str[0] = NULLCHAR;
4217     elapsed_time[0] = NULLCHAR;
4218     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4219         int  i = 0, j;
4220         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4221             if(string[i] == ' ') { ranks++; files = 0; }
4222             else files++;
4223             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4224             i++;
4225         }
4226         for(j = 0; j <i; j++) board_chars[j] = string[j];
4227         board_chars[i] = '\0';
4228         string += i + 1;
4229     }
4230     n = sscanf(string, PATTERN, &to_play, &double_push,
4231                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4232                &gamenum, white, black, &relation, &basetime, &increment,
4233                &white_stren, &black_stren, &white_time, &black_time,
4234                &moveNum, str, elapsed_time, move_str, &ics_flip,
4235                &ticking);
4236
4237     if (n < 21) {
4238         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4239         DisplayError(str, 0);
4240         return;
4241     }
4242
4243     /* Convert the move number to internal form */
4244     moveNum = (moveNum - 1) * 2;
4245     if (to_play == 'B') moveNum++;
4246     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4247       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4248                         0, 1);
4249       return;
4250     }
4251
4252     switch (relation) {
4253       case RELATION_OBSERVING_PLAYED:
4254       case RELATION_OBSERVING_STATIC:
4255         if (gamenum == -1) {
4256             /* Old ICC buglet */
4257             relation = RELATION_OBSERVING_STATIC;
4258         }
4259         newGameMode = IcsObserving;
4260         break;
4261       case RELATION_PLAYING_MYMOVE:
4262       case RELATION_PLAYING_NOTMYMOVE:
4263         newGameMode =
4264           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4265             IcsPlayingWhite : IcsPlayingBlack;
4266         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4267         break;
4268       case RELATION_EXAMINING:
4269         newGameMode = IcsExamining;
4270         break;
4271       case RELATION_ISOLATED_BOARD:
4272       default:
4273         /* Just display this board.  If user was doing something else,
4274            we will forget about it until the next board comes. */
4275         newGameMode = IcsIdle;
4276         break;
4277       case RELATION_STARTING_POSITION:
4278         newGameMode = gameMode;
4279         break;
4280     }
4281
4282     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4283         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4284          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4285       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4286       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4287       static int lastBgGame = -1;
4288       char *toSqr;
4289       for (k = 0; k < ranks; k++) {
4290         for (j = 0; j < files; j++)
4291           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4292         if(gameInfo.holdingsWidth > 1) {
4293              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4294              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4295         }
4296       }
4297       CopyBoard(partnerBoard, board);
4298       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4299         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4300         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4301       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4302       if(toSqr = strchr(str, '-')) {
4303         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4304         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4305       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4306       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4307       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4308       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4309       if(twoBoards) {
4310           DisplayWhiteClock(white_time*fac, to_play == 'W');
4311           DisplayBlackClock(black_time*fac, to_play != 'W');
4312           activePartner = to_play;
4313           if(gamenum != lastBgGame) {
4314               char buf[MSG_SIZ];
4315               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4316               DisplayTitle(buf);
4317           }
4318           lastBgGame = gamenum;
4319           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4320                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4321       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4322                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4323       DisplayMessage(partnerStatus, "");
4324         partnerBoardValid = TRUE;
4325       return;
4326     }
4327
4328     if(appData.dualBoard && appData.bgObserve) {
4329         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4330             SendToICS(ics_prefix), SendToICS("pobserve\n");
4331         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4332             char buf[MSG_SIZ];
4333             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4334             SendToICS(buf);
4335         }
4336     }
4337
4338     /* Modify behavior for initial board display on move listing
4339        of wild games.
4340        */
4341     switch (ics_getting_history) {
4342       case H_FALSE:
4343       case H_REQUESTED:
4344         break;
4345       case H_GOT_REQ_HEADER:
4346       case H_GOT_UNREQ_HEADER:
4347         /* This is the initial position of the current game */
4348         gamenum = ics_gamenum;
4349         moveNum = 0;            /* old ICS bug workaround */
4350         if (to_play == 'B') {
4351           startedFromSetupPosition = TRUE;
4352           blackPlaysFirst = TRUE;
4353           moveNum = 1;
4354           if (forwardMostMove == 0) forwardMostMove = 1;
4355           if (backwardMostMove == 0) backwardMostMove = 1;
4356           if (currentMove == 0) currentMove = 1;
4357         }
4358         newGameMode = gameMode;
4359         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4360         break;
4361       case H_GOT_UNWANTED_HEADER:
4362         /* This is an initial board that we don't want */
4363         return;
4364       case H_GETTING_MOVES:
4365         /* Should not happen */
4366         DisplayError(_("Error gathering move list: extra board"), 0);
4367         ics_getting_history = H_FALSE;
4368         return;
4369     }
4370
4371    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4372                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4373                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4374      /* [HGM] We seem to have switched variant unexpectedly
4375       * Try to guess new variant from board size
4376       */
4377           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4378           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4379           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4380           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4381           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4382           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4383           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4384           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4385           /* Get a move list just to see the header, which
4386              will tell us whether this is really bug or zh */
4387           if (ics_getting_history == H_FALSE) {
4388             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4389             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4390             SendToICS(str);
4391           }
4392     }
4393
4394     /* Take action if this is the first board of a new game, or of a
4395        different game than is currently being displayed.  */
4396     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4397         relation == RELATION_ISOLATED_BOARD) {
4398
4399         /* Forget the old game and get the history (if any) of the new one */
4400         if (gameMode != BeginningOfGame) {
4401           Reset(TRUE, TRUE);
4402         }
4403         newGame = TRUE;
4404         if (appData.autoRaiseBoard) BoardToTop();
4405         prevMove = -3;
4406         if (gamenum == -1) {
4407             newGameMode = IcsIdle;
4408         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4409                    appData.getMoveList && !reqFlag) {
4410             /* Need to get game history */
4411             ics_getting_history = H_REQUESTED;
4412             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4413             SendToICS(str);
4414         }
4415
4416         /* Initially flip the board to have black on the bottom if playing
4417            black or if the ICS flip flag is set, but let the user change
4418            it with the Flip View button. */
4419         flipView = appData.autoFlipView ?
4420           (newGameMode == IcsPlayingBlack) || ics_flip :
4421           appData.flipView;
4422
4423         /* Done with values from previous mode; copy in new ones */
4424         gameMode = newGameMode;
4425         ModeHighlight();
4426         ics_gamenum = gamenum;
4427         if (gamenum == gs_gamenum) {
4428             int klen = strlen(gs_kind);
4429             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4430             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4431             gameInfo.event = StrSave(str);
4432         } else {
4433             gameInfo.event = StrSave("ICS game");
4434         }
4435         gameInfo.site = StrSave(appData.icsHost);
4436         gameInfo.date = PGNDate();
4437         gameInfo.round = StrSave("-");
4438         gameInfo.white = StrSave(white);
4439         gameInfo.black = StrSave(black);
4440         timeControl = basetime * 60 * 1000;
4441         timeControl_2 = 0;
4442         timeIncrement = increment * 1000;
4443         movesPerSession = 0;
4444         gameInfo.timeControl = TimeControlTagValue();
4445         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4446   if (appData.debugMode) {
4447     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4448     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4449     setbuf(debugFP, NULL);
4450   }
4451
4452         gameInfo.outOfBook = NULL;
4453
4454         /* Do we have the ratings? */
4455         if (strcmp(player1Name, white) == 0 &&
4456             strcmp(player2Name, black) == 0) {
4457             if (appData.debugMode)
4458               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4459                       player1Rating, player2Rating);
4460             gameInfo.whiteRating = player1Rating;
4461             gameInfo.blackRating = player2Rating;
4462         } else if (strcmp(player2Name, white) == 0 &&
4463                    strcmp(player1Name, black) == 0) {
4464             if (appData.debugMode)
4465               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4466                       player2Rating, player1Rating);
4467             gameInfo.whiteRating = player2Rating;
4468             gameInfo.blackRating = player1Rating;
4469         }
4470         player1Name[0] = player2Name[0] = NULLCHAR;
4471
4472         /* Silence shouts if requested */
4473         if (appData.quietPlay &&
4474             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4475             SendToICS(ics_prefix);
4476             SendToICS("set shout 0\n");
4477         }
4478     }
4479
4480     /* Deal with midgame name changes */
4481     if (!newGame) {
4482         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4483             if (gameInfo.white) free(gameInfo.white);
4484             gameInfo.white = StrSave(white);
4485         }
4486         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4487             if (gameInfo.black) free(gameInfo.black);
4488             gameInfo.black = StrSave(black);
4489         }
4490     }
4491
4492     /* Throw away game result if anything actually changes in examine mode */
4493     if (gameMode == IcsExamining && !newGame) {
4494         gameInfo.result = GameUnfinished;
4495         if (gameInfo.resultDetails != NULL) {
4496             free(gameInfo.resultDetails);
4497             gameInfo.resultDetails = NULL;
4498         }
4499     }
4500
4501     /* In pausing && IcsExamining mode, we ignore boards coming
4502        in if they are in a different variation than we are. */
4503     if (pauseExamInvalid) return;
4504     if (pausing && gameMode == IcsExamining) {
4505         if (moveNum <= pauseExamForwardMostMove) {
4506             pauseExamInvalid = TRUE;
4507             forwardMostMove = pauseExamForwardMostMove;
4508             return;
4509         }
4510     }
4511
4512   if (appData.debugMode) {
4513     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4514   }
4515     /* Parse the board */
4516     for (k = 0; k < ranks; k++) {
4517       for (j = 0; j < files; j++)
4518         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4519       if(gameInfo.holdingsWidth > 1) {
4520            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4521            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4522       }
4523     }
4524     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4525       board[5][BOARD_RGHT+1] = WhiteAngel;
4526       board[6][BOARD_RGHT+1] = WhiteMarshall;
4527       board[1][0] = BlackMarshall;
4528       board[2][0] = BlackAngel;
4529       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4530     }
4531     CopyBoard(boards[moveNum], board);
4532     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4533     if (moveNum == 0) {
4534         startedFromSetupPosition =
4535           !CompareBoards(board, initialPosition);
4536         if(startedFromSetupPosition)
4537             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4538     }
4539
4540     /* [HGM] Set castling rights. Take the outermost Rooks,
4541        to make it also work for FRC opening positions. Note that board12
4542        is really defective for later FRC positions, as it has no way to
4543        indicate which Rook can castle if they are on the same side of King.
4544        For the initial position we grant rights to the outermost Rooks,
4545        and remember thos rights, and we then copy them on positions
4546        later in an FRC game. This means WB might not recognize castlings with
4547        Rooks that have moved back to their original position as illegal,
4548        but in ICS mode that is not its job anyway.
4549     */
4550     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4551     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4552
4553         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4554             if(board[0][i] == WhiteRook) j = i;
4555         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4556         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4557             if(board[0][i] == WhiteRook) j = i;
4558         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4559         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4560             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4561         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4562         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4563             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4564         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4565
4566         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4567         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4568         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4569             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4570         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4571             if(board[BOARD_HEIGHT-1][k] == bKing)
4572                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4573         if(gameInfo.variant == VariantTwoKings) {
4574             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4575             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4576             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4577         }
4578     } else { int r;
4579         r = boards[moveNum][CASTLING][0] = initialRights[0];
4580         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4581         r = boards[moveNum][CASTLING][1] = initialRights[1];
4582         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4583         r = boards[moveNum][CASTLING][3] = initialRights[3];
4584         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4585         r = boards[moveNum][CASTLING][4] = initialRights[4];
4586         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4587         /* wildcastle kludge: always assume King has rights */
4588         r = boards[moveNum][CASTLING][2] = initialRights[2];
4589         r = boards[moveNum][CASTLING][5] = initialRights[5];
4590     }
4591     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4592     boards[moveNum][EP_STATUS] = EP_NONE;
4593     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4594     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4595     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4596
4597
4598     if (ics_getting_history == H_GOT_REQ_HEADER ||
4599         ics_getting_history == H_GOT_UNREQ_HEADER) {
4600         /* This was an initial position from a move list, not
4601            the current position */
4602         return;
4603     }
4604
4605     /* Update currentMove and known move number limits */
4606     newMove = newGame || moveNum > forwardMostMove;
4607
4608     if (newGame) {
4609         forwardMostMove = backwardMostMove = currentMove = moveNum;
4610         if (gameMode == IcsExamining && moveNum == 0) {
4611           /* Workaround for ICS limitation: we are not told the wild
4612              type when starting to examine a game.  But if we ask for
4613              the move list, the move list header will tell us */
4614             ics_getting_history = H_REQUESTED;
4615             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4616             SendToICS(str);
4617         }
4618     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4619                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4620 #if ZIPPY
4621         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4622         /* [HGM] applied this also to an engine that is silently watching        */
4623         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4624             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4625             gameInfo.variant == currentlyInitializedVariant) {
4626           takeback = forwardMostMove - moveNum;
4627           for (i = 0; i < takeback; i++) {
4628             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4629             SendToProgram("undo\n", &first);
4630           }
4631         }
4632 #endif
4633
4634         forwardMostMove = moveNum;
4635         if (!pausing || currentMove > forwardMostMove)
4636           currentMove = forwardMostMove;
4637     } else {
4638         /* New part of history that is not contiguous with old part */
4639         if (pausing && gameMode == IcsExamining) {
4640             pauseExamInvalid = TRUE;
4641             forwardMostMove = pauseExamForwardMostMove;
4642             return;
4643         }
4644         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4645 #if ZIPPY
4646             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4647                 // [HGM] when we will receive the move list we now request, it will be
4648                 // fed to the engine from the first move on. So if the engine is not
4649                 // in the initial position now, bring it there.
4650                 InitChessProgram(&first, 0);
4651             }
4652 #endif
4653             ics_getting_history = H_REQUESTED;
4654             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4655             SendToICS(str);
4656         }
4657         forwardMostMove = backwardMostMove = currentMove = moveNum;
4658     }
4659
4660     /* Update the clocks */
4661     if (strchr(elapsed_time, '.')) {
4662       /* Time is in ms */
4663       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4664       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4665     } else {
4666       /* Time is in seconds */
4667       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4668       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4669     }
4670
4671
4672 #if ZIPPY
4673     if (appData.zippyPlay && newGame &&
4674         gameMode != IcsObserving && gameMode != IcsIdle &&
4675         gameMode != IcsExamining)
4676       ZippyFirstBoard(moveNum, basetime, increment);
4677 #endif
4678
4679     /* Put the move on the move list, first converting
4680        to canonical algebraic form. */
4681     if (moveNum > 0) {
4682   if (appData.debugMode) {
4683     if (appData.debugMode) { int f = forwardMostMove;
4684         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4685                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4686                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4687     }
4688     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4689     fprintf(debugFP, "moveNum = %d\n", moveNum);
4690     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4691     setbuf(debugFP, NULL);
4692   }
4693         if (moveNum <= backwardMostMove) {
4694             /* We don't know what the board looked like before
4695                this move.  Punt. */
4696           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4697             strcat(parseList[moveNum - 1], " ");
4698             strcat(parseList[moveNum - 1], elapsed_time);
4699             moveList[moveNum - 1][0] = NULLCHAR;
4700         } else if (strcmp(move_str, "none") == 0) {
4701             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4702             /* Again, we don't know what the board looked like;
4703                this is really the start of the game. */
4704             parseList[moveNum - 1][0] = NULLCHAR;
4705             moveList[moveNum - 1][0] = NULLCHAR;
4706             backwardMostMove = moveNum;
4707             startedFromSetupPosition = TRUE;
4708             fromX = fromY = toX = toY = -1;
4709         } else {
4710           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4711           //                 So we parse the long-algebraic move string in stead of the SAN move
4712           int valid; char buf[MSG_SIZ], *prom;
4713
4714           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4715                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4716           // str looks something like "Q/a1-a2"; kill the slash
4717           if(str[1] == '/')
4718             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4719           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4720           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4721                 strcat(buf, prom); // long move lacks promo specification!
4722           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4723                 if(appData.debugMode)
4724                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4725                 safeStrCpy(move_str, buf, MSG_SIZ);
4726           }
4727           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4728                                 &fromX, &fromY, &toX, &toY, &promoChar)
4729                || ParseOneMove(buf, moveNum - 1, &moveType,
4730                                 &fromX, &fromY, &toX, &toY, &promoChar);
4731           // end of long SAN patch
4732           if (valid) {
4733             (void) CoordsToAlgebraic(boards[moveNum - 1],
4734                                      PosFlags(moveNum - 1),
4735                                      fromY, fromX, toY, toX, promoChar,
4736                                      parseList[moveNum-1]);
4737             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4738               case MT_NONE:
4739               case MT_STALEMATE:
4740               default:
4741                 break;
4742               case MT_CHECK:
4743                 if(gameInfo.variant != VariantShogi)
4744                     strcat(parseList[moveNum - 1], "+");
4745                 break;
4746               case MT_CHECKMATE:
4747               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4748                 strcat(parseList[moveNum - 1], "#");
4749                 break;
4750             }
4751             strcat(parseList[moveNum - 1], " ");
4752             strcat(parseList[moveNum - 1], elapsed_time);
4753             /* currentMoveString is set as a side-effect of ParseOneMove */
4754             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4755             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4756             strcat(moveList[moveNum - 1], "\n");
4757
4758             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4759                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4760               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4761                 ChessSquare old, new = boards[moveNum][k][j];
4762                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4763                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4764                   if(old == new) continue;
4765                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4766                   else if(new == WhiteWazir || new == BlackWazir) {
4767                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4768                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4769                       else boards[moveNum][k][j] = old; // preserve type of Gold
4770                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4771                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4772               }
4773           } else {
4774             /* Move from ICS was illegal!?  Punt. */
4775             if (appData.debugMode) {
4776               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4777               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4778             }
4779             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4780             strcat(parseList[moveNum - 1], " ");
4781             strcat(parseList[moveNum - 1], elapsed_time);
4782             moveList[moveNum - 1][0] = NULLCHAR;
4783             fromX = fromY = toX = toY = -1;
4784           }
4785         }
4786   if (appData.debugMode) {
4787     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4788     setbuf(debugFP, NULL);
4789   }
4790
4791 #if ZIPPY
4792         /* Send move to chess program (BEFORE animating it). */
4793         if (appData.zippyPlay && !newGame && newMove &&
4794            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4795
4796             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4797                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4798                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4799                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4800                             move_str);
4801                     DisplayError(str, 0);
4802                 } else {
4803                     if (first.sendTime) {
4804                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4805                     }
4806                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4807                     if (firstMove && !bookHit) {
4808                         firstMove = FALSE;
4809                         if (first.useColors) {
4810                           SendToProgram(gameMode == IcsPlayingWhite ?
4811                                         "white\ngo\n" :
4812                                         "black\ngo\n", &first);
4813                         } else {
4814                           SendToProgram("go\n", &first);
4815                         }
4816                         first.maybeThinking = TRUE;
4817                     }
4818                 }
4819             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4820               if (moveList[moveNum - 1][0] == NULLCHAR) {
4821                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4822                 DisplayError(str, 0);
4823               } else {
4824                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4825                 SendMoveToProgram(moveNum - 1, &first);
4826               }
4827             }
4828         }
4829 #endif
4830     }
4831
4832     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4833         /* If move comes from a remote source, animate it.  If it
4834            isn't remote, it will have already been animated. */
4835         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4836             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4837         }
4838         if (!pausing && appData.highlightLastMove) {
4839             SetHighlights(fromX, fromY, toX, toY);
4840         }
4841     }
4842
4843     /* Start the clocks */
4844     whiteFlag = blackFlag = FALSE;
4845     appData.clockMode = !(basetime == 0 && increment == 0);
4846     if (ticking == 0) {
4847       ics_clock_paused = TRUE;
4848       StopClocks();
4849     } else if (ticking == 1) {
4850       ics_clock_paused = FALSE;
4851     }
4852     if (gameMode == IcsIdle ||
4853         relation == RELATION_OBSERVING_STATIC ||
4854         relation == RELATION_EXAMINING ||
4855         ics_clock_paused)
4856       DisplayBothClocks();
4857     else
4858       StartClocks();
4859
4860     /* Display opponents and material strengths */
4861     if (gameInfo.variant != VariantBughouse &&
4862         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4863         if (tinyLayout || smallLayout) {
4864             if(gameInfo.variant == VariantNormal)
4865               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4866                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4867                     basetime, increment);
4868             else
4869               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4870                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4871                     basetime, increment, (int) gameInfo.variant);
4872         } else {
4873             if(gameInfo.variant == VariantNormal)
4874               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4875                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4876                     basetime, increment);
4877             else
4878               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4879                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4880                     basetime, increment, VariantName(gameInfo.variant));
4881         }
4882         DisplayTitle(str);
4883   if (appData.debugMode) {
4884     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4885   }
4886     }
4887
4888
4889     /* Display the board */
4890     if (!pausing && !appData.noGUI) {
4891
4892       if (appData.premove)
4893           if (!gotPremove ||
4894              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4895              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4896               ClearPremoveHighlights();
4897
4898       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4899         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4900       DrawPosition(j, boards[currentMove]);
4901
4902       DisplayMove(moveNum - 1);
4903       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4904             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4905               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4906         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4907       }
4908     }
4909
4910     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4911 #if ZIPPY
4912     if(bookHit) { // [HGM] book: simulate book reply
4913         static char bookMove[MSG_SIZ]; // a bit generous?
4914
4915         programStats.nodes = programStats.depth = programStats.time =
4916         programStats.score = programStats.got_only_move = 0;
4917         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4918
4919         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4920         strcat(bookMove, bookHit);
4921         HandleMachineMove(bookMove, &first);
4922     }
4923 #endif
4924 }
4925
4926 void
4927 GetMoveListEvent ()
4928 {
4929     char buf[MSG_SIZ];
4930     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4931         ics_getting_history = H_REQUESTED;
4932         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4933         SendToICS(buf);
4934     }
4935 }
4936
4937 void
4938 SendToBoth (char *msg)
4939 {   // to make it easy to keep two engines in step in dual analysis
4940     SendToProgram(msg, &first);
4941     if(second.analyzing) SendToProgram(msg, &second);
4942 }
4943
4944 void
4945 AnalysisPeriodicEvent (int force)
4946 {
4947     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4948          && !force) || !appData.periodicUpdates)
4949       return;
4950
4951     /* Send . command to Crafty to collect stats */
4952     SendToBoth(".\n");
4953
4954     /* Don't send another until we get a response (this makes
4955        us stop sending to old Crafty's which don't understand
4956        the "." command (sending illegal cmds resets node count & time,
4957        which looks bad)) */
4958     programStats.ok_to_send = 0;
4959 }
4960
4961 void
4962 ics_update_width (int new_width)
4963 {
4964         ics_printf("set width %d\n", new_width);
4965 }
4966
4967 void
4968 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4969 {
4970     char buf[MSG_SIZ];
4971
4972     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4973         // null move in variant where engine does not understand it (for analysis purposes)
4974         SendBoard(cps, moveNum + 1); // send position after move in stead.
4975         return;
4976     }
4977     if (cps->useUsermove) {
4978       SendToProgram("usermove ", cps);
4979     }
4980     if (cps->useSAN) {
4981       char *space;
4982       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4983         int len = space - parseList[moveNum];
4984         memcpy(buf, parseList[moveNum], len);
4985         buf[len++] = '\n';
4986         buf[len] = NULLCHAR;
4987       } else {
4988         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4989       }
4990       SendToProgram(buf, cps);
4991     } else {
4992       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4993         AlphaRank(moveList[moveNum], 4);
4994         SendToProgram(moveList[moveNum], cps);
4995         AlphaRank(moveList[moveNum], 4); // and back
4996       } else
4997       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4998        * the engine. It would be nice to have a better way to identify castle
4999        * moves here. */
5000       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5001                                                                          && cps->useOOCastle) {
5002         int fromX = moveList[moveNum][0] - AAA;
5003         int fromY = moveList[moveNum][1] - ONE;
5004         int toX = moveList[moveNum][2] - AAA;
5005         int toY = moveList[moveNum][3] - ONE;
5006         if((boards[moveNum][fromY][fromX] == WhiteKing
5007             && boards[moveNum][toY][toX] == WhiteRook)
5008            || (boards[moveNum][fromY][fromX] == BlackKing
5009                && boards[moveNum][toY][toX] == BlackRook)) {
5010           if(toX > fromX) SendToProgram("O-O\n", cps);
5011           else SendToProgram("O-O-O\n", cps);
5012         }
5013         else SendToProgram(moveList[moveNum], cps);
5014       } else
5015       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5016         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5017           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5018           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5019                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5020         } else
5021           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5022                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5023         SendToProgram(buf, cps);
5024       }
5025       else SendToProgram(moveList[moveNum], cps);
5026       /* End of additions by Tord */
5027     }
5028
5029     /* [HGM] setting up the opening has brought engine in force mode! */
5030     /*       Send 'go' if we are in a mode where machine should play. */
5031     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5032         (gameMode == TwoMachinesPlay   ||
5033 #if ZIPPY
5034          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5035 #endif
5036          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5037         SendToProgram("go\n", cps);
5038   if (appData.debugMode) {
5039     fprintf(debugFP, "(extra)\n");
5040   }
5041     }
5042     setboardSpoiledMachineBlack = 0;
5043 }
5044
5045 void
5046 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5047 {
5048     char user_move[MSG_SIZ];
5049     char suffix[4];
5050
5051     if(gameInfo.variant == VariantSChess && promoChar) {
5052         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5053         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5054     } else suffix[0] = NULLCHAR;
5055
5056     switch (moveType) {
5057       default:
5058         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5059                 (int)moveType, fromX, fromY, toX, toY);
5060         DisplayError(user_move + strlen("say "), 0);
5061         break;
5062       case WhiteKingSideCastle:
5063       case BlackKingSideCastle:
5064       case WhiteQueenSideCastleWild:
5065       case BlackQueenSideCastleWild:
5066       /* PUSH Fabien */
5067       case WhiteHSideCastleFR:
5068       case BlackHSideCastleFR:
5069       /* POP Fabien */
5070         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5071         break;
5072       case WhiteQueenSideCastle:
5073       case BlackQueenSideCastle:
5074       case WhiteKingSideCastleWild:
5075       case BlackKingSideCastleWild:
5076       /* PUSH Fabien */
5077       case WhiteASideCastleFR:
5078       case BlackASideCastleFR:
5079       /* POP Fabien */
5080         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5081         break;
5082       case WhiteNonPromotion:
5083       case BlackNonPromotion:
5084         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5085         break;
5086       case WhitePromotion:
5087       case BlackPromotion:
5088         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5089           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5090                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5091                 PieceToChar(WhiteFerz));
5092         else if(gameInfo.variant == VariantGreat)
5093           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5094                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5095                 PieceToChar(WhiteMan));
5096         else
5097           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5098                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5099                 promoChar);
5100         break;
5101       case WhiteDrop:
5102       case BlackDrop:
5103       drop:
5104         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5105                  ToUpper(PieceToChar((ChessSquare) fromX)),
5106                  AAA + toX, ONE + toY);
5107         break;
5108       case IllegalMove:  /* could be a variant we don't quite understand */
5109         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5110       case NormalMove:
5111       case WhiteCapturesEnPassant:
5112       case BlackCapturesEnPassant:
5113         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5114                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5115         break;
5116     }
5117     SendToICS(user_move);
5118     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5119         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5120 }
5121
5122 void
5123 UploadGameEvent ()
5124 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5125     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5126     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5127     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5128       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5129       return;
5130     }
5131     if(gameMode != IcsExamining) { // is this ever not the case?
5132         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5133
5134         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5135           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5136         } else { // on FICS we must first go to general examine mode
5137           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5138         }
5139         if(gameInfo.variant != VariantNormal) {
5140             // try figure out wild number, as xboard names are not always valid on ICS
5141             for(i=1; i<=36; i++) {
5142               snprintf(buf, MSG_SIZ, "wild/%d", i);
5143                 if(StringToVariant(buf) == gameInfo.variant) break;
5144             }
5145             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5146             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5147             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5148         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5149         SendToICS(ics_prefix);
5150         SendToICS(buf);
5151         if(startedFromSetupPosition || backwardMostMove != 0) {
5152           fen = PositionToFEN(backwardMostMove, NULL);
5153           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5154             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5155             SendToICS(buf);
5156           } else { // FICS: everything has to set by separate bsetup commands
5157             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5158             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5159             SendToICS(buf);
5160             if(!WhiteOnMove(backwardMostMove)) {
5161                 SendToICS("bsetup tomove black\n");
5162             }
5163             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5164             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5165             SendToICS(buf);
5166             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5167             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5168             SendToICS(buf);
5169             i = boards[backwardMostMove][EP_STATUS];
5170             if(i >= 0) { // set e.p.
5171               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5172                 SendToICS(buf);
5173             }
5174             bsetup++;
5175           }
5176         }
5177       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5178             SendToICS("bsetup done\n"); // switch to normal examining.
5179     }
5180     for(i = backwardMostMove; i<last; i++) {
5181         char buf[20];
5182         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5183         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5184             int len = strlen(moveList[i]);
5185             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5186             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5187         }
5188         SendToICS(buf);
5189     }
5190     SendToICS(ics_prefix);
5191     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5192 }
5193
5194 void
5195 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5196 {
5197     if (rf == DROP_RANK) {
5198       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5199       sprintf(move, "%c@%c%c\n",
5200                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5201     } else {
5202         if (promoChar == 'x' || promoChar == NULLCHAR) {
5203           sprintf(move, "%c%c%c%c\n",
5204                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5205         } else {
5206             sprintf(move, "%c%c%c%c%c\n",
5207                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5208         }
5209     }
5210 }
5211
5212 void
5213 ProcessICSInitScript (FILE *f)
5214 {
5215     char buf[MSG_SIZ];
5216
5217     while (fgets(buf, MSG_SIZ, f)) {
5218         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5219     }
5220
5221     fclose(f);
5222 }
5223
5224
5225 static int lastX, lastY, selectFlag, dragging;
5226
5227 void
5228 Sweep (int step)
5229 {
5230     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5231     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5232     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5233     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5234     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5235     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5236     do {
5237         promoSweep -= step;
5238         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5239         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5240         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5241         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5242         if(!step) step = -1;
5243     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5244             appData.testLegality && (promoSweep == king ||
5245             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5246     if(toX >= 0) {
5247         int victim = boards[currentMove][toY][toX];
5248         boards[currentMove][toY][toX] = promoSweep;
5249         DrawPosition(FALSE, boards[currentMove]);
5250         boards[currentMove][toY][toX] = victim;
5251     } else
5252     ChangeDragPiece(promoSweep);
5253 }
5254
5255 int
5256 PromoScroll (int x, int y)
5257 {
5258   int step = 0;
5259
5260   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5261   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5262   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5263   if(!step) return FALSE;
5264   lastX = x; lastY = y;
5265   if((promoSweep < BlackPawn) == flipView) step = -step;
5266   if(step > 0) selectFlag = 1;
5267   if(!selectFlag) Sweep(step);
5268   return FALSE;
5269 }
5270
5271 void
5272 NextPiece (int step)
5273 {
5274     ChessSquare piece = boards[currentMove][toY][toX];
5275     do {
5276         pieceSweep -= step;
5277         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5278         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5279         if(!step) step = -1;
5280     } while(PieceToChar(pieceSweep) == '.');
5281     boards[currentMove][toY][toX] = pieceSweep;
5282     DrawPosition(FALSE, boards[currentMove]);
5283     boards[currentMove][toY][toX] = piece;
5284 }
5285 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5286 void
5287 AlphaRank (char *move, int n)
5288 {
5289 //    char *p = move, c; int x, y;
5290
5291     if (appData.debugMode) {
5292         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5293     }
5294
5295     if(move[1]=='*' &&
5296        move[2]>='0' && move[2]<='9' &&
5297        move[3]>='a' && move[3]<='x'    ) {
5298         move[1] = '@';
5299         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5300         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5301     } else
5302     if(move[0]>='0' && move[0]<='9' &&
5303        move[1]>='a' && move[1]<='x' &&
5304        move[2]>='0' && move[2]<='9' &&
5305        move[3]>='a' && move[3]<='x'    ) {
5306         /* input move, Shogi -> normal */
5307         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5308         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5309         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5310         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5311     } else
5312     if(move[1]=='@' &&
5313        move[3]>='0' && move[3]<='9' &&
5314        move[2]>='a' && move[2]<='x'    ) {
5315         move[1] = '*';
5316         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5317         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5318     } else
5319     if(
5320        move[0]>='a' && move[0]<='x' &&
5321        move[3]>='0' && move[3]<='9' &&
5322        move[2]>='a' && move[2]<='x'    ) {
5323          /* output move, normal -> Shogi */
5324         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5325         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5326         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5327         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5328         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5329     }
5330     if (appData.debugMode) {
5331         fprintf(debugFP, "   out = '%s'\n", move);
5332     }
5333 }
5334
5335 char yy_textstr[8000];
5336
5337 /* Parser for moves from gnuchess, ICS, or user typein box */
5338 Boolean
5339 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5340 {
5341     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5342
5343     switch (*moveType) {
5344       case WhitePromotion:
5345       case BlackPromotion:
5346       case WhiteNonPromotion:
5347       case BlackNonPromotion:
5348       case NormalMove:
5349       case WhiteCapturesEnPassant:
5350       case BlackCapturesEnPassant:
5351       case WhiteKingSideCastle:
5352       case WhiteQueenSideCastle:
5353       case BlackKingSideCastle:
5354       case BlackQueenSideCastle:
5355       case WhiteKingSideCastleWild:
5356       case WhiteQueenSideCastleWild:
5357       case BlackKingSideCastleWild:
5358       case BlackQueenSideCastleWild:
5359       /* Code added by Tord: */
5360       case WhiteHSideCastleFR:
5361       case WhiteASideCastleFR:
5362       case BlackHSideCastleFR:
5363       case BlackASideCastleFR:
5364       /* End of code added by Tord */
5365       case IllegalMove:         /* bug or odd chess variant */
5366         *fromX = currentMoveString[0] - AAA;
5367         *fromY = currentMoveString[1] - ONE;
5368         *toX = currentMoveString[2] - AAA;
5369         *toY = currentMoveString[3] - ONE;
5370         *promoChar = currentMoveString[4];
5371         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5372             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5373     if (appData.debugMode) {
5374         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5375     }
5376             *fromX = *fromY = *toX = *toY = 0;
5377             return FALSE;
5378         }
5379         if (appData.testLegality) {
5380           return (*moveType != IllegalMove);
5381         } else {
5382           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5383                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5384         }
5385
5386       case WhiteDrop:
5387       case BlackDrop:
5388         *fromX = *moveType == WhiteDrop ?
5389           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5390           (int) CharToPiece(ToLower(currentMoveString[0]));
5391         *fromY = DROP_RANK;
5392         *toX = currentMoveString[2] - AAA;
5393         *toY = currentMoveString[3] - ONE;
5394         *promoChar = NULLCHAR;
5395         return TRUE;
5396
5397       case AmbiguousMove:
5398       case ImpossibleMove:
5399       case EndOfFile:
5400       case ElapsedTime:
5401       case Comment:
5402       case PGNTag:
5403       case NAG:
5404       case WhiteWins:
5405       case BlackWins:
5406       case GameIsDrawn:
5407       default:
5408     if (appData.debugMode) {
5409         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5410     }
5411         /* bug? */
5412         *fromX = *fromY = *toX = *toY = 0;
5413         *promoChar = NULLCHAR;
5414         return FALSE;
5415     }
5416 }
5417
5418 Boolean pushed = FALSE;
5419 char *lastParseAttempt;
5420
5421 void
5422 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5423 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5424   int fromX, fromY, toX, toY; char promoChar;
5425   ChessMove moveType;
5426   Boolean valid;
5427   int nr = 0;
5428
5429   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5430   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5431     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5432     pushed = TRUE;
5433   }
5434   endPV = forwardMostMove;
5435   do {
5436     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5437     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5438     lastParseAttempt = pv;
5439     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5440     if(!valid && nr == 0 &&
5441        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5442         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5443         // Hande case where played move is different from leading PV move
5444         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5445         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5446         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5447         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5448           endPV += 2; // if position different, keep this
5449           moveList[endPV-1][0] = fromX + AAA;
5450           moveList[endPV-1][1] = fromY + ONE;
5451           moveList[endPV-1][2] = toX + AAA;
5452           moveList[endPV-1][3] = toY + ONE;
5453           parseList[endPV-1][0] = NULLCHAR;
5454           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5455         }
5456       }
5457     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5458     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5459     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5460     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5461         valid++; // allow comments in PV
5462         continue;
5463     }
5464     nr++;
5465     if(endPV+1 > framePtr) break; // no space, truncate
5466     if(!valid) break;
5467     endPV++;
5468     CopyBoard(boards[endPV], boards[endPV-1]);
5469     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5470     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5471     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5472     CoordsToAlgebraic(boards[endPV - 1],
5473                              PosFlags(endPV - 1),
5474                              fromY, fromX, toY, toX, promoChar,
5475                              parseList[endPV - 1]);
5476   } while(valid);
5477   if(atEnd == 2) return; // used hidden, for PV conversion
5478   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5479   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5480   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5481                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5482   DrawPosition(TRUE, boards[currentMove]);
5483 }
5484
5485 int
5486 MultiPV (ChessProgramState *cps)
5487 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5488         int i;
5489         for(i=0; i<cps->nrOptions; i++)
5490             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5491                 return i;
5492         return -1;
5493 }
5494
5495 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5496
5497 Boolean
5498 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5499 {
5500         int startPV, multi, lineStart, origIndex = index;
5501         char *p, buf2[MSG_SIZ];
5502         ChessProgramState *cps = (pane ? &second : &first);
5503
5504         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5505         lastX = x; lastY = y;
5506         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5507         lineStart = startPV = index;
5508         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5509         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5510         index = startPV;
5511         do{ while(buf[index] && buf[index] != '\n') index++;
5512         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5513         buf[index] = 0;
5514         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5515                 int n = cps->option[multi].value;
5516                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5517                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5518                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5519                 cps->option[multi].value = n;
5520                 *start = *end = 0;
5521                 return FALSE;
5522         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5523                 ExcludeClick(origIndex - lineStart);
5524                 return FALSE;
5525         }
5526         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5527         *start = startPV; *end = index-1;
5528         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5529         return TRUE;
5530 }
5531
5532 char *
5533 PvToSAN (char *pv)
5534 {
5535         static char buf[10*MSG_SIZ];
5536         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5537         *buf = NULLCHAR;
5538         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5539         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5540         for(i = forwardMostMove; i<endPV; i++){
5541             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5542             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5543             k += strlen(buf+k);
5544         }
5545         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5546         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5547         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5548         endPV = savedEnd;
5549         return buf;
5550 }
5551
5552 Boolean
5553 LoadPV (int x, int y)
5554 { // called on right mouse click to load PV
5555   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5556   lastX = x; lastY = y;
5557   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5558   extendGame = FALSE;
5559   return TRUE;
5560 }
5561
5562 void
5563 UnLoadPV ()
5564 {
5565   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5566   if(endPV < 0) return;
5567   if(appData.autoCopyPV) CopyFENToClipboard();
5568   endPV = -1;
5569   if(extendGame && currentMove > forwardMostMove) {
5570         Boolean saveAnimate = appData.animate;
5571         if(pushed) {
5572             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5573                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5574             } else storedGames--; // abandon shelved tail of original game
5575         }
5576         pushed = FALSE;
5577         forwardMostMove = currentMove;
5578         currentMove = oldFMM;
5579         appData.animate = FALSE;
5580         ToNrEvent(forwardMostMove);
5581         appData.animate = saveAnimate;
5582   }
5583   currentMove = forwardMostMove;
5584   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5585   ClearPremoveHighlights();
5586   DrawPosition(TRUE, boards[currentMove]);
5587 }
5588
5589 void
5590 MovePV (int x, int y, int h)
5591 { // step through PV based on mouse coordinates (called on mouse move)
5592   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5593
5594   // we must somehow check if right button is still down (might be released off board!)
5595   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5596   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5597   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5598   if(!step) return;
5599   lastX = x; lastY = y;
5600
5601   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5602   if(endPV < 0) return;
5603   if(y < margin) step = 1; else
5604   if(y > h - margin) step = -1;
5605   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5606   currentMove += step;
5607   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5608   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5609                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5610   DrawPosition(FALSE, boards[currentMove]);
5611 }
5612
5613
5614 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5615 // All positions will have equal probability, but the current method will not provide a unique
5616 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5617 #define DARK 1
5618 #define LITE 2
5619 #define ANY 3
5620
5621 int squaresLeft[4];
5622 int piecesLeft[(int)BlackPawn];
5623 int seed, nrOfShuffles;
5624
5625 void
5626 GetPositionNumber ()
5627 {       // sets global variable seed
5628         int i;
5629
5630         seed = appData.defaultFrcPosition;
5631         if(seed < 0) { // randomize based on time for negative FRC position numbers
5632                 for(i=0; i<50; i++) seed += random();
5633                 seed = random() ^ random() >> 8 ^ random() << 8;
5634                 if(seed<0) seed = -seed;
5635         }
5636 }
5637
5638 int
5639 put (Board board, int pieceType, int rank, int n, int shade)
5640 // put the piece on the (n-1)-th empty squares of the given shade
5641 {
5642         int i;
5643
5644         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5645                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5646                         board[rank][i] = (ChessSquare) pieceType;
5647                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5648                         squaresLeft[ANY]--;
5649                         piecesLeft[pieceType]--;
5650                         return i;
5651                 }
5652         }
5653         return -1;
5654 }
5655
5656
5657 void
5658 AddOnePiece (Board board, int pieceType, int rank, int shade)
5659 // calculate where the next piece goes, (any empty square), and put it there
5660 {
5661         int i;
5662
5663         i = seed % squaresLeft[shade];
5664         nrOfShuffles *= squaresLeft[shade];
5665         seed /= squaresLeft[shade];
5666         put(board, pieceType, rank, i, shade);
5667 }
5668
5669 void
5670 AddTwoPieces (Board board, int pieceType, int rank)
5671 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5672 {
5673         int i, n=squaresLeft[ANY], j=n-1, k;
5674
5675         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5676         i = seed % k;  // pick one
5677         nrOfShuffles *= k;
5678         seed /= k;
5679         while(i >= j) i -= j--;
5680         j = n - 1 - j; i += j;
5681         put(board, pieceType, rank, j, ANY);
5682         put(board, pieceType, rank, i, ANY);
5683 }
5684
5685 void
5686 SetUpShuffle (Board board, int number)
5687 {
5688         int i, p, first=1;
5689
5690         GetPositionNumber(); nrOfShuffles = 1;
5691
5692         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5693         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5694         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5695
5696         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5697
5698         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5699             p = (int) board[0][i];
5700             if(p < (int) BlackPawn) piecesLeft[p] ++;
5701             board[0][i] = EmptySquare;
5702         }
5703
5704         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5705             // shuffles restricted to allow normal castling put KRR first
5706             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5707                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5708             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5709                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5710             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5711                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5712             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5713                 put(board, WhiteRook, 0, 0, ANY);
5714             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5715         }
5716
5717         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5718             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5719             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5720                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5721                 while(piecesLeft[p] >= 2) {
5722                     AddOnePiece(board, p, 0, LITE);
5723                     AddOnePiece(board, p, 0, DARK);
5724                 }
5725                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5726             }
5727
5728         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5729             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5730             // but we leave King and Rooks for last, to possibly obey FRC restriction
5731             if(p == (int)WhiteRook) continue;
5732             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5733             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5734         }
5735
5736         // now everything is placed, except perhaps King (Unicorn) and Rooks
5737
5738         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5739             // Last King gets castling rights
5740             while(piecesLeft[(int)WhiteUnicorn]) {
5741                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5742                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5743             }
5744
5745             while(piecesLeft[(int)WhiteKing]) {
5746                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5747                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5748             }
5749
5750
5751         } else {
5752             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5753             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5754         }
5755
5756         // Only Rooks can be left; simply place them all
5757         while(piecesLeft[(int)WhiteRook]) {
5758                 i = put(board, WhiteRook, 0, 0, ANY);
5759                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5760                         if(first) {
5761                                 first=0;
5762                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5763                         }
5764                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5765                 }
5766         }
5767         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5768             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5769         }
5770
5771         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5772 }
5773
5774 int
5775 SetCharTable (char *table, const char * map)
5776 /* [HGM] moved here from winboard.c because of its general usefulness */
5777 /*       Basically a safe strcpy that uses the last character as King */
5778 {
5779     int result = FALSE; int NrPieces;
5780
5781     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5782                     && NrPieces >= 12 && !(NrPieces&1)) {
5783         int i; /* [HGM] Accept even length from 12 to 34 */
5784
5785         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5786         for( i=0; i<NrPieces/2-1; i++ ) {
5787             table[i] = map[i];
5788             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5789         }
5790         table[(int) WhiteKing]  = map[NrPieces/2-1];
5791         table[(int) BlackKing]  = map[NrPieces-1];
5792
5793         result = TRUE;
5794     }
5795
5796     return result;
5797 }
5798
5799 void
5800 Prelude (Board board)
5801 {       // [HGM] superchess: random selection of exo-pieces
5802         int i, j, k; ChessSquare p;
5803         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5804
5805         GetPositionNumber(); // use FRC position number
5806
5807         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5808             SetCharTable(pieceToChar, appData.pieceToCharTable);
5809             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5810                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5811         }
5812
5813         j = seed%4;                 seed /= 4;
5814         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5815         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5816         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5817         j = seed%3 + (seed%3 >= j); seed /= 3;
5818         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5819         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5820         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5821         j = seed%3;                 seed /= 3;
5822         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5823         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5824         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5825         j = seed%2 + (seed%2 >= j); seed /= 2;
5826         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5827         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5828         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5829         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5830         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5831         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5832         put(board, exoPieces[0],    0, 0, ANY);
5833         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5834 }
5835
5836 void
5837 InitPosition (int redraw)
5838 {
5839     ChessSquare (* pieces)[BOARD_FILES];
5840     int i, j, pawnRow, overrule,
5841     oldx = gameInfo.boardWidth,
5842     oldy = gameInfo.boardHeight,
5843     oldh = gameInfo.holdingsWidth;
5844     static int oldv;
5845
5846     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5847
5848     /* [AS] Initialize pv info list [HGM] and game status */
5849     {
5850         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5851             pvInfoList[i].depth = 0;
5852             boards[i][EP_STATUS] = EP_NONE;
5853             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5854         }
5855
5856         initialRulePlies = 0; /* 50-move counter start */
5857
5858         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5859         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5860     }
5861
5862
5863     /* [HGM] logic here is completely changed. In stead of full positions */
5864     /* the initialized data only consist of the two backranks. The switch */
5865     /* selects which one we will use, which is than copied to the Board   */
5866     /* initialPosition, which for the rest is initialized by Pawns and    */
5867     /* empty squares. This initial position is then copied to boards[0],  */
5868     /* possibly after shuffling, so that it remains available.            */
5869
5870     gameInfo.holdingsWidth = 0; /* default board sizes */
5871     gameInfo.boardWidth    = 8;
5872     gameInfo.boardHeight   = 8;
5873     gameInfo.holdingsSize  = 0;
5874     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5875     for(i=0; i<BOARD_FILES-2; i++)
5876       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5877     initialPosition[EP_STATUS] = EP_NONE;
5878     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5879     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5880          SetCharTable(pieceNickName, appData.pieceNickNames);
5881     else SetCharTable(pieceNickName, "............");
5882     pieces = FIDEArray;
5883
5884     switch (gameInfo.variant) {
5885     case VariantFischeRandom:
5886       shuffleOpenings = TRUE;
5887     default:
5888       break;
5889     case VariantShatranj:
5890       pieces = ShatranjArray;
5891       nrCastlingRights = 0;
5892       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5893       break;
5894     case VariantMakruk:
5895       pieces = makrukArray;
5896       nrCastlingRights = 0;
5897       startedFromSetupPosition = TRUE;
5898       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5899       break;
5900     case VariantTwoKings:
5901       pieces = twoKingsArray;
5902       break;
5903     case VariantGrand:
5904       pieces = GrandArray;
5905       nrCastlingRights = 0;
5906       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5907       gameInfo.boardWidth = 10;
5908       gameInfo.boardHeight = 10;
5909       gameInfo.holdingsSize = 7;
5910       break;
5911     case VariantCapaRandom:
5912       shuffleOpenings = TRUE;
5913     case VariantCapablanca:
5914       pieces = CapablancaArray;
5915       gameInfo.boardWidth = 10;
5916       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5917       break;
5918     case VariantGothic:
5919       pieces = GothicArray;
5920       gameInfo.boardWidth = 10;
5921       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5922       break;
5923     case VariantSChess:
5924       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5925       gameInfo.holdingsSize = 7;
5926       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5927       break;
5928     case VariantJanus:
5929       pieces = JanusArray;
5930       gameInfo.boardWidth = 10;
5931       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5932       nrCastlingRights = 6;
5933         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5934         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5935         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5936         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5937         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5938         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5939       break;
5940     case VariantFalcon:
5941       pieces = FalconArray;
5942       gameInfo.boardWidth = 10;
5943       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5944       break;
5945     case VariantXiangqi:
5946       pieces = XiangqiArray;
5947       gameInfo.boardWidth  = 9;
5948       gameInfo.boardHeight = 10;
5949       nrCastlingRights = 0;
5950       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5951       break;
5952     case VariantShogi:
5953       pieces = ShogiArray;
5954       gameInfo.boardWidth  = 9;
5955       gameInfo.boardHeight = 9;
5956       gameInfo.holdingsSize = 7;
5957       nrCastlingRights = 0;
5958       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5959       break;
5960     case VariantCourier:
5961       pieces = CourierArray;
5962       gameInfo.boardWidth  = 12;
5963       nrCastlingRights = 0;
5964       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5965       break;
5966     case VariantKnightmate:
5967       pieces = KnightmateArray;
5968       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5969       break;
5970     case VariantSpartan:
5971       pieces = SpartanArray;
5972       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5973       break;
5974     case VariantFairy:
5975       pieces = fairyArray;
5976       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5977       break;
5978     case VariantGreat:
5979       pieces = GreatArray;
5980       gameInfo.boardWidth = 10;
5981       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5982       gameInfo.holdingsSize = 8;
5983       break;
5984     case VariantSuper:
5985       pieces = FIDEArray;
5986       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5987       gameInfo.holdingsSize = 8;
5988       startedFromSetupPosition = TRUE;
5989       break;
5990     case VariantCrazyhouse:
5991     case VariantBughouse:
5992       pieces = FIDEArray;
5993       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5994       gameInfo.holdingsSize = 5;
5995       break;
5996     case VariantWildCastle:
5997       pieces = FIDEArray;
5998       /* !!?shuffle with kings guaranteed to be on d or e file */
5999       shuffleOpenings = 1;
6000       break;
6001     case VariantNoCastle:
6002       pieces = FIDEArray;
6003       nrCastlingRights = 0;
6004       /* !!?unconstrained back-rank shuffle */
6005       shuffleOpenings = 1;
6006       break;
6007     }
6008
6009     overrule = 0;
6010     if(appData.NrFiles >= 0) {
6011         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6012         gameInfo.boardWidth = appData.NrFiles;
6013     }
6014     if(appData.NrRanks >= 0) {
6015         gameInfo.boardHeight = appData.NrRanks;
6016     }
6017     if(appData.holdingsSize >= 0) {
6018         i = appData.holdingsSize;
6019         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6020         gameInfo.holdingsSize = i;
6021     }
6022     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6023     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6024         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6025
6026     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6027     if(pawnRow < 1) pawnRow = 1;
6028     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6029
6030     /* User pieceToChar list overrules defaults */
6031     if(appData.pieceToCharTable != NULL)
6032         SetCharTable(pieceToChar, appData.pieceToCharTable);
6033
6034     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6035
6036         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6037             s = (ChessSquare) 0; /* account holding counts in guard band */
6038         for( i=0; i<BOARD_HEIGHT; i++ )
6039             initialPosition[i][j] = s;
6040
6041         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6042         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6043         initialPosition[pawnRow][j] = WhitePawn;
6044         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6045         if(gameInfo.variant == VariantXiangqi) {
6046             if(j&1) {
6047                 initialPosition[pawnRow][j] =
6048                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6049                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6050                    initialPosition[2][j] = WhiteCannon;
6051                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6052                 }
6053             }
6054         }
6055         if(gameInfo.variant == VariantGrand) {
6056             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6057                initialPosition[0][j] = WhiteRook;
6058                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6059             }
6060         }
6061         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6062     }
6063     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6064
6065             j=BOARD_LEFT+1;
6066             initialPosition[1][j] = WhiteBishop;
6067             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6068             j=BOARD_RGHT-2;
6069             initialPosition[1][j] = WhiteRook;
6070             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6071     }
6072
6073     if( nrCastlingRights == -1) {
6074         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6075         /*       This sets default castling rights from none to normal corners   */
6076         /* Variants with other castling rights must set them themselves above    */
6077         nrCastlingRights = 6;
6078
6079         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6080         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6081         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6082         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6083         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6084         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6085      }
6086
6087      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6088      if(gameInfo.variant == VariantGreat) { // promotion commoners
6089         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6090         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6091         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6092         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6093      }
6094      if( gameInfo.variant == VariantSChess ) {
6095       initialPosition[1][0] = BlackMarshall;
6096       initialPosition[2][0] = BlackAngel;
6097       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6098       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6099       initialPosition[1][1] = initialPosition[2][1] =
6100       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6101      }
6102   if (appData.debugMode) {
6103     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6104   }
6105     if(shuffleOpenings) {
6106         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6107         startedFromSetupPosition = TRUE;
6108     }
6109     if(startedFromPositionFile) {
6110       /* [HGM] loadPos: use PositionFile for every new game */
6111       CopyBoard(initialPosition, filePosition);
6112       for(i=0; i<nrCastlingRights; i++)
6113           initialRights[i] = filePosition[CASTLING][i];
6114       startedFromSetupPosition = TRUE;
6115     }
6116
6117     CopyBoard(boards[0], initialPosition);
6118
6119     if(oldx != gameInfo.boardWidth ||
6120        oldy != gameInfo.boardHeight ||
6121        oldv != gameInfo.variant ||
6122        oldh != gameInfo.holdingsWidth
6123                                          )
6124             InitDrawingSizes(-2 ,0);
6125
6126     oldv = gameInfo.variant;
6127     if (redraw)
6128       DrawPosition(TRUE, boards[currentMove]);
6129 }
6130
6131 void
6132 SendBoard (ChessProgramState *cps, int moveNum)
6133 {
6134     char message[MSG_SIZ];
6135
6136     if (cps->useSetboard) {
6137       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6138       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6139       SendToProgram(message, cps);
6140       free(fen);
6141
6142     } else {
6143       ChessSquare *bp;
6144       int i, j, left=0, right=BOARD_WIDTH;
6145       /* Kludge to set black to move, avoiding the troublesome and now
6146        * deprecated "black" command.
6147        */
6148       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6149         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6150
6151       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6152
6153       SendToProgram("edit\n", cps);
6154       SendToProgram("#\n", cps);
6155       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6156         bp = &boards[moveNum][i][left];
6157         for (j = left; j < right; j++, bp++) {
6158           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6159           if ((int) *bp < (int) BlackPawn) {
6160             if(j == BOARD_RGHT+1)
6161                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6162             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6163             if(message[0] == '+' || message[0] == '~') {
6164               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6165                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6166                         AAA + j, ONE + i);
6167             }
6168             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6169                 message[1] = BOARD_RGHT   - 1 - j + '1';
6170                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6171             }
6172             SendToProgram(message, cps);
6173           }
6174         }
6175       }
6176
6177       SendToProgram("c\n", cps);
6178       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6179         bp = &boards[moveNum][i][left];
6180         for (j = left; j < right; j++, bp++) {
6181           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6182           if (((int) *bp != (int) EmptySquare)
6183               && ((int) *bp >= (int) BlackPawn)) {
6184             if(j == BOARD_LEFT-2)
6185                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6186             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6187                     AAA + j, ONE + i);
6188             if(message[0] == '+' || message[0] == '~') {
6189               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6190                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6191                         AAA + j, ONE + i);
6192             }
6193             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6194                 message[1] = BOARD_RGHT   - 1 - j + '1';
6195                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6196             }
6197             SendToProgram(message, cps);
6198           }
6199         }
6200       }
6201
6202       SendToProgram(".\n", cps);
6203     }
6204     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6205 }
6206
6207 char exclusionHeader[MSG_SIZ];
6208 int exCnt, excludePtr;
6209 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6210 static Exclusion excluTab[200];
6211 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6212
6213 static void
6214 WriteMap (int s)
6215 {
6216     int j;
6217     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6218     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6219 }
6220
6221 static void
6222 ClearMap ()
6223 {
6224     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6225     excludePtr = 24; exCnt = 0;
6226     WriteMap(0);
6227 }
6228
6229 static void
6230 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6231 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6232     char buf[2*MOVE_LEN], *p;
6233     Exclusion *e = excluTab;
6234     int i;
6235     for(i=0; i<exCnt; i++)
6236         if(e[i].ff == fromX && e[i].fr == fromY &&
6237            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6238     if(i == exCnt) { // was not in exclude list; add it
6239         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6240         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6241             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6242             return; // abort
6243         }
6244         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6245         excludePtr++; e[i].mark = excludePtr++;
6246         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6247         exCnt++;
6248     }
6249     exclusionHeader[e[i].mark] = state;
6250 }
6251
6252 static int
6253 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6254 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6255     char buf[MSG_SIZ];
6256     int j, k;
6257     ChessMove moveType;
6258     if((signed char)promoChar == -1) { // kludge to indicate best move
6259         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6260             return 1; // if unparsable, abort
6261     }
6262     // update exclusion map (resolving toggle by consulting existing state)
6263     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6264     j = k%8; k >>= 3;
6265     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6266     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6267          excludeMap[k] |=   1<<j;
6268     else excludeMap[k] &= ~(1<<j);
6269     // update header
6270     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6271     // inform engine
6272     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6273     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6274     SendToBoth(buf);
6275     return (state == '+');
6276 }
6277
6278 static void
6279 ExcludeClick (int index)
6280 {
6281     int i, j;
6282     Exclusion *e = excluTab;
6283     if(index < 25) { // none, best or tail clicked
6284         if(index < 13) { // none: include all
6285             WriteMap(0); // clear map
6286             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6287             SendToBoth("include all\n"); // and inform engine
6288         } else if(index > 18) { // tail
6289             if(exclusionHeader[19] == '-') { // tail was excluded
6290                 SendToBoth("include all\n");
6291                 WriteMap(0); // clear map completely
6292                 // now re-exclude selected moves
6293                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6294                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6295             } else { // tail was included or in mixed state
6296                 SendToBoth("exclude all\n");
6297                 WriteMap(0xFF); // fill map completely
6298                 // now re-include selected moves
6299                 j = 0; // count them
6300                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6301                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6302                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6303             }
6304         } else { // best
6305             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6306         }
6307     } else {
6308         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6309             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6310             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6311             break;
6312         }
6313     }
6314 }
6315
6316 ChessSquare
6317 DefaultPromoChoice (int white)
6318 {
6319     ChessSquare result;
6320     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6321         result = WhiteFerz; // no choice
6322     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6323         result= WhiteKing; // in Suicide Q is the last thing we want
6324     else if(gameInfo.variant == VariantSpartan)
6325         result = white ? WhiteQueen : WhiteAngel;
6326     else result = WhiteQueen;
6327     if(!white) result = WHITE_TO_BLACK result;
6328     return result;
6329 }
6330
6331 static int autoQueen; // [HGM] oneclick
6332
6333 int
6334 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6335 {
6336     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6337     /* [HGM] add Shogi promotions */
6338     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6339     ChessSquare piece;
6340     ChessMove moveType;
6341     Boolean premove;
6342
6343     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6344     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6345
6346     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6347       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6348         return FALSE;
6349
6350     piece = boards[currentMove][fromY][fromX];
6351     if(gameInfo.variant == VariantShogi) {
6352         promotionZoneSize = BOARD_HEIGHT/3;
6353         highestPromotingPiece = (int)WhiteFerz;
6354     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6355         promotionZoneSize = 3;
6356     }
6357
6358     // Treat Lance as Pawn when it is not representing Amazon
6359     if(gameInfo.variant != VariantSuper) {
6360         if(piece == WhiteLance) piece = WhitePawn; else
6361         if(piece == BlackLance) piece = BlackPawn;
6362     }
6363
6364     // next weed out all moves that do not touch the promotion zone at all
6365     if((int)piece >= BlackPawn) {
6366         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6367              return FALSE;
6368         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6369     } else {
6370         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6371            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6372     }
6373
6374     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6375
6376     // weed out mandatory Shogi promotions
6377     if(gameInfo.variant == VariantShogi) {
6378         if(piece >= BlackPawn) {
6379             if(toY == 0 && piece == BlackPawn ||
6380                toY == 0 && piece == BlackQueen ||
6381                toY <= 1 && piece == BlackKnight) {
6382                 *promoChoice = '+';
6383                 return FALSE;
6384             }
6385         } else {
6386             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6387                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6388                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6389                 *promoChoice = '+';
6390                 return FALSE;
6391             }
6392         }
6393     }
6394
6395     // weed out obviously illegal Pawn moves
6396     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6397         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6398         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6399         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6400         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6401         // note we are not allowed to test for valid (non-)capture, due to premove
6402     }
6403
6404     // we either have a choice what to promote to, or (in Shogi) whether to promote
6405     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6406         *promoChoice = PieceToChar(BlackFerz);  // no choice
6407         return FALSE;
6408     }
6409     // no sense asking what we must promote to if it is going to explode...
6410     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6411         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6412         return FALSE;
6413     }
6414     // give caller the default choice even if we will not make it
6415     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6416     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6417     if(        sweepSelect && gameInfo.variant != VariantGreat
6418                            && gameInfo.variant != VariantGrand
6419                            && gameInfo.variant != VariantSuper) return FALSE;
6420     if(autoQueen) return FALSE; // predetermined
6421
6422     // suppress promotion popup on illegal moves that are not premoves
6423     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6424               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6425     if(appData.testLegality && !premove) {
6426         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6427                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6428         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6429             return FALSE;
6430     }
6431
6432     return TRUE;
6433 }
6434
6435 int
6436 InPalace (int row, int column)
6437 {   /* [HGM] for Xiangqi */
6438     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6439          column < (BOARD_WIDTH + 4)/2 &&
6440          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6441     return FALSE;
6442 }
6443
6444 int
6445 PieceForSquare (int x, int y)
6446 {
6447   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6448      return -1;
6449   else
6450      return boards[currentMove][y][x];
6451 }
6452
6453 int
6454 OKToStartUserMove (int x, int y)
6455 {
6456     ChessSquare from_piece;
6457     int white_piece;
6458
6459     if (matchMode) return FALSE;
6460     if (gameMode == EditPosition) return TRUE;
6461
6462     if (x >= 0 && y >= 0)
6463       from_piece = boards[currentMove][y][x];
6464     else
6465       from_piece = EmptySquare;
6466
6467     if (from_piece == EmptySquare) return FALSE;
6468
6469     white_piece = (int)from_piece >= (int)WhitePawn &&
6470       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6471
6472     switch (gameMode) {
6473       case AnalyzeFile:
6474       case TwoMachinesPlay:
6475       case EndOfGame:
6476         return FALSE;
6477
6478       case IcsObserving:
6479       case IcsIdle:
6480         return FALSE;
6481
6482       case MachinePlaysWhite:
6483       case IcsPlayingBlack:
6484         if (appData.zippyPlay) return FALSE;
6485         if (white_piece) {
6486             DisplayMoveError(_("You are playing Black"));
6487             return FALSE;
6488         }
6489         break;
6490
6491       case MachinePlaysBlack:
6492       case IcsPlayingWhite:
6493         if (appData.zippyPlay) return FALSE;
6494         if (!white_piece) {
6495             DisplayMoveError(_("You are playing White"));
6496             return FALSE;
6497         }
6498         break;
6499
6500       case PlayFromGameFile:
6501             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6502       case EditGame:
6503         if (!white_piece && WhiteOnMove(currentMove)) {
6504             DisplayMoveError(_("It is White's turn"));
6505             return FALSE;
6506         }
6507         if (white_piece && !WhiteOnMove(currentMove)) {
6508             DisplayMoveError(_("It is Black's turn"));
6509             return FALSE;
6510         }
6511         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6512             /* Editing correspondence game history */
6513             /* Could disallow this or prompt for confirmation */
6514             cmailOldMove = -1;
6515         }
6516         break;
6517
6518       case BeginningOfGame:
6519         if (appData.icsActive) return FALSE;
6520         if (!appData.noChessProgram) {
6521             if (!white_piece) {
6522                 DisplayMoveError(_("You are playing White"));
6523                 return FALSE;
6524             }
6525         }
6526         break;
6527
6528       case Training:
6529         if (!white_piece && WhiteOnMove(currentMove)) {
6530             DisplayMoveError(_("It is White's turn"));
6531             return FALSE;
6532         }
6533         if (white_piece && !WhiteOnMove(currentMove)) {
6534             DisplayMoveError(_("It is Black's turn"));
6535             return FALSE;
6536         }
6537         break;
6538
6539       default:
6540       case IcsExamining:
6541         break;
6542     }
6543     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6544         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6545         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6546         && gameMode != AnalyzeFile && gameMode != Training) {
6547         DisplayMoveError(_("Displayed position is not current"));
6548         return FALSE;
6549     }
6550     return TRUE;
6551 }
6552
6553 Boolean
6554 OnlyMove (int *x, int *y, Boolean captures)
6555 {
6556     DisambiguateClosure cl;
6557     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6558     switch(gameMode) {
6559       case MachinePlaysBlack:
6560       case IcsPlayingWhite:
6561       case BeginningOfGame:
6562         if(!WhiteOnMove(currentMove)) return FALSE;
6563         break;
6564       case MachinePlaysWhite:
6565       case IcsPlayingBlack:
6566         if(WhiteOnMove(currentMove)) return FALSE;
6567         break;
6568       case EditGame:
6569         break;
6570       default:
6571         return FALSE;
6572     }
6573     cl.pieceIn = EmptySquare;
6574     cl.rfIn = *y;
6575     cl.ffIn = *x;
6576     cl.rtIn = -1;
6577     cl.ftIn = -1;
6578     cl.promoCharIn = NULLCHAR;
6579     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6580     if( cl.kind == NormalMove ||
6581         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6582         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6583         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6584       fromX = cl.ff;
6585       fromY = cl.rf;
6586       *x = cl.ft;
6587       *y = cl.rt;
6588       return TRUE;
6589     }
6590     if(cl.kind != ImpossibleMove) return FALSE;
6591     cl.pieceIn = EmptySquare;
6592     cl.rfIn = -1;
6593     cl.ffIn = -1;
6594     cl.rtIn = *y;
6595     cl.ftIn = *x;
6596     cl.promoCharIn = NULLCHAR;
6597     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6598     if( cl.kind == NormalMove ||
6599         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6600         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6601         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6602       fromX = cl.ff;
6603       fromY = cl.rf;
6604       *x = cl.ft;
6605       *y = cl.rt;
6606       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6607       return TRUE;
6608     }
6609     return FALSE;
6610 }
6611
6612 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6613 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6614 int lastLoadGameUseList = FALSE;
6615 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6616 ChessMove lastLoadGameStart = EndOfFile;
6617 int doubleClick;
6618
6619 void
6620 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6621 {
6622     ChessMove moveType;
6623     ChessSquare pup;
6624     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6625
6626     /* Check if the user is playing in turn.  This is complicated because we
6627        let the user "pick up" a piece before it is his turn.  So the piece he
6628        tried to pick up may have been captured by the time he puts it down!
6629        Therefore we use the color the user is supposed to be playing in this
6630        test, not the color of the piece that is currently on the starting
6631        square---except in EditGame mode, where the user is playing both
6632        sides; fortunately there the capture race can't happen.  (It can
6633        now happen in IcsExamining mode, but that's just too bad.  The user
6634        will get a somewhat confusing message in that case.)
6635        */
6636
6637     switch (gameMode) {
6638       case AnalyzeFile:
6639       case TwoMachinesPlay:
6640       case EndOfGame:
6641       case IcsObserving:
6642       case IcsIdle:
6643         /* We switched into a game mode where moves are not accepted,
6644            perhaps while the mouse button was down. */
6645         return;
6646
6647       case MachinePlaysWhite:
6648         /* User is moving for Black */
6649         if (WhiteOnMove(currentMove)) {
6650             DisplayMoveError(_("It is White's turn"));
6651             return;
6652         }
6653         break;
6654
6655       case MachinePlaysBlack:
6656         /* User is moving for White */
6657         if (!WhiteOnMove(currentMove)) {
6658             DisplayMoveError(_("It is Black's turn"));
6659             return;
6660         }
6661         break;
6662
6663       case PlayFromGameFile:
6664             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6665       case EditGame:
6666       case IcsExamining:
6667       case BeginningOfGame:
6668       case AnalyzeMode:
6669       case Training:
6670         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6671         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6672             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6673             /* User is moving for Black */
6674             if (WhiteOnMove(currentMove)) {
6675                 DisplayMoveError(_("It is White's turn"));
6676                 return;
6677             }
6678         } else {
6679             /* User is moving for White */
6680             if (!WhiteOnMove(currentMove)) {
6681                 DisplayMoveError(_("It is Black's turn"));
6682                 return;
6683             }
6684         }
6685         break;
6686
6687       case IcsPlayingBlack:
6688         /* User is moving for Black */
6689         if (WhiteOnMove(currentMove)) {
6690             if (!appData.premove) {
6691                 DisplayMoveError(_("It is White's turn"));
6692             } else if (toX >= 0 && toY >= 0) {
6693                 premoveToX = toX;
6694                 premoveToY = toY;
6695                 premoveFromX = fromX;
6696                 premoveFromY = fromY;
6697                 premovePromoChar = promoChar;
6698                 gotPremove = 1;
6699                 if (appData.debugMode)
6700                     fprintf(debugFP, "Got premove: fromX %d,"
6701                             "fromY %d, toX %d, toY %d\n",
6702                             fromX, fromY, toX, toY);
6703             }
6704             return;
6705         }
6706         break;
6707
6708       case IcsPlayingWhite:
6709         /* User is moving for White */
6710         if (!WhiteOnMove(currentMove)) {
6711             if (!appData.premove) {
6712                 DisplayMoveError(_("It is Black's turn"));
6713             } else if (toX >= 0 && toY >= 0) {
6714                 premoveToX = toX;
6715                 premoveToY = toY;
6716                 premoveFromX = fromX;
6717                 premoveFromY = fromY;
6718                 premovePromoChar = promoChar;
6719                 gotPremove = 1;
6720                 if (appData.debugMode)
6721                     fprintf(debugFP, "Got premove: fromX %d,"
6722                             "fromY %d, toX %d, toY %d\n",
6723                             fromX, fromY, toX, toY);
6724             }
6725             return;
6726         }
6727         break;
6728
6729       default:
6730         break;
6731
6732       case EditPosition:
6733         /* EditPosition, empty square, or different color piece;
6734            click-click move is possible */
6735         if (toX == -2 || toY == -2) {
6736             boards[0][fromY][fromX] = EmptySquare;
6737             DrawPosition(FALSE, boards[currentMove]);
6738             return;
6739         } else if (toX >= 0 && toY >= 0) {
6740             boards[0][toY][toX] = boards[0][fromY][fromX];
6741             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6742                 if(boards[0][fromY][0] != EmptySquare) {
6743                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6744                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6745                 }
6746             } else
6747             if(fromX == BOARD_RGHT+1) {
6748                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6749                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6750                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6751                 }
6752             } else
6753             boards[0][fromY][fromX] = gatingPiece;
6754             DrawPosition(FALSE, boards[currentMove]);
6755             return;
6756         }
6757         return;
6758     }
6759
6760     if(toX < 0 || toY < 0) return;
6761     pup = boards[currentMove][toY][toX];
6762
6763     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6764     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6765          if( pup != EmptySquare ) return;
6766          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6767            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6768                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6769            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6770            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6771            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6772            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6773          fromY = DROP_RANK;
6774     }
6775
6776     /* [HGM] always test for legality, to get promotion info */
6777     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6778                                          fromY, fromX, toY, toX, promoChar);
6779
6780     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6781
6782     /* [HGM] but possibly ignore an IllegalMove result */
6783     if (appData.testLegality) {
6784         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6785             DisplayMoveError(_("Illegal move"));
6786             return;
6787         }
6788     }
6789
6790     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6791         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6792              ClearPremoveHighlights(); // was included
6793         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6794         return;
6795     }
6796
6797     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6798 }
6799
6800 /* Common tail of UserMoveEvent and DropMenuEvent */
6801 int
6802 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6803 {
6804     char *bookHit = 0;
6805
6806     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6807         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6808         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6809         if(WhiteOnMove(currentMove)) {
6810             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6811         } else {
6812             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6813         }
6814     }
6815
6816     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6817        move type in caller when we know the move is a legal promotion */
6818     if(moveType == NormalMove && promoChar)
6819         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6820
6821     /* [HGM] <popupFix> The following if has been moved here from
6822        UserMoveEvent(). Because it seemed to belong here (why not allow
6823        piece drops in training games?), and because it can only be
6824        performed after it is known to what we promote. */
6825     if (gameMode == Training) {
6826       /* compare the move played on the board to the next move in the
6827        * game. If they match, display the move and the opponent's response.
6828        * If they don't match, display an error message.
6829        */
6830       int saveAnimate;
6831       Board testBoard;
6832       CopyBoard(testBoard, boards[currentMove]);
6833       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6834
6835       if (CompareBoards(testBoard, boards[currentMove+1])) {
6836         ForwardInner(currentMove+1);
6837
6838         /* Autoplay the opponent's response.
6839          * if appData.animate was TRUE when Training mode was entered,
6840          * the response will be animated.
6841          */
6842         saveAnimate = appData.animate;
6843         appData.animate = animateTraining;
6844         ForwardInner(currentMove+1);
6845         appData.animate = saveAnimate;
6846
6847         /* check for the end of the game */
6848         if (currentMove >= forwardMostMove) {
6849           gameMode = PlayFromGameFile;
6850           ModeHighlight();
6851           SetTrainingModeOff();
6852           DisplayInformation(_("End of game"));
6853         }
6854       } else {
6855         DisplayError(_("Incorrect move"), 0);
6856       }
6857       return 1;
6858     }
6859
6860   /* Ok, now we know that the move is good, so we can kill
6861      the previous line in Analysis Mode */
6862   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6863                                 && currentMove < forwardMostMove) {
6864     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6865     else forwardMostMove = currentMove;
6866   }
6867
6868   ClearMap();
6869
6870   /* If we need the chess program but it's dead, restart it */
6871   ResurrectChessProgram();
6872
6873   /* A user move restarts a paused game*/
6874   if (pausing)
6875     PauseEvent();
6876
6877   thinkOutput[0] = NULLCHAR;
6878
6879   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6880
6881   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6882     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6883     return 1;
6884   }
6885
6886   if (gameMode == BeginningOfGame) {
6887     if (appData.noChessProgram) {
6888       gameMode = EditGame;
6889       SetGameInfo();
6890     } else {
6891       char buf[MSG_SIZ];
6892       gameMode = MachinePlaysBlack;
6893       StartClocks();
6894       SetGameInfo();
6895       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6896       DisplayTitle(buf);
6897       if (first.sendName) {
6898         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6899         SendToProgram(buf, &first);
6900       }
6901       StartClocks();
6902     }
6903     ModeHighlight();
6904   }
6905
6906   /* Relay move to ICS or chess engine */
6907   if (appData.icsActive) {
6908     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6909         gameMode == IcsExamining) {
6910       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6911         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6912         SendToICS("draw ");
6913         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6914       }
6915       // also send plain move, in case ICS does not understand atomic claims
6916       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6917       ics_user_moved = 1;
6918     }
6919   } else {
6920     if (first.sendTime && (gameMode == BeginningOfGame ||
6921                            gameMode == MachinePlaysWhite ||
6922                            gameMode == MachinePlaysBlack)) {
6923       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6924     }
6925     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6926          // [HGM] book: if program might be playing, let it use book
6927         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6928         first.maybeThinking = TRUE;
6929     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6930         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6931         SendBoard(&first, currentMove+1);
6932         if(second.analyzing) {
6933             if(!second.useSetboard) SendToProgram("undo\n", &second);
6934             SendBoard(&second, currentMove+1);
6935         }
6936     } else {
6937         SendMoveToProgram(forwardMostMove-1, &first);
6938         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6939     }
6940     if (currentMove == cmailOldMove + 1) {
6941       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6942     }
6943   }
6944
6945   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6946
6947   switch (gameMode) {
6948   case EditGame:
6949     if(appData.testLegality)
6950     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6951     case MT_NONE:
6952     case MT_CHECK:
6953       break;
6954     case MT_CHECKMATE:
6955     case MT_STAINMATE:
6956       if (WhiteOnMove(currentMove)) {
6957         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6958       } else {
6959         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6960       }
6961       break;
6962     case MT_STALEMATE:
6963       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6964       break;
6965     }
6966     break;
6967
6968   case MachinePlaysBlack:
6969   case MachinePlaysWhite:
6970     /* disable certain menu options while machine is thinking */
6971     SetMachineThinkingEnables();
6972     break;
6973
6974   default:
6975     break;
6976   }
6977
6978   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6979   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6980
6981   if(bookHit) { // [HGM] book: simulate book reply
6982         static char bookMove[MSG_SIZ]; // a bit generous?
6983
6984         programStats.nodes = programStats.depth = programStats.time =
6985         programStats.score = programStats.got_only_move = 0;
6986         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6987
6988         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6989         strcat(bookMove, bookHit);
6990         HandleMachineMove(bookMove, &first);
6991   }
6992   return 1;
6993 }
6994
6995 void
6996 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6997 {
6998     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6999     Markers *m = (Markers *) closure;
7000     if(rf == fromY && ff == fromX)
7001         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7002                          || kind == WhiteCapturesEnPassant
7003                          || kind == BlackCapturesEnPassant);
7004     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7005 }
7006
7007 void
7008 MarkTargetSquares (int clear)
7009 {
7010   int x, y;
7011   if(clear) // no reason to ever suppress clearing
7012     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7013   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7014      !appData.testLegality || gameMode == EditPosition) return;
7015   if(!clear) {
7016     int capt = 0;
7017     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7018     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7019       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7020       if(capt)
7021       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7022     }
7023   }
7024   DrawPosition(FALSE, NULL);
7025 }
7026
7027 int
7028 Explode (Board board, int fromX, int fromY, int toX, int toY)
7029 {
7030     if(gameInfo.variant == VariantAtomic &&
7031        (board[toY][toX] != EmptySquare ||                     // capture?
7032         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7033                          board[fromY][fromX] == BlackPawn   )
7034       )) {
7035         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7036         return TRUE;
7037     }
7038     return FALSE;
7039 }
7040
7041 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7042
7043 int
7044 CanPromote (ChessSquare piece, int y)
7045 {
7046         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7047         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7048         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7049            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7050            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7051                                                   gameInfo.variant == VariantMakruk) return FALSE;
7052         return (piece == BlackPawn && y == 1 ||
7053                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7054                 piece == BlackLance && y == 1 ||
7055                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7056 }
7057
7058 void
7059 LeftClick (ClickType clickType, int xPix, int yPix)
7060 {
7061     int x, y;
7062     Boolean saveAnimate;
7063     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7064     char promoChoice = NULLCHAR;
7065     ChessSquare piece;
7066     static TimeMark lastClickTime, prevClickTime;
7067
7068     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7069
7070     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7071
7072     if (clickType == Press) ErrorPopDown();
7073
7074     x = EventToSquare(xPix, BOARD_WIDTH);
7075     y = EventToSquare(yPix, BOARD_HEIGHT);
7076     if (!flipView && y >= 0) {
7077         y = BOARD_HEIGHT - 1 - y;
7078     }
7079     if (flipView && x >= 0) {
7080         x = BOARD_WIDTH - 1 - x;
7081     }
7082
7083     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7084         defaultPromoChoice = promoSweep;
7085         promoSweep = EmptySquare;   // terminate sweep
7086         promoDefaultAltered = TRUE;
7087         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7088     }
7089
7090     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7091         if(clickType == Release) return; // ignore upclick of click-click destination
7092         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7093         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7094         if(gameInfo.holdingsWidth &&
7095                 (WhiteOnMove(currentMove)
7096                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7097                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7098             // click in right holdings, for determining promotion piece
7099             ChessSquare p = boards[currentMove][y][x];
7100             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7101             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7102             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7103                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7104                 fromX = fromY = -1;
7105                 return;
7106             }
7107         }
7108         DrawPosition(FALSE, boards[currentMove]);
7109         return;
7110     }
7111
7112     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7113     if(clickType == Press
7114             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7115               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7116               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7117         return;
7118
7119     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7120         // could be static click on premove from-square: abort premove
7121         gotPremove = 0;
7122         ClearPremoveHighlights();
7123     }
7124
7125     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7126         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7127
7128     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7129         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7130                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7131         defaultPromoChoice = DefaultPromoChoice(side);
7132     }
7133
7134     autoQueen = appData.alwaysPromoteToQueen;
7135
7136     if (fromX == -1) {
7137       int originalY = y;
7138       gatingPiece = EmptySquare;
7139       if (clickType != Press) {
7140         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7141             DragPieceEnd(xPix, yPix); dragging = 0;
7142             DrawPosition(FALSE, NULL);
7143         }
7144         return;
7145       }
7146       doubleClick = FALSE;
7147       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7148         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7149       }
7150       fromX = x; fromY = y; toX = toY = -1;
7151       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7152          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7153          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7154             /* First square */
7155             if (OKToStartUserMove(fromX, fromY)) {
7156                 second = 0;
7157                 MarkTargetSquares(0);
7158                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7159                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7160                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7161                     promoSweep = defaultPromoChoice;
7162                     selectFlag = 0; lastX = xPix; lastY = yPix;
7163                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7164                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7165                 }
7166                 if (appData.highlightDragging) {
7167                     SetHighlights(fromX, fromY, -1, -1);
7168                 } else {
7169                     ClearHighlights();
7170                 }
7171             } else fromX = fromY = -1;
7172             return;
7173         }
7174     }
7175
7176     /* fromX != -1 */
7177     if (clickType == Press && gameMode != EditPosition) {
7178         ChessSquare fromP;
7179         ChessSquare toP;
7180         int frc;
7181
7182         // ignore off-board to clicks
7183         if(y < 0 || x < 0) return;
7184
7185         /* Check if clicking again on the same color piece */
7186         fromP = boards[currentMove][fromY][fromX];
7187         toP = boards[currentMove][y][x];
7188         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7189         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7190              WhitePawn <= toP && toP <= WhiteKing &&
7191              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7192              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7193             (BlackPawn <= fromP && fromP <= BlackKing &&
7194              BlackPawn <= toP && toP <= BlackKing &&
7195              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7196              !(fromP == BlackKing && toP == BlackRook && frc))) {
7197             /* Clicked again on same color piece -- changed his mind */
7198             second = (x == fromX && y == fromY);
7199             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7200                 second = FALSE; // first double-click rather than scond click
7201                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7202             }
7203             promoDefaultAltered = FALSE;
7204             MarkTargetSquares(1);
7205            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7206             if (appData.highlightDragging) {
7207                 SetHighlights(x, y, -1, -1);
7208             } else {
7209                 ClearHighlights();
7210             }
7211             if (OKToStartUserMove(x, y)) {
7212                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7213                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7214                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7215                  gatingPiece = boards[currentMove][fromY][fromX];
7216                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7217                 fromX = x;
7218                 fromY = y; dragging = 1;
7219                 MarkTargetSquares(0);
7220                 DragPieceBegin(xPix, yPix, FALSE);
7221                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7222                     promoSweep = defaultPromoChoice;
7223                     selectFlag = 0; lastX = xPix; lastY = yPix;
7224                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7225                 }
7226             }
7227            }
7228            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7229            second = FALSE;
7230         }
7231         // ignore clicks on holdings
7232         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7233     }
7234
7235     if (clickType == Release && x == fromX && y == fromY) {
7236         DragPieceEnd(xPix, yPix); dragging = 0;
7237         if(clearFlag) {
7238             // a deferred attempt to click-click move an empty square on top of a piece
7239             boards[currentMove][y][x] = EmptySquare;
7240             ClearHighlights();
7241             DrawPosition(FALSE, boards[currentMove]);
7242             fromX = fromY = -1; clearFlag = 0;
7243             return;
7244         }
7245         if (appData.animateDragging) {
7246             /* Undo animation damage if any */
7247             DrawPosition(FALSE, NULL);
7248         }
7249         if (second || sweepSelecting) {
7250             /* Second up/down in same square; just abort move */
7251             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7252             second = sweepSelecting = 0;
7253             fromX = fromY = -1;
7254             gatingPiece = EmptySquare;
7255             ClearHighlights();
7256             gotPremove = 0;
7257             ClearPremoveHighlights();
7258         } else {
7259             /* First upclick in same square; start click-click mode */
7260             SetHighlights(x, y, -1, -1);
7261         }
7262         return;
7263     }
7264
7265     clearFlag = 0;
7266
7267     /* we now have a different from- and (possibly off-board) to-square */
7268     /* Completed move */
7269     if(!sweepSelecting) {
7270         toX = x;
7271         toY = y;
7272     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7273
7274     saveAnimate = appData.animate;
7275     if (clickType == Press) {
7276         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7277             // must be Edit Position mode with empty-square selected
7278             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7279             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7280             return;
7281         }
7282         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7283           if(appData.sweepSelect) {
7284             ChessSquare piece = boards[currentMove][fromY][fromX];
7285             promoSweep = defaultPromoChoice;
7286             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7287             selectFlag = 0; lastX = xPix; lastY = yPix;
7288             Sweep(0); // Pawn that is going to promote: preview promotion piece
7289             sweepSelecting = 1;
7290             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7291             MarkTargetSquares(1);
7292           }
7293           return; // promo popup appears on up-click
7294         }
7295         /* Finish clickclick move */
7296         if (appData.animate || appData.highlightLastMove) {
7297             SetHighlights(fromX, fromY, toX, toY);
7298         } else {
7299             ClearHighlights();
7300         }
7301     } else {
7302 #if 0
7303 // [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
7304         /* Finish drag move */
7305         if (appData.highlightLastMove) {
7306             SetHighlights(fromX, fromY, toX, toY);
7307         } else {
7308             ClearHighlights();
7309         }
7310 #endif
7311         DragPieceEnd(xPix, yPix); dragging = 0;
7312         /* Don't animate move and drag both */
7313         appData.animate = FALSE;
7314     }
7315
7316     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7317     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7318         ChessSquare piece = boards[currentMove][fromY][fromX];
7319         if(gameMode == EditPosition && piece != EmptySquare &&
7320            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7321             int n;
7322
7323             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7324                 n = PieceToNumber(piece - (int)BlackPawn);
7325                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7326                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7327                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7328             } else
7329             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7330                 n = PieceToNumber(piece);
7331                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7332                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7333                 boards[currentMove][n][BOARD_WIDTH-2]++;
7334             }
7335             boards[currentMove][fromY][fromX] = EmptySquare;
7336         }
7337         ClearHighlights();
7338         fromX = fromY = -1;
7339         MarkTargetSquares(1);
7340         DrawPosition(TRUE, boards[currentMove]);
7341         return;
7342     }
7343
7344     // off-board moves should not be highlighted
7345     if(x < 0 || y < 0) ClearHighlights();
7346
7347     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7348
7349     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7350         SetHighlights(fromX, fromY, toX, toY);
7351         MarkTargetSquares(1);
7352         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7353             // [HGM] super: promotion to captured piece selected from holdings
7354             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7355             promotionChoice = TRUE;
7356             // kludge follows to temporarily execute move on display, without promoting yet
7357             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7358             boards[currentMove][toY][toX] = p;
7359             DrawPosition(FALSE, boards[currentMove]);
7360             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7361             boards[currentMove][toY][toX] = q;
7362             DisplayMessage("Click in holdings to choose piece", "");
7363             return;
7364         }
7365         PromotionPopUp();
7366     } else {
7367         int oldMove = currentMove;
7368         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7369         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7370         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7371         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7372            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7373             DrawPosition(TRUE, boards[currentMove]);
7374         MarkTargetSquares(1);
7375         fromX = fromY = -1;
7376     }
7377     appData.animate = saveAnimate;
7378     if (appData.animate || appData.animateDragging) {
7379         /* Undo animation damage if needed */
7380         DrawPosition(FALSE, NULL);
7381     }
7382 }
7383
7384 int
7385 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7386 {   // front-end-free part taken out of PieceMenuPopup
7387     int whichMenu; int xSqr, ySqr;
7388
7389     if(seekGraphUp) { // [HGM] seekgraph
7390         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7391         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7392         return -2;
7393     }
7394
7395     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7396          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7397         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7398         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7399         if(action == Press)   {
7400             originalFlip = flipView;
7401             flipView = !flipView; // temporarily flip board to see game from partners perspective
7402             DrawPosition(TRUE, partnerBoard);
7403             DisplayMessage(partnerStatus, "");
7404             partnerUp = TRUE;
7405         } else if(action == Release) {
7406             flipView = originalFlip;
7407             DrawPosition(TRUE, boards[currentMove]);
7408             partnerUp = FALSE;
7409         }
7410         return -2;
7411     }
7412
7413     xSqr = EventToSquare(x, BOARD_WIDTH);
7414     ySqr = EventToSquare(y, BOARD_HEIGHT);
7415     if (action == Release) {
7416         if(pieceSweep != EmptySquare) {
7417             EditPositionMenuEvent(pieceSweep, toX, toY);
7418             pieceSweep = EmptySquare;
7419         } else UnLoadPV(); // [HGM] pv
7420     }
7421     if (action != Press) return -2; // return code to be ignored
7422     switch (gameMode) {
7423       case IcsExamining:
7424         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7425       case EditPosition:
7426         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7427         if (xSqr < 0 || ySqr < 0) return -1;
7428         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7429         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7430         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7431         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7432         NextPiece(0);
7433         return 2; // grab
7434       case IcsObserving:
7435         if(!appData.icsEngineAnalyze) return -1;
7436       case IcsPlayingWhite:
7437       case IcsPlayingBlack:
7438         if(!appData.zippyPlay) goto noZip;
7439       case AnalyzeMode:
7440       case AnalyzeFile:
7441       case MachinePlaysWhite:
7442       case MachinePlaysBlack:
7443       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7444         if (!appData.dropMenu) {
7445           LoadPV(x, y);
7446           return 2; // flag front-end to grab mouse events
7447         }
7448         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7449            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7450       case EditGame:
7451       noZip:
7452         if (xSqr < 0 || ySqr < 0) return -1;
7453         if (!appData.dropMenu || appData.testLegality &&
7454             gameInfo.variant != VariantBughouse &&
7455             gameInfo.variant != VariantCrazyhouse) return -1;
7456         whichMenu = 1; // drop menu
7457         break;
7458       default:
7459         return -1;
7460     }
7461
7462     if (((*fromX = xSqr) < 0) ||
7463         ((*fromY = ySqr) < 0)) {
7464         *fromX = *fromY = -1;
7465         return -1;
7466     }
7467     if (flipView)
7468       *fromX = BOARD_WIDTH - 1 - *fromX;
7469     else
7470       *fromY = BOARD_HEIGHT - 1 - *fromY;
7471
7472     return whichMenu;
7473 }
7474
7475 void
7476 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7477 {
7478 //    char * hint = lastHint;
7479     FrontEndProgramStats stats;
7480
7481     stats.which = cps == &first ? 0 : 1;
7482     stats.depth = cpstats->depth;
7483     stats.nodes = cpstats->nodes;
7484     stats.score = cpstats->score;
7485     stats.time = cpstats->time;
7486     stats.pv = cpstats->movelist;
7487     stats.hint = lastHint;
7488     stats.an_move_index = 0;
7489     stats.an_move_count = 0;
7490
7491     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7492         stats.hint = cpstats->move_name;
7493         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7494         stats.an_move_count = cpstats->nr_moves;
7495     }
7496
7497     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
7498
7499     SetProgramStats( &stats );
7500 }
7501
7502 void
7503 ClearEngineOutputPane (int which)
7504 {
7505     static FrontEndProgramStats dummyStats;
7506     dummyStats.which = which;
7507     dummyStats.pv = "#";
7508     SetProgramStats( &dummyStats );
7509 }
7510
7511 #define MAXPLAYERS 500
7512
7513 char *
7514 TourneyStandings (int display)
7515 {
7516     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7517     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7518     char result, *p, *names[MAXPLAYERS];
7519
7520     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7521         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7522     names[0] = p = strdup(appData.participants);
7523     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7524
7525     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7526
7527     while(result = appData.results[nr]) {
7528         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7529         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7530         wScore = bScore = 0;
7531         switch(result) {
7532           case '+': wScore = 2; break;
7533           case '-': bScore = 2; break;
7534           case '=': wScore = bScore = 1; break;
7535           case ' ':
7536           case '*': return strdup("busy"); // tourney not finished
7537         }
7538         score[w] += wScore;
7539         score[b] += bScore;
7540         games[w]++;
7541         games[b]++;
7542         nr++;
7543     }
7544     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7545     for(w=0; w<nPlayers; w++) {
7546         bScore = -1;
7547         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7548         ranking[w] = b; points[w] = bScore; score[b] = -2;
7549     }
7550     p = malloc(nPlayers*34+1);
7551     for(w=0; w<nPlayers && w<display; w++)
7552         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7553     free(names[0]);
7554     return p;
7555 }
7556
7557 void
7558 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7559 {       // count all piece types
7560         int p, f, r;
7561         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7562         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7563         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7564                 p = board[r][f];
7565                 pCnt[p]++;
7566                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7567                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7568                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7569                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7570                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7571                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7572         }
7573 }
7574
7575 int
7576 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7577 {
7578         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7579         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7580
7581         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7582         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7583         if(myPawns == 2 && nMine == 3) // KPP
7584             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7585         if(myPawns == 1 && nMine == 2) // KP
7586             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7587         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7588             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7589         if(myPawns) return FALSE;
7590         if(pCnt[WhiteRook+side])
7591             return pCnt[BlackRook-side] ||
7592                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7593                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7594                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7595         if(pCnt[WhiteCannon+side]) {
7596             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7597             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7598         }
7599         if(pCnt[WhiteKnight+side])
7600             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7601         return FALSE;
7602 }
7603
7604 int
7605 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7606 {
7607         VariantClass v = gameInfo.variant;
7608
7609         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7610         if(v == VariantShatranj) return TRUE; // always winnable through baring
7611         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7612         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7613
7614         if(v == VariantXiangqi) {
7615                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7616
7617                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7618                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7619                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7620                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7621                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7622                 if(stale) // we have at least one last-rank P plus perhaps C
7623                     return majors // KPKX
7624                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7625                 else // KCA*E*
7626                     return pCnt[WhiteFerz+side] // KCAK
7627                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7628                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7629                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7630
7631         } else if(v == VariantKnightmate) {
7632                 if(nMine == 1) return FALSE;
7633                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7634         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7635                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7636
7637                 if(nMine == 1) return FALSE; // bare King
7638                 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
7639                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7640                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7641                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7642                 if(pCnt[WhiteKnight+side])
7643                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7644                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7645                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7646                 if(nBishops)
7647                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7648                 if(pCnt[WhiteAlfil+side])
7649                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7650                 if(pCnt[WhiteWazir+side])
7651                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7652         }
7653
7654         return TRUE;
7655 }
7656
7657 int
7658 CompareWithRights (Board b1, Board b2)
7659 {
7660     int rights = 0;
7661     if(!CompareBoards(b1, b2)) return FALSE;
7662     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7663     /* compare castling rights */
7664     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7665            rights++; /* King lost rights, while rook still had them */
7666     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7667         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7668            rights++; /* but at least one rook lost them */
7669     }
7670     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7671            rights++;
7672     if( b1[CASTLING][5] != NoRights ) {
7673         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7674            rights++;
7675     }
7676     return rights == 0;
7677 }
7678
7679 int
7680 Adjudicate (ChessProgramState *cps)
7681 {       // [HGM] some adjudications useful with buggy engines
7682         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7683         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7684         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7685         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7686         int k, drop, count = 0; static int bare = 1;
7687         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7688         Boolean canAdjudicate = !appData.icsActive;
7689
7690         // most tests only when we understand the game, i.e. legality-checking on
7691             if( appData.testLegality )
7692             {   /* [HGM] Some more adjudications for obstinate engines */
7693                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7694                 static int moveCount = 6;
7695                 ChessMove result;
7696                 char *reason = NULL;
7697
7698                 /* Count what is on board. */
7699                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7700
7701                 /* Some material-based adjudications that have to be made before stalemate test */
7702                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7703                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7704                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7705                      if(canAdjudicate && appData.checkMates) {
7706                          if(engineOpponent)
7707                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7708                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7709                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7710                          return 1;
7711                      }
7712                 }
7713
7714                 /* Bare King in Shatranj (loses) or Losers (wins) */
7715                 if( nrW == 1 || nrB == 1) {
7716                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7717                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7718                      if(canAdjudicate && appData.checkMates) {
7719                          if(engineOpponent)
7720                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7721                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7722                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7723                          return 1;
7724                      }
7725                   } else
7726                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7727                   {    /* bare King */
7728                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7729                         if(canAdjudicate && appData.checkMates) {
7730                             /* but only adjudicate if adjudication enabled */
7731                             if(engineOpponent)
7732                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7733                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7734                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7735                             return 1;
7736                         }
7737                   }
7738                 } else bare = 1;
7739
7740
7741             // don't wait for engine to announce game end if we can judge ourselves
7742             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7743               case MT_CHECK:
7744                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7745                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7746                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7747                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7748                             checkCnt++;
7749                         if(checkCnt >= 2) {
7750                             reason = "Xboard adjudication: 3rd check";
7751                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7752                             break;
7753                         }
7754                     }
7755                 }
7756               case MT_NONE:
7757               default:
7758                 break;
7759               case MT_STALEMATE:
7760               case MT_STAINMATE:
7761                 reason = "Xboard adjudication: Stalemate";
7762                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7763                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7764                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7765                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7766                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7767                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7768                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7769                                                                         EP_CHECKMATE : EP_WINS);
7770                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7771                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7772                 }
7773                 break;
7774               case MT_CHECKMATE:
7775                 reason = "Xboard adjudication: Checkmate";
7776                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7777                 if(gameInfo.variant == VariantShogi) {
7778                     if(forwardMostMove > backwardMostMove
7779                        && moveList[forwardMostMove-1][1] == '@'
7780                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7781                         reason = "XBoard adjudication: pawn-drop mate";
7782                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7783                     }
7784                 }
7785                 break;
7786             }
7787
7788                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7789                     case EP_STALEMATE:
7790                         result = GameIsDrawn; break;
7791                     case EP_CHECKMATE:
7792                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7793                     case EP_WINS:
7794                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7795                     default:
7796                         result = EndOfFile;
7797                 }
7798                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7799                     if(engineOpponent)
7800                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7801                     GameEnds( result, reason, GE_XBOARD );
7802                     return 1;
7803                 }
7804
7805                 /* Next absolutely insufficient mating material. */
7806                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7807                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7808                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7809
7810                      /* always flag draws, for judging claims */
7811                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7812
7813                      if(canAdjudicate && appData.materialDraws) {
7814                          /* but only adjudicate them if adjudication enabled */
7815                          if(engineOpponent) {
7816                            SendToProgram("force\n", engineOpponent); // suppress reply
7817                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7818                          }
7819                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7820                          return 1;
7821                      }
7822                 }
7823
7824                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7825                 if(gameInfo.variant == VariantXiangqi ?
7826                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7827                  : nrW + nrB == 4 &&
7828                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7829                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7830                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7831                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7832                    ) ) {
7833                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7834                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7835                           if(engineOpponent) {
7836                             SendToProgram("force\n", engineOpponent); // suppress reply
7837                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7838                           }
7839                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7840                           return 1;
7841                      }
7842                 } else moveCount = 6;
7843             }
7844
7845         // Repetition draws and 50-move rule can be applied independently of legality testing
7846
7847                 /* Check for rep-draws */
7848                 count = 0;
7849                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7850                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7851                 for(k = forwardMostMove-2;
7852                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7853                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7854                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7855                     k-=2)
7856                 {   int rights=0;
7857                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7858                         /* compare castling rights */
7859                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7860                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7861                                 rights++; /* King lost rights, while rook still had them */
7862                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7863                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7864                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7865                                    rights++; /* but at least one rook lost them */
7866                         }
7867                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7868                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7869                                 rights++;
7870                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7871                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7872                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7873                                    rights++;
7874                         }
7875                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7876                             && appData.drawRepeats > 1) {
7877                              /* adjudicate after user-specified nr of repeats */
7878                              int result = GameIsDrawn;
7879                              char *details = "XBoard adjudication: repetition draw";
7880                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7881                                 // [HGM] xiangqi: check for forbidden perpetuals
7882                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7883                                 for(m=forwardMostMove; m>k; m-=2) {
7884                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7885                                         ourPerpetual = 0; // the current mover did not always check
7886                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7887                                         hisPerpetual = 0; // the opponent did not always check
7888                                 }
7889                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7890                                                                         ourPerpetual, hisPerpetual);
7891                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7892                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7893                                     details = "Xboard adjudication: perpetual checking";
7894                                 } else
7895                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7896                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7897                                 } else
7898                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7899                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7900                                         result = BlackWins;
7901                                         details = "Xboard adjudication: repetition";
7902                                     }
7903                                 } else // it must be XQ
7904                                 // Now check for perpetual chases
7905                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7906                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7907                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7908                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7909                                         static char resdet[MSG_SIZ];
7910                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7911                                         details = resdet;
7912                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7913                                     } else
7914                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7915                                         break; // Abort repetition-checking loop.
7916                                 }
7917                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7918                              }
7919                              if(engineOpponent) {
7920                                SendToProgram("force\n", engineOpponent); // suppress reply
7921                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7922                              }
7923                              GameEnds( result, details, GE_XBOARD );
7924                              return 1;
7925                         }
7926                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7927                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7928                     }
7929                 }
7930
7931                 /* Now we test for 50-move draws. Determine ply count */
7932                 count = forwardMostMove;
7933                 /* look for last irreversble move */
7934                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7935                     count--;
7936                 /* if we hit starting position, add initial plies */
7937                 if( count == backwardMostMove )
7938                     count -= initialRulePlies;
7939                 count = forwardMostMove - count;
7940                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7941                         // adjust reversible move counter for checks in Xiangqi
7942                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7943                         if(i < backwardMostMove) i = backwardMostMove;
7944                         while(i <= forwardMostMove) {
7945                                 lastCheck = inCheck; // check evasion does not count
7946                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7947                                 if(inCheck || lastCheck) count--; // check does not count
7948                                 i++;
7949                         }
7950                 }
7951                 if( count >= 100)
7952                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7953                          /* this is used to judge if draw claims are legal */
7954                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7955                          if(engineOpponent) {
7956                            SendToProgram("force\n", engineOpponent); // suppress reply
7957                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7958                          }
7959                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7960                          return 1;
7961                 }
7962
7963                 /* if draw offer is pending, treat it as a draw claim
7964                  * when draw condition present, to allow engines a way to
7965                  * claim draws before making their move to avoid a race
7966                  * condition occurring after their move
7967                  */
7968                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7969                          char *p = NULL;
7970                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7971                              p = "Draw claim: 50-move rule";
7972                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7973                              p = "Draw claim: 3-fold repetition";
7974                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7975                              p = "Draw claim: insufficient mating material";
7976                          if( p != NULL && canAdjudicate) {
7977                              if(engineOpponent) {
7978                                SendToProgram("force\n", engineOpponent); // suppress reply
7979                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7980                              }
7981                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7982                              return 1;
7983                          }
7984                 }
7985
7986                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7987                     if(engineOpponent) {
7988                       SendToProgram("force\n", engineOpponent); // suppress reply
7989                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7990                     }
7991                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7992                     return 1;
7993                 }
7994         return 0;
7995 }
7996
7997 char *
7998 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7999 {   // [HGM] book: this routine intercepts moves to simulate book replies
8000     char *bookHit = NULL;
8001
8002     //first determine if the incoming move brings opponent into his book
8003     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8004         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8005     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8006     if(bookHit != NULL && !cps->bookSuspend) {
8007         // make sure opponent is not going to reply after receiving move to book position
8008         SendToProgram("force\n", cps);
8009         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8010     }
8011     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8012     // now arrange restart after book miss
8013     if(bookHit) {
8014         // after a book hit we never send 'go', and the code after the call to this routine
8015         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8016         char buf[MSG_SIZ], *move = bookHit;
8017         if(cps->useSAN) {
8018             int fromX, fromY, toX, toY;
8019             char promoChar;
8020             ChessMove moveType;
8021             move = buf + 30;
8022             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8023                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8024                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8025                                     PosFlags(forwardMostMove),
8026                                     fromY, fromX, toY, toX, promoChar, move);
8027             } else {
8028                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8029                 bookHit = NULL;
8030             }
8031         }
8032         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8033         SendToProgram(buf, cps);
8034         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8035     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8036         SendToProgram("go\n", cps);
8037         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8038     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8039         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8040             SendToProgram("go\n", cps);
8041         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8042     }
8043     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8044 }
8045
8046 int
8047 LoadError (char *errmess, ChessProgramState *cps)
8048 {   // unloads engine and switches back to -ncp mode if it was first
8049     if(cps->initDone) return FALSE;
8050     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8051     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8052     cps->pr = NoProc;
8053     if(cps == &first) {
8054         appData.noChessProgram = TRUE;
8055         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8056         gameMode = BeginningOfGame; ModeHighlight();
8057         SetNCPMode();
8058     }
8059     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8060     DisplayMessage("", ""); // erase waiting message
8061     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8062     return TRUE;
8063 }
8064
8065 char *savedMessage;
8066 ChessProgramState *savedState;
8067 void
8068 DeferredBookMove (void)
8069 {
8070         if(savedState->lastPing != savedState->lastPong)
8071                     ScheduleDelayedEvent(DeferredBookMove, 10);
8072         else
8073         HandleMachineMove(savedMessage, savedState);
8074 }
8075
8076 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8077 static ChessProgramState *stalledEngine;
8078 static char stashedInputMove[MSG_SIZ];
8079
8080 void
8081 HandleMachineMove (char *message, ChessProgramState *cps)
8082 {
8083     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8084     char realname[MSG_SIZ];
8085     int fromX, fromY, toX, toY;
8086     ChessMove moveType;
8087     char promoChar;
8088     char *p, *pv=buf1;
8089     int machineWhite, oldError;
8090     char *bookHit;
8091
8092     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8093         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8094         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8095             DisplayError(_("Invalid pairing from pairing engine"), 0);
8096             return;
8097         }
8098         pairingReceived = 1;
8099         NextMatchGame();
8100         return; // Skim the pairing messages here.
8101     }
8102
8103     oldError = cps->userError; cps->userError = 0;
8104
8105 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8106     /*
8107      * Kludge to ignore BEL characters
8108      */
8109     while (*message == '\007') message++;
8110
8111     /*
8112      * [HGM] engine debug message: ignore lines starting with '#' character
8113      */
8114     if(cps->debug && *message == '#') return;
8115
8116     /*
8117      * Look for book output
8118      */
8119     if (cps == &first && bookRequested) {
8120         if (message[0] == '\t' || message[0] == ' ') {
8121             /* Part of the book output is here; append it */
8122             strcat(bookOutput, message);
8123             strcat(bookOutput, "  \n");
8124             return;
8125         } else if (bookOutput[0] != NULLCHAR) {
8126             /* All of book output has arrived; display it */
8127             char *p = bookOutput;
8128             while (*p != NULLCHAR) {
8129                 if (*p == '\t') *p = ' ';
8130                 p++;
8131             }
8132             DisplayInformation(bookOutput);
8133             bookRequested = FALSE;
8134             /* Fall through to parse the current output */
8135         }
8136     }
8137
8138     /*
8139      * Look for machine move.
8140      */
8141     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8142         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8143     {
8144         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8145             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8146             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8147             stalledEngine = cps;
8148             if(appData.ponderNextMove) { // bring opponent out of ponder
8149                 if(gameMode == TwoMachinesPlay) {
8150                     if(cps->other->pause)
8151                         PauseEngine(cps->other);
8152                     else
8153                         SendToProgram("easy\n", cps->other);
8154                 }
8155             }
8156             StopClocks();
8157             return;
8158         }
8159
8160         /* This method is only useful on engines that support ping */
8161         if (cps->lastPing != cps->lastPong) {
8162           if (gameMode == BeginningOfGame) {
8163             /* Extra move from before last new; ignore */
8164             if (appData.debugMode) {
8165                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8166             }
8167           } else {
8168             if (appData.debugMode) {
8169                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8170                         cps->which, gameMode);
8171             }
8172
8173             SendToProgram("undo\n", cps);
8174           }
8175           return;
8176         }
8177
8178         switch (gameMode) {
8179           case BeginningOfGame:
8180             /* Extra move from before last reset; ignore */
8181             if (appData.debugMode) {
8182                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8183             }
8184             return;
8185
8186           case EndOfGame:
8187           case IcsIdle:
8188           default:
8189             /* Extra move after we tried to stop.  The mode test is
8190                not a reliable way of detecting this problem, but it's
8191                the best we can do on engines that don't support ping.
8192             */
8193             if (appData.debugMode) {
8194                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8195                         cps->which, gameMode);
8196             }
8197             SendToProgram("undo\n", cps);
8198             return;
8199
8200           case MachinePlaysWhite:
8201           case IcsPlayingWhite:
8202             machineWhite = TRUE;
8203             break;
8204
8205           case MachinePlaysBlack:
8206           case IcsPlayingBlack:
8207             machineWhite = FALSE;
8208             break;
8209
8210           case TwoMachinesPlay:
8211             machineWhite = (cps->twoMachinesColor[0] == 'w');
8212             break;
8213         }
8214         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8215             if (appData.debugMode) {
8216                 fprintf(debugFP,
8217                         "Ignoring move out of turn by %s, gameMode %d"
8218                         ", forwardMost %d\n",
8219                         cps->which, gameMode, forwardMostMove);
8220             }
8221             return;
8222         }
8223
8224         if(cps->alphaRank) AlphaRank(machineMove, 4);
8225         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8226                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8227             /* Machine move could not be parsed; ignore it. */
8228           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8229                     machineMove, _(cps->which));
8230             DisplayMoveError(buf1);
8231             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8232                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8233             if (gameMode == TwoMachinesPlay) {
8234               GameEnds(machineWhite ? BlackWins : WhiteWins,
8235                        buf1, GE_XBOARD);
8236             }
8237             return;
8238         }
8239
8240         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8241         /* So we have to redo legality test with true e.p. status here,  */
8242         /* to make sure an illegal e.p. capture does not slip through,   */
8243         /* to cause a forfeit on a justified illegal-move complaint      */
8244         /* of the opponent.                                              */
8245         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8246            ChessMove moveType;
8247            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8248                              fromY, fromX, toY, toX, promoChar);
8249             if(moveType == IllegalMove) {
8250               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8251                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8252                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8253                            buf1, GE_XBOARD);
8254                 return;
8255            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8256            /* [HGM] Kludge to handle engines that send FRC-style castling
8257               when they shouldn't (like TSCP-Gothic) */
8258            switch(moveType) {
8259              case WhiteASideCastleFR:
8260              case BlackASideCastleFR:
8261                toX+=2;
8262                currentMoveString[2]++;
8263                break;
8264              case WhiteHSideCastleFR:
8265              case BlackHSideCastleFR:
8266                toX--;
8267                currentMoveString[2]--;
8268                break;
8269              default: ; // nothing to do, but suppresses warning of pedantic compilers
8270            }
8271         }
8272         hintRequested = FALSE;
8273         lastHint[0] = NULLCHAR;
8274         bookRequested = FALSE;
8275         /* Program may be pondering now */
8276         cps->maybeThinking = TRUE;
8277         if (cps->sendTime == 2) cps->sendTime = 1;
8278         if (cps->offeredDraw) cps->offeredDraw--;
8279
8280         /* [AS] Save move info*/
8281         pvInfoList[ forwardMostMove ].score = programStats.score;
8282         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8283         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8284
8285         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8286
8287         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8288         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8289             int count = 0;
8290
8291             while( count < adjudicateLossPlies ) {
8292                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8293
8294                 if( count & 1 ) {
8295                     score = -score; /* Flip score for winning side */
8296                 }
8297
8298                 if( score > adjudicateLossThreshold ) {
8299                     break;
8300                 }
8301
8302                 count++;
8303             }
8304
8305             if( count >= adjudicateLossPlies ) {
8306                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8307
8308                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8309                     "Xboard adjudication",
8310                     GE_XBOARD );
8311
8312                 return;
8313             }
8314         }
8315
8316         if(Adjudicate(cps)) {
8317             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8318             return; // [HGM] adjudicate: for all automatic game ends
8319         }
8320
8321 #if ZIPPY
8322         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8323             first.initDone) {
8324           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8325                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8326                 SendToICS("draw ");
8327                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8328           }
8329           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8330           ics_user_moved = 1;
8331           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8332                 char buf[3*MSG_SIZ];
8333
8334                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8335                         programStats.score / 100.,
8336                         programStats.depth,
8337                         programStats.time / 100.,
8338                         (unsigned int)programStats.nodes,
8339                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8340                         programStats.movelist);
8341                 SendToICS(buf);
8342 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8343           }
8344         }
8345 #endif
8346
8347         /* [AS] Clear stats for next move */
8348         ClearProgramStats();
8349         thinkOutput[0] = NULLCHAR;
8350         hiddenThinkOutputState = 0;
8351
8352         bookHit = NULL;
8353         if (gameMode == TwoMachinesPlay) {
8354             /* [HGM] relaying draw offers moved to after reception of move */
8355             /* and interpreting offer as claim if it brings draw condition */
8356             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8357                 SendToProgram("draw\n", cps->other);
8358             }
8359             if (cps->other->sendTime) {
8360                 SendTimeRemaining(cps->other,
8361                                   cps->other->twoMachinesColor[0] == 'w');
8362             }
8363             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8364             if (firstMove && !bookHit) {
8365                 firstMove = FALSE;
8366                 if (cps->other->useColors) {
8367                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8368                 }
8369                 SendToProgram("go\n", cps->other);
8370             }
8371             cps->other->maybeThinking = TRUE;
8372         }
8373
8374         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8375
8376         if (!pausing && appData.ringBellAfterMoves) {
8377             RingBell();
8378         }
8379
8380         /*
8381          * Reenable menu items that were disabled while
8382          * machine was thinking
8383          */
8384         if (gameMode != TwoMachinesPlay)
8385             SetUserThinkingEnables();
8386
8387         // [HGM] book: after book hit opponent has received move and is now in force mode
8388         // force the book reply into it, and then fake that it outputted this move by jumping
8389         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8390         if(bookHit) {
8391                 static char bookMove[MSG_SIZ]; // a bit generous?
8392
8393                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8394                 strcat(bookMove, bookHit);
8395                 message = bookMove;
8396                 cps = cps->other;
8397                 programStats.nodes = programStats.depth = programStats.time =
8398                 programStats.score = programStats.got_only_move = 0;
8399                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8400
8401                 if(cps->lastPing != cps->lastPong) {
8402                     savedMessage = message; // args for deferred call
8403                     savedState = cps;
8404                     ScheduleDelayedEvent(DeferredBookMove, 10);
8405                     return;
8406                 }
8407                 goto FakeBookMove;
8408         }
8409
8410         return;
8411     }
8412
8413     /* Set special modes for chess engines.  Later something general
8414      *  could be added here; for now there is just one kludge feature,
8415      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8416      *  when "xboard" is given as an interactive command.
8417      */
8418     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8419         cps->useSigint = FALSE;
8420         cps->useSigterm = FALSE;
8421     }
8422     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8423       ParseFeatures(message+8, cps);
8424       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8425     }
8426
8427     if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8428                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8429       int dummy, s=6; char buf[MSG_SIZ];
8430       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8431       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8432       if(startedFromSetupPosition) return;
8433       ParseFEN(boards[0], &dummy, message+s);
8434       DrawPosition(TRUE, boards[0]);
8435       startedFromSetupPosition = TRUE;
8436       return;
8437     }
8438     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8439      * want this, I was asked to put it in, and obliged.
8440      */
8441     if (!strncmp(message, "setboard ", 9)) {
8442         Board initial_position;
8443
8444         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8445
8446         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8447             DisplayError(_("Bad FEN received from engine"), 0);
8448             return ;
8449         } else {
8450            Reset(TRUE, FALSE);
8451            CopyBoard(boards[0], initial_position);
8452            initialRulePlies = FENrulePlies;
8453            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8454            else gameMode = MachinePlaysBlack;
8455            DrawPosition(FALSE, boards[currentMove]);
8456         }
8457         return;
8458     }
8459
8460     /*
8461      * Look for communication commands
8462      */
8463     if (!strncmp(message, "telluser ", 9)) {
8464         if(message[9] == '\\' && message[10] == '\\')
8465             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8466         PlayTellSound();
8467         DisplayNote(message + 9);
8468         return;
8469     }
8470     if (!strncmp(message, "tellusererror ", 14)) {
8471         cps->userError = 1;
8472         if(message[14] == '\\' && message[15] == '\\')
8473             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8474         PlayTellSound();
8475         DisplayError(message + 14, 0);
8476         return;
8477     }
8478     if (!strncmp(message, "tellopponent ", 13)) {
8479       if (appData.icsActive) {
8480         if (loggedOn) {
8481           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8482           SendToICS(buf1);
8483         }
8484       } else {
8485         DisplayNote(message + 13);
8486       }
8487       return;
8488     }
8489     if (!strncmp(message, "tellothers ", 11)) {
8490       if (appData.icsActive) {
8491         if (loggedOn) {
8492           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8493           SendToICS(buf1);
8494         }
8495       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8496       return;
8497     }
8498     if (!strncmp(message, "tellall ", 8)) {
8499       if (appData.icsActive) {
8500         if (loggedOn) {
8501           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8502           SendToICS(buf1);
8503         }
8504       } else {
8505         DisplayNote(message + 8);
8506       }
8507       return;
8508     }
8509     if (strncmp(message, "warning", 7) == 0) {
8510         /* Undocumented feature, use tellusererror in new code */
8511         DisplayError(message, 0);
8512         return;
8513     }
8514     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8515         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8516         strcat(realname, " query");
8517         AskQuestion(realname, buf2, buf1, cps->pr);
8518         return;
8519     }
8520     /* Commands from the engine directly to ICS.  We don't allow these to be
8521      *  sent until we are logged on. Crafty kibitzes have been known to
8522      *  interfere with the login process.
8523      */
8524     if (loggedOn) {
8525         if (!strncmp(message, "tellics ", 8)) {
8526             SendToICS(message + 8);
8527             SendToICS("\n");
8528             return;
8529         }
8530         if (!strncmp(message, "tellicsnoalias ", 15)) {
8531             SendToICS(ics_prefix);
8532             SendToICS(message + 15);
8533             SendToICS("\n");
8534             return;
8535         }
8536         /* The following are for backward compatibility only */
8537         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8538             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8539             SendToICS(ics_prefix);
8540             SendToICS(message);
8541             SendToICS("\n");
8542             return;
8543         }
8544     }
8545     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8546         return;
8547     }
8548     /*
8549      * If the move is illegal, cancel it and redraw the board.
8550      * Also deal with other error cases.  Matching is rather loose
8551      * here to accommodate engines written before the spec.
8552      */
8553     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8554         strncmp(message, "Error", 5) == 0) {
8555         if (StrStr(message, "name") ||
8556             StrStr(message, "rating") || StrStr(message, "?") ||
8557             StrStr(message, "result") || StrStr(message, "board") ||
8558             StrStr(message, "bk") || StrStr(message, "computer") ||
8559             StrStr(message, "variant") || StrStr(message, "hint") ||
8560             StrStr(message, "random") || StrStr(message, "depth") ||
8561             StrStr(message, "accepted")) {
8562             return;
8563         }
8564         if (StrStr(message, "protover")) {
8565           /* Program is responding to input, so it's apparently done
8566              initializing, and this error message indicates it is
8567              protocol version 1.  So we don't need to wait any longer
8568              for it to initialize and send feature commands. */
8569           FeatureDone(cps, 1);
8570           cps->protocolVersion = 1;
8571           return;
8572         }
8573         cps->maybeThinking = FALSE;
8574
8575         if (StrStr(message, "draw")) {
8576             /* Program doesn't have "draw" command */
8577             cps->sendDrawOffers = 0;
8578             return;
8579         }
8580         if (cps->sendTime != 1 &&
8581             (StrStr(message, "time") || StrStr(message, "otim"))) {
8582           /* Program apparently doesn't have "time" or "otim" command */
8583           cps->sendTime = 0;
8584           return;
8585         }
8586         if (StrStr(message, "analyze")) {
8587             cps->analysisSupport = FALSE;
8588             cps->analyzing = FALSE;
8589 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8590             EditGameEvent(); // [HGM] try to preserve loaded game
8591             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8592             DisplayError(buf2, 0);
8593             return;
8594         }
8595         if (StrStr(message, "(no matching move)st")) {
8596           /* Special kludge for GNU Chess 4 only */
8597           cps->stKludge = TRUE;
8598           SendTimeControl(cps, movesPerSession, timeControl,
8599                           timeIncrement, appData.searchDepth,
8600                           searchTime);
8601           return;
8602         }
8603         if (StrStr(message, "(no matching move)sd")) {
8604           /* Special kludge for GNU Chess 4 only */
8605           cps->sdKludge = TRUE;
8606           SendTimeControl(cps, movesPerSession, timeControl,
8607                           timeIncrement, appData.searchDepth,
8608                           searchTime);
8609           return;
8610         }
8611         if (!StrStr(message, "llegal")) {
8612             return;
8613         }
8614         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8615             gameMode == IcsIdle) return;
8616         if (forwardMostMove <= backwardMostMove) return;
8617         if (pausing) PauseEvent();
8618       if(appData.forceIllegal) {
8619             // [HGM] illegal: machine refused move; force position after move into it
8620           SendToProgram("force\n", cps);
8621           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8622                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8623                 // when black is to move, while there might be nothing on a2 or black
8624                 // might already have the move. So send the board as if white has the move.
8625                 // But first we must change the stm of the engine, as it refused the last move
8626                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8627                 if(WhiteOnMove(forwardMostMove)) {
8628                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8629                     SendBoard(cps, forwardMostMove); // kludgeless board
8630                 } else {
8631                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8632                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8633                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8634                 }
8635           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8636             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8637                  gameMode == TwoMachinesPlay)
8638               SendToProgram("go\n", cps);
8639             return;
8640       } else
8641         if (gameMode == PlayFromGameFile) {
8642             /* Stop reading this game file */
8643             gameMode = EditGame;
8644             ModeHighlight();
8645         }
8646         /* [HGM] illegal-move claim should forfeit game when Xboard */
8647         /* only passes fully legal moves                            */
8648         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8649             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8650                                 "False illegal-move claim", GE_XBOARD );
8651             return; // do not take back move we tested as valid
8652         }
8653         currentMove = forwardMostMove-1;
8654         DisplayMove(currentMove-1); /* before DisplayMoveError */
8655         SwitchClocks(forwardMostMove-1); // [HGM] race
8656         DisplayBothClocks();
8657         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8658                 parseList[currentMove], _(cps->which));
8659         DisplayMoveError(buf1);
8660         DrawPosition(FALSE, boards[currentMove]);
8661
8662         SetUserThinkingEnables();
8663         return;
8664     }
8665     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8666         /* Program has a broken "time" command that
8667            outputs a string not ending in newline.
8668            Don't use it. */
8669         cps->sendTime = 0;
8670     }
8671
8672     /*
8673      * If chess program startup fails, exit with an error message.
8674      * Attempts to recover here are futile. [HGM] Well, we try anyway
8675      */
8676     if ((StrStr(message, "unknown host") != NULL)
8677         || (StrStr(message, "No remote directory") != NULL)
8678         || (StrStr(message, "not found") != NULL)
8679         || (StrStr(message, "No such file") != NULL)
8680         || (StrStr(message, "can't alloc") != NULL)
8681         || (StrStr(message, "Permission denied") != NULL)) {
8682
8683         cps->maybeThinking = FALSE;
8684         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8685                 _(cps->which), cps->program, cps->host, message);
8686         RemoveInputSource(cps->isr);
8687         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8688             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8689             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8690         }
8691         return;
8692     }
8693
8694     /*
8695      * Look for hint output
8696      */
8697     if (sscanf(message, "Hint: %s", buf1) == 1) {
8698         if (cps == &first && hintRequested) {
8699             hintRequested = FALSE;
8700             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8701                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8702                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8703                                     PosFlags(forwardMostMove),
8704                                     fromY, fromX, toY, toX, promoChar, buf1);
8705                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8706                 DisplayInformation(buf2);
8707             } else {
8708                 /* Hint move could not be parsed!? */
8709               snprintf(buf2, sizeof(buf2),
8710                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8711                         buf1, _(cps->which));
8712                 DisplayError(buf2, 0);
8713             }
8714         } else {
8715           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8716         }
8717         return;
8718     }
8719
8720     /*
8721      * Ignore other messages if game is not in progress
8722      */
8723     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8724         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8725
8726     /*
8727      * look for win, lose, draw, or draw offer
8728      */
8729     if (strncmp(message, "1-0", 3) == 0) {
8730         char *p, *q, *r = "";
8731         p = strchr(message, '{');
8732         if (p) {
8733             q = strchr(p, '}');
8734             if (q) {
8735                 *q = NULLCHAR;
8736                 r = p + 1;
8737             }
8738         }
8739         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8740         return;
8741     } else if (strncmp(message, "0-1", 3) == 0) {
8742         char *p, *q, *r = "";
8743         p = strchr(message, '{');
8744         if (p) {
8745             q = strchr(p, '}');
8746             if (q) {
8747                 *q = NULLCHAR;
8748                 r = p + 1;
8749             }
8750         }
8751         /* Kludge for Arasan 4.1 bug */
8752         if (strcmp(r, "Black resigns") == 0) {
8753             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8754             return;
8755         }
8756         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8757         return;
8758     } else if (strncmp(message, "1/2", 3) == 0) {
8759         char *p, *q, *r = "";
8760         p = strchr(message, '{');
8761         if (p) {
8762             q = strchr(p, '}');
8763             if (q) {
8764                 *q = NULLCHAR;
8765                 r = p + 1;
8766             }
8767         }
8768
8769         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8770         return;
8771
8772     } else if (strncmp(message, "White resign", 12) == 0) {
8773         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8774         return;
8775     } else if (strncmp(message, "Black resign", 12) == 0) {
8776         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8777         return;
8778     } else if (strncmp(message, "White matches", 13) == 0 ||
8779                strncmp(message, "Black matches", 13) == 0   ) {
8780         /* [HGM] ignore GNUShogi noises */
8781         return;
8782     } else if (strncmp(message, "White", 5) == 0 &&
8783                message[5] != '(' &&
8784                StrStr(message, "Black") == NULL) {
8785         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8786         return;
8787     } else if (strncmp(message, "Black", 5) == 0 &&
8788                message[5] != '(') {
8789         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8790         return;
8791     } else if (strcmp(message, "resign") == 0 ||
8792                strcmp(message, "computer resigns") == 0) {
8793         switch (gameMode) {
8794           case MachinePlaysBlack:
8795           case IcsPlayingBlack:
8796             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8797             break;
8798           case MachinePlaysWhite:
8799           case IcsPlayingWhite:
8800             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8801             break;
8802           case TwoMachinesPlay:
8803             if (cps->twoMachinesColor[0] == 'w')
8804               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8805             else
8806               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8807             break;
8808           default:
8809             /* can't happen */
8810             break;
8811         }
8812         return;
8813     } else if (strncmp(message, "opponent mates", 14) == 0) {
8814         switch (gameMode) {
8815           case MachinePlaysBlack:
8816           case IcsPlayingBlack:
8817             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8818             break;
8819           case MachinePlaysWhite:
8820           case IcsPlayingWhite:
8821             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8822             break;
8823           case TwoMachinesPlay:
8824             if (cps->twoMachinesColor[0] == 'w')
8825               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8826             else
8827               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8828             break;
8829           default:
8830             /* can't happen */
8831             break;
8832         }
8833         return;
8834     } else if (strncmp(message, "computer mates", 14) == 0) {
8835         switch (gameMode) {
8836           case MachinePlaysBlack:
8837           case IcsPlayingBlack:
8838             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8839             break;
8840           case MachinePlaysWhite:
8841           case IcsPlayingWhite:
8842             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8843             break;
8844           case TwoMachinesPlay:
8845             if (cps->twoMachinesColor[0] == 'w')
8846               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8847             else
8848               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8849             break;
8850           default:
8851             /* can't happen */
8852             break;
8853         }
8854         return;
8855     } else if (strncmp(message, "checkmate", 9) == 0) {
8856         if (WhiteOnMove(forwardMostMove)) {
8857             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8858         } else {
8859             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8860         }
8861         return;
8862     } else if (strstr(message, "Draw") != NULL ||
8863                strstr(message, "game is a draw") != NULL) {
8864         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8865         return;
8866     } else if (strstr(message, "offer") != NULL &&
8867                strstr(message, "draw") != NULL) {
8868 #if ZIPPY
8869         if (appData.zippyPlay && first.initDone) {
8870             /* Relay offer to ICS */
8871             SendToICS(ics_prefix);
8872             SendToICS("draw\n");
8873         }
8874 #endif
8875         cps->offeredDraw = 2; /* valid until this engine moves twice */
8876         if (gameMode == TwoMachinesPlay) {
8877             if (cps->other->offeredDraw) {
8878                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8879             /* [HGM] in two-machine mode we delay relaying draw offer      */
8880             /* until after we also have move, to see if it is really claim */
8881             }
8882         } else if (gameMode == MachinePlaysWhite ||
8883                    gameMode == MachinePlaysBlack) {
8884           if (userOfferedDraw) {
8885             DisplayInformation(_("Machine accepts your draw offer"));
8886             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8887           } else {
8888             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8889           }
8890         }
8891     }
8892
8893
8894     /*
8895      * Look for thinking output
8896      */
8897     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8898           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8899                                 ) {
8900         int plylev, mvleft, mvtot, curscore, time;
8901         char mvname[MOVE_LEN];
8902         u64 nodes; // [DM]
8903         char plyext;
8904         int ignore = FALSE;
8905         int prefixHint = FALSE;
8906         mvname[0] = NULLCHAR;
8907
8908         switch (gameMode) {
8909           case MachinePlaysBlack:
8910           case IcsPlayingBlack:
8911             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8912             break;
8913           case MachinePlaysWhite:
8914           case IcsPlayingWhite:
8915             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8916             break;
8917           case AnalyzeMode:
8918           case AnalyzeFile:
8919             break;
8920           case IcsObserving: /* [DM] icsEngineAnalyze */
8921             if (!appData.icsEngineAnalyze) ignore = TRUE;
8922             break;
8923           case TwoMachinesPlay:
8924             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8925                 ignore = TRUE;
8926             }
8927             break;
8928           default:
8929             ignore = TRUE;
8930             break;
8931         }
8932
8933         if (!ignore) {
8934             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8935             buf1[0] = NULLCHAR;
8936             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8937                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8938
8939                 if (plyext != ' ' && plyext != '\t') {
8940                     time *= 100;
8941                 }
8942
8943                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8944                 if( cps->scoreIsAbsolute &&
8945                     ( gameMode == MachinePlaysBlack ||
8946                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8947                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8948                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8949                      !WhiteOnMove(currentMove)
8950                     ) )
8951                 {
8952                     curscore = -curscore;
8953                 }
8954
8955                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8956
8957                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8958                         char buf[MSG_SIZ];
8959                         FILE *f;
8960                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8961                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8962                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8963                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8964                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8965                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8966                                 fclose(f);
8967                         } else DisplayError(_("failed writing PV"), 0);
8968                 }
8969
8970                 tempStats.depth = plylev;
8971                 tempStats.nodes = nodes;
8972                 tempStats.time = time;
8973                 tempStats.score = curscore;
8974                 tempStats.got_only_move = 0;
8975
8976                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8977                         int ticklen;
8978
8979                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8980                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8981                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8982                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8983                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8984                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8985                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8986                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8987                 }
8988
8989                 /* Buffer overflow protection */
8990                 if (pv[0] != NULLCHAR) {
8991                     if (strlen(pv) >= sizeof(tempStats.movelist)
8992                         && appData.debugMode) {
8993                         fprintf(debugFP,
8994                                 "PV is too long; using the first %u bytes.\n",
8995                                 (unsigned) sizeof(tempStats.movelist) - 1);
8996                     }
8997
8998                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8999                 } else {
9000                     sprintf(tempStats.movelist, " no PV\n");
9001                 }
9002
9003                 if (tempStats.seen_stat) {
9004                     tempStats.ok_to_send = 1;
9005                 }
9006
9007                 if (strchr(tempStats.movelist, '(') != NULL) {
9008                     tempStats.line_is_book = 1;
9009                     tempStats.nr_moves = 0;
9010                     tempStats.moves_left = 0;
9011                 } else {
9012                     tempStats.line_is_book = 0;
9013                 }
9014
9015                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9016                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9017
9018                 SendProgramStatsToFrontend( cps, &tempStats );
9019
9020                 /*
9021                     [AS] Protect the thinkOutput buffer from overflow... this
9022                     is only useful if buf1 hasn't overflowed first!
9023                 */
9024                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9025                          plylev,
9026                          (gameMode == TwoMachinesPlay ?
9027                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9028                          ((double) curscore) / 100.0,
9029                          prefixHint ? lastHint : "",
9030                          prefixHint ? " " : "" );
9031
9032                 if( buf1[0] != NULLCHAR ) {
9033                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9034
9035                     if( strlen(pv) > max_len ) {
9036                         if( appData.debugMode) {
9037                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9038                         }
9039                         pv[max_len+1] = '\0';
9040                     }
9041
9042                     strcat( thinkOutput, pv);
9043                 }
9044
9045                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9046                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9047                     DisplayMove(currentMove - 1);
9048                 }
9049                 return;
9050
9051             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9052                 /* crafty (9.25+) says "(only move) <move>"
9053                  * if there is only 1 legal move
9054                  */
9055                 sscanf(p, "(only move) %s", buf1);
9056                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9057                 sprintf(programStats.movelist, "%s (only move)", buf1);
9058                 programStats.depth = 1;
9059                 programStats.nr_moves = 1;
9060                 programStats.moves_left = 1;
9061                 programStats.nodes = 1;
9062                 programStats.time = 1;
9063                 programStats.got_only_move = 1;
9064
9065                 /* Not really, but we also use this member to
9066                    mean "line isn't going to change" (Crafty
9067                    isn't searching, so stats won't change) */
9068                 programStats.line_is_book = 1;
9069
9070                 SendProgramStatsToFrontend( cps, &programStats );
9071
9072                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9073                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9074                     DisplayMove(currentMove - 1);
9075                 }
9076                 return;
9077             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9078                               &time, &nodes, &plylev, &mvleft,
9079                               &mvtot, mvname) >= 5) {
9080                 /* The stat01: line is from Crafty (9.29+) in response
9081                    to the "." command */
9082                 programStats.seen_stat = 1;
9083                 cps->maybeThinking = TRUE;
9084
9085                 if (programStats.got_only_move || !appData.periodicUpdates)
9086                   return;
9087
9088                 programStats.depth = plylev;
9089                 programStats.time = time;
9090                 programStats.nodes = nodes;
9091                 programStats.moves_left = mvleft;
9092                 programStats.nr_moves = mvtot;
9093                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9094                 programStats.ok_to_send = 1;
9095                 programStats.movelist[0] = '\0';
9096
9097                 SendProgramStatsToFrontend( cps, &programStats );
9098
9099                 return;
9100
9101             } else if (strncmp(message,"++",2) == 0) {
9102                 /* Crafty 9.29+ outputs this */
9103                 programStats.got_fail = 2;
9104                 return;
9105
9106             } else if (strncmp(message,"--",2) == 0) {
9107                 /* Crafty 9.29+ outputs this */
9108                 programStats.got_fail = 1;
9109                 return;
9110
9111             } else if (thinkOutput[0] != NULLCHAR &&
9112                        strncmp(message, "    ", 4) == 0) {
9113                 unsigned message_len;
9114
9115                 p = message;
9116                 while (*p && *p == ' ') p++;
9117
9118                 message_len = strlen( p );
9119
9120                 /* [AS] Avoid buffer overflow */
9121                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9122                     strcat(thinkOutput, " ");
9123                     strcat(thinkOutput, p);
9124                 }
9125
9126                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9127                     strcat(programStats.movelist, " ");
9128                     strcat(programStats.movelist, p);
9129                 }
9130
9131                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9132                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9133                     DisplayMove(currentMove - 1);
9134                 }
9135                 return;
9136             }
9137         }
9138         else {
9139             buf1[0] = NULLCHAR;
9140
9141             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9142                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9143             {
9144                 ChessProgramStats cpstats;
9145
9146                 if (plyext != ' ' && plyext != '\t') {
9147                     time *= 100;
9148                 }
9149
9150                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9151                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9152                     curscore = -curscore;
9153                 }
9154
9155                 cpstats.depth = plylev;
9156                 cpstats.nodes = nodes;
9157                 cpstats.time = time;
9158                 cpstats.score = curscore;
9159                 cpstats.got_only_move = 0;
9160                 cpstats.movelist[0] = '\0';
9161
9162                 if (buf1[0] != NULLCHAR) {
9163                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9164                 }
9165
9166                 cpstats.ok_to_send = 0;
9167                 cpstats.line_is_book = 0;
9168                 cpstats.nr_moves = 0;
9169                 cpstats.moves_left = 0;
9170
9171                 SendProgramStatsToFrontend( cps, &cpstats );
9172             }
9173         }
9174     }
9175 }
9176
9177
9178 /* Parse a game score from the character string "game", and
9179    record it as the history of the current game.  The game
9180    score is NOT assumed to start from the standard position.
9181    The display is not updated in any way.
9182    */
9183 void
9184 ParseGameHistory (char *game)
9185 {
9186     ChessMove moveType;
9187     int fromX, fromY, toX, toY, boardIndex;
9188     char promoChar;
9189     char *p, *q;
9190     char buf[MSG_SIZ];
9191
9192     if (appData.debugMode)
9193       fprintf(debugFP, "Parsing game history: %s\n", game);
9194
9195     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9196     gameInfo.site = StrSave(appData.icsHost);
9197     gameInfo.date = PGNDate();
9198     gameInfo.round = StrSave("-");
9199
9200     /* Parse out names of players */
9201     while (*game == ' ') game++;
9202     p = buf;
9203     while (*game != ' ') *p++ = *game++;
9204     *p = NULLCHAR;
9205     gameInfo.white = StrSave(buf);
9206     while (*game == ' ') game++;
9207     p = buf;
9208     while (*game != ' ' && *game != '\n') *p++ = *game++;
9209     *p = NULLCHAR;
9210     gameInfo.black = StrSave(buf);
9211
9212     /* Parse moves */
9213     boardIndex = blackPlaysFirst ? 1 : 0;
9214     yynewstr(game);
9215     for (;;) {
9216         yyboardindex = boardIndex;
9217         moveType = (ChessMove) Myylex();
9218         switch (moveType) {
9219           case IllegalMove:             /* maybe suicide chess, etc. */
9220   if (appData.debugMode) {
9221     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9222     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9223     setbuf(debugFP, NULL);
9224   }
9225           case WhitePromotion:
9226           case BlackPromotion:
9227           case WhiteNonPromotion:
9228           case BlackNonPromotion:
9229           case NormalMove:
9230           case WhiteCapturesEnPassant:
9231           case BlackCapturesEnPassant:
9232           case WhiteKingSideCastle:
9233           case WhiteQueenSideCastle:
9234           case BlackKingSideCastle:
9235           case BlackQueenSideCastle:
9236           case WhiteKingSideCastleWild:
9237           case WhiteQueenSideCastleWild:
9238           case BlackKingSideCastleWild:
9239           case BlackQueenSideCastleWild:
9240           /* PUSH Fabien */
9241           case WhiteHSideCastleFR:
9242           case WhiteASideCastleFR:
9243           case BlackHSideCastleFR:
9244           case BlackASideCastleFR:
9245           /* POP Fabien */
9246             fromX = currentMoveString[0] - AAA;
9247             fromY = currentMoveString[1] - ONE;
9248             toX = currentMoveString[2] - AAA;
9249             toY = currentMoveString[3] - ONE;
9250             promoChar = currentMoveString[4];
9251             break;
9252           case WhiteDrop:
9253           case BlackDrop:
9254             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9255             fromX = moveType == WhiteDrop ?
9256               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9257             (int) CharToPiece(ToLower(currentMoveString[0]));
9258             fromY = DROP_RANK;
9259             toX = currentMoveString[2] - AAA;
9260             toY = currentMoveString[3] - ONE;
9261             promoChar = NULLCHAR;
9262             break;
9263           case AmbiguousMove:
9264             /* bug? */
9265             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9266   if (appData.debugMode) {
9267     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9268     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9269     setbuf(debugFP, NULL);
9270   }
9271             DisplayError(buf, 0);
9272             return;
9273           case ImpossibleMove:
9274             /* bug? */
9275             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9276   if (appData.debugMode) {
9277     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9278     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9279     setbuf(debugFP, NULL);
9280   }
9281             DisplayError(buf, 0);
9282             return;
9283           case EndOfFile:
9284             if (boardIndex < backwardMostMove) {
9285                 /* Oops, gap.  How did that happen? */
9286                 DisplayError(_("Gap in move list"), 0);
9287                 return;
9288             }
9289             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9290             if (boardIndex > forwardMostMove) {
9291                 forwardMostMove = boardIndex;
9292             }
9293             return;
9294           case ElapsedTime:
9295             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9296                 strcat(parseList[boardIndex-1], " ");
9297                 strcat(parseList[boardIndex-1], yy_text);
9298             }
9299             continue;
9300           case Comment:
9301           case PGNTag:
9302           case NAG:
9303           default:
9304             /* ignore */
9305             continue;
9306           case WhiteWins:
9307           case BlackWins:
9308           case GameIsDrawn:
9309           case GameUnfinished:
9310             if (gameMode == IcsExamining) {
9311                 if (boardIndex < backwardMostMove) {
9312                     /* Oops, gap.  How did that happen? */
9313                     return;
9314                 }
9315                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9316                 return;
9317             }
9318             gameInfo.result = moveType;
9319             p = strchr(yy_text, '{');
9320             if (p == NULL) p = strchr(yy_text, '(');
9321             if (p == NULL) {
9322                 p = yy_text;
9323                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9324             } else {
9325                 q = strchr(p, *p == '{' ? '}' : ')');
9326                 if (q != NULL) *q = NULLCHAR;
9327                 p++;
9328             }
9329             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9330             gameInfo.resultDetails = StrSave(p);
9331             continue;
9332         }
9333         if (boardIndex >= forwardMostMove &&
9334             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9335             backwardMostMove = blackPlaysFirst ? 1 : 0;
9336             return;
9337         }
9338         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9339                                  fromY, fromX, toY, toX, promoChar,
9340                                  parseList[boardIndex]);
9341         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9342         /* currentMoveString is set as a side-effect of yylex */
9343         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9344         strcat(moveList[boardIndex], "\n");
9345         boardIndex++;
9346         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9347         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9348           case MT_NONE:
9349           case MT_STALEMATE:
9350           default:
9351             break;
9352           case MT_CHECK:
9353             if(gameInfo.variant != VariantShogi)
9354                 strcat(parseList[boardIndex - 1], "+");
9355             break;
9356           case MT_CHECKMATE:
9357           case MT_STAINMATE:
9358             strcat(parseList[boardIndex - 1], "#");
9359             break;
9360         }
9361     }
9362 }
9363
9364
9365 /* Apply a move to the given board  */
9366 void
9367 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9368 {
9369   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9370   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9371
9372     /* [HGM] compute & store e.p. status and castling rights for new position */
9373     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9374
9375       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9376       oldEP = (signed char)board[EP_STATUS];
9377       board[EP_STATUS] = EP_NONE;
9378
9379   if (fromY == DROP_RANK) {
9380         /* must be first */
9381         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9382             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9383             return;
9384         }
9385         piece = board[toY][toX] = (ChessSquare) fromX;
9386   } else {
9387       int i;
9388
9389       if( board[toY][toX] != EmptySquare )
9390            board[EP_STATUS] = EP_CAPTURE;
9391
9392       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9393            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9394                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9395       } else
9396       if( board[fromY][fromX] == WhitePawn ) {
9397            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9398                board[EP_STATUS] = EP_PAWN_MOVE;
9399            if( toY-fromY==2) {
9400                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9401                         gameInfo.variant != VariantBerolina || toX < fromX)
9402                       board[EP_STATUS] = toX | berolina;
9403                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9404                         gameInfo.variant != VariantBerolina || toX > fromX)
9405                       board[EP_STATUS] = toX;
9406            }
9407       } else
9408       if( board[fromY][fromX] == BlackPawn ) {
9409            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9410                board[EP_STATUS] = EP_PAWN_MOVE;
9411            if( toY-fromY== -2) {
9412                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9413                         gameInfo.variant != VariantBerolina || toX < fromX)
9414                       board[EP_STATUS] = toX | berolina;
9415                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9416                         gameInfo.variant != VariantBerolina || toX > fromX)
9417                       board[EP_STATUS] = toX;
9418            }
9419        }
9420
9421        for(i=0; i<nrCastlingRights; i++) {
9422            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9423               board[CASTLING][i] == toX   && castlingRank[i] == toY
9424              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9425        }
9426
9427        if(gameInfo.variant == VariantSChess) { // update virginity
9428            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9429            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9430            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9431            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9432        }
9433
9434      if (fromX == toX && fromY == toY) return;
9435
9436      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9437      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9438      if(gameInfo.variant == VariantKnightmate)
9439          king += (int) WhiteUnicorn - (int) WhiteKing;
9440
9441     /* Code added by Tord: */
9442     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9443     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9444         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9445       board[fromY][fromX] = EmptySquare;
9446       board[toY][toX] = EmptySquare;
9447       if((toX > fromX) != (piece == WhiteRook)) {
9448         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9449       } else {
9450         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9451       }
9452     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9453                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9454       board[fromY][fromX] = EmptySquare;
9455       board[toY][toX] = EmptySquare;
9456       if((toX > fromX) != (piece == BlackRook)) {
9457         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9458       } else {
9459         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9460       }
9461     /* End of code added by Tord */
9462
9463     } else if (board[fromY][fromX] == king
9464         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9465         && toY == fromY && toX > fromX+1) {
9466         board[fromY][fromX] = EmptySquare;
9467         board[toY][toX] = king;
9468         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9469         board[fromY][BOARD_RGHT-1] = EmptySquare;
9470     } else if (board[fromY][fromX] == king
9471         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9472                && toY == fromY && toX < fromX-1) {
9473         board[fromY][fromX] = EmptySquare;
9474         board[toY][toX] = king;
9475         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9476         board[fromY][BOARD_LEFT] = EmptySquare;
9477     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9478                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9479                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9480                ) {
9481         /* white pawn promotion */
9482         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9483         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9484             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9485         board[fromY][fromX] = EmptySquare;
9486     } else if ((fromY >= BOARD_HEIGHT>>1)
9487                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9488                && (toX != fromX)
9489                && gameInfo.variant != VariantXiangqi
9490                && gameInfo.variant != VariantBerolina
9491                && (board[fromY][fromX] == WhitePawn)
9492                && (board[toY][toX] == EmptySquare)) {
9493         board[fromY][fromX] = EmptySquare;
9494         board[toY][toX] = WhitePawn;
9495         captured = board[toY - 1][toX];
9496         board[toY - 1][toX] = EmptySquare;
9497     } else if ((fromY == BOARD_HEIGHT-4)
9498                && (toX == fromX)
9499                && gameInfo.variant == VariantBerolina
9500                && (board[fromY][fromX] == WhitePawn)
9501                && (board[toY][toX] == EmptySquare)) {
9502         board[fromY][fromX] = EmptySquare;
9503         board[toY][toX] = WhitePawn;
9504         if(oldEP & EP_BEROLIN_A) {
9505                 captured = board[fromY][fromX-1];
9506                 board[fromY][fromX-1] = EmptySquare;
9507         }else{  captured = board[fromY][fromX+1];
9508                 board[fromY][fromX+1] = EmptySquare;
9509         }
9510     } else if (board[fromY][fromX] == king
9511         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9512                && toY == fromY && toX > fromX+1) {
9513         board[fromY][fromX] = EmptySquare;
9514         board[toY][toX] = king;
9515         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9516         board[fromY][BOARD_RGHT-1] = EmptySquare;
9517     } else if (board[fromY][fromX] == king
9518         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9519                && toY == fromY && toX < fromX-1) {
9520         board[fromY][fromX] = EmptySquare;
9521         board[toY][toX] = king;
9522         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9523         board[fromY][BOARD_LEFT] = EmptySquare;
9524     } else if (fromY == 7 && fromX == 3
9525                && board[fromY][fromX] == BlackKing
9526                && toY == 7 && toX == 5) {
9527         board[fromY][fromX] = EmptySquare;
9528         board[toY][toX] = BlackKing;
9529         board[fromY][7] = EmptySquare;
9530         board[toY][4] = BlackRook;
9531     } else if (fromY == 7 && fromX == 3
9532                && board[fromY][fromX] == BlackKing
9533                && toY == 7 && toX == 1) {
9534         board[fromY][fromX] = EmptySquare;
9535         board[toY][toX] = BlackKing;
9536         board[fromY][0] = EmptySquare;
9537         board[toY][2] = BlackRook;
9538     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9539                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9540                && toY < promoRank && promoChar
9541                ) {
9542         /* black pawn promotion */
9543         board[toY][toX] = CharToPiece(ToLower(promoChar));
9544         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9545             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9546         board[fromY][fromX] = EmptySquare;
9547     } else if ((fromY < BOARD_HEIGHT>>1)
9548                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9549                && (toX != fromX)
9550                && gameInfo.variant != VariantXiangqi
9551                && gameInfo.variant != VariantBerolina
9552                && (board[fromY][fromX] == BlackPawn)
9553                && (board[toY][toX] == EmptySquare)) {
9554         board[fromY][fromX] = EmptySquare;
9555         board[toY][toX] = BlackPawn;
9556         captured = board[toY + 1][toX];
9557         board[toY + 1][toX] = EmptySquare;
9558     } else if ((fromY == 3)
9559                && (toX == fromX)
9560                && gameInfo.variant == VariantBerolina
9561                && (board[fromY][fromX] == BlackPawn)
9562                && (board[toY][toX] == EmptySquare)) {
9563         board[fromY][fromX] = EmptySquare;
9564         board[toY][toX] = BlackPawn;
9565         if(oldEP & EP_BEROLIN_A) {
9566                 captured = board[fromY][fromX-1];
9567                 board[fromY][fromX-1] = EmptySquare;
9568         }else{  captured = board[fromY][fromX+1];
9569                 board[fromY][fromX+1] = EmptySquare;
9570         }
9571     } else {
9572         board[toY][toX] = board[fromY][fromX];
9573         board[fromY][fromX] = EmptySquare;
9574     }
9575   }
9576
9577     if (gameInfo.holdingsWidth != 0) {
9578
9579       /* !!A lot more code needs to be written to support holdings  */
9580       /* [HGM] OK, so I have written it. Holdings are stored in the */
9581       /* penultimate board files, so they are automaticlly stored   */
9582       /* in the game history.                                       */
9583       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9584                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9585         /* Delete from holdings, by decreasing count */
9586         /* and erasing image if necessary            */
9587         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9588         if(p < (int) BlackPawn) { /* white drop */
9589              p -= (int)WhitePawn;
9590                  p = PieceToNumber((ChessSquare)p);
9591              if(p >= gameInfo.holdingsSize) p = 0;
9592              if(--board[p][BOARD_WIDTH-2] <= 0)
9593                   board[p][BOARD_WIDTH-1] = EmptySquare;
9594              if((int)board[p][BOARD_WIDTH-2] < 0)
9595                         board[p][BOARD_WIDTH-2] = 0;
9596         } else {                  /* black drop */
9597              p -= (int)BlackPawn;
9598                  p = PieceToNumber((ChessSquare)p);
9599              if(p >= gameInfo.holdingsSize) p = 0;
9600              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9601                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9602              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9603                         board[BOARD_HEIGHT-1-p][1] = 0;
9604         }
9605       }
9606       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9607           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9608         /* [HGM] holdings: Add to holdings, if holdings exist */
9609         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9610                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9611                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9612         }
9613         p = (int) captured;
9614         if (p >= (int) BlackPawn) {
9615           p -= (int)BlackPawn;
9616           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9617                   /* in Shogi restore piece to its original  first */
9618                   captured = (ChessSquare) (DEMOTED captured);
9619                   p = DEMOTED p;
9620           }
9621           p = PieceToNumber((ChessSquare)p);
9622           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9623           board[p][BOARD_WIDTH-2]++;
9624           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9625         } else {
9626           p -= (int)WhitePawn;
9627           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9628                   captured = (ChessSquare) (DEMOTED captured);
9629                   p = DEMOTED p;
9630           }
9631           p = PieceToNumber((ChessSquare)p);
9632           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9633           board[BOARD_HEIGHT-1-p][1]++;
9634           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9635         }
9636       }
9637     } else if (gameInfo.variant == VariantAtomic) {
9638       if (captured != EmptySquare) {
9639         int y, x;
9640         for (y = toY-1; y <= toY+1; y++) {
9641           for (x = toX-1; x <= toX+1; x++) {
9642             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9643                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9644               board[y][x] = EmptySquare;
9645             }
9646           }
9647         }
9648         board[toY][toX] = EmptySquare;
9649       }
9650     }
9651     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9652         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9653     } else
9654     if(promoChar == '+') {
9655         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9656         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9657     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9658         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9659         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9660            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9661         board[toY][toX] = newPiece;
9662     }
9663     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9664                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9665         // [HGM] superchess: take promotion piece out of holdings
9666         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9667         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9668             if(!--board[k][BOARD_WIDTH-2])
9669                 board[k][BOARD_WIDTH-1] = EmptySquare;
9670         } else {
9671             if(!--board[BOARD_HEIGHT-1-k][1])
9672                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9673         }
9674     }
9675
9676 }
9677
9678 /* Updates forwardMostMove */
9679 void
9680 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9681 {
9682 //    forwardMostMove++; // [HGM] bare: moved downstream
9683
9684     (void) CoordsToAlgebraic(boards[forwardMostMove],
9685                              PosFlags(forwardMostMove),
9686                              fromY, fromX, toY, toX, promoChar,
9687                              parseList[forwardMostMove]);
9688
9689     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9690         int timeLeft; static int lastLoadFlag=0; int king, piece;
9691         piece = boards[forwardMostMove][fromY][fromX];
9692         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9693         if(gameInfo.variant == VariantKnightmate)
9694             king += (int) WhiteUnicorn - (int) WhiteKing;
9695         if(forwardMostMove == 0) {
9696             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9697                 fprintf(serverMoves, "%s;", UserName());
9698             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9699                 fprintf(serverMoves, "%s;", second.tidy);
9700             fprintf(serverMoves, "%s;", first.tidy);
9701             if(gameMode == MachinePlaysWhite)
9702                 fprintf(serverMoves, "%s;", UserName());
9703             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9704                 fprintf(serverMoves, "%s;", second.tidy);
9705         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9706         lastLoadFlag = loadFlag;
9707         // print base move
9708         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9709         // print castling suffix
9710         if( toY == fromY && piece == king ) {
9711             if(toX-fromX > 1)
9712                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9713             if(fromX-toX >1)
9714                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9715         }
9716         // e.p. suffix
9717         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9718              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9719              boards[forwardMostMove][toY][toX] == EmptySquare
9720              && fromX != toX && fromY != toY)
9721                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9722         // promotion suffix
9723         if(promoChar != NULLCHAR) {
9724             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9725                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9726                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9727             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9728         }
9729         if(!loadFlag) {
9730                 char buf[MOVE_LEN*2], *p; int len;
9731             fprintf(serverMoves, "/%d/%d",
9732                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9733             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9734             else                      timeLeft = blackTimeRemaining/1000;
9735             fprintf(serverMoves, "/%d", timeLeft);
9736                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9737                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9738                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9739                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9740             fprintf(serverMoves, "/%s", buf);
9741         }
9742         fflush(serverMoves);
9743     }
9744
9745     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9746         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9747       return;
9748     }
9749     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9750     if (commentList[forwardMostMove+1] != NULL) {
9751         free(commentList[forwardMostMove+1]);
9752         commentList[forwardMostMove+1] = NULL;
9753     }
9754     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9755     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9756     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9757     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9758     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9759     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9760     adjustedClock = FALSE;
9761     gameInfo.result = GameUnfinished;
9762     if (gameInfo.resultDetails != NULL) {
9763         free(gameInfo.resultDetails);
9764         gameInfo.resultDetails = NULL;
9765     }
9766     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9767                               moveList[forwardMostMove - 1]);
9768     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9769       case MT_NONE:
9770       case MT_STALEMATE:
9771       default:
9772         break;
9773       case MT_CHECK:
9774         if(gameInfo.variant != VariantShogi)
9775             strcat(parseList[forwardMostMove - 1], "+");
9776         break;
9777       case MT_CHECKMATE:
9778       case MT_STAINMATE:
9779         strcat(parseList[forwardMostMove - 1], "#");
9780         break;
9781     }
9782
9783 }
9784
9785 /* Updates currentMove if not pausing */
9786 void
9787 ShowMove (int fromX, int fromY, int toX, int toY)
9788 {
9789     int instant = (gameMode == PlayFromGameFile) ?
9790         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9791     if(appData.noGUI) return;
9792     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9793         if (!instant) {
9794             if (forwardMostMove == currentMove + 1) {
9795                 AnimateMove(boards[forwardMostMove - 1],
9796                             fromX, fromY, toX, toY);
9797             }
9798         }
9799         currentMove = forwardMostMove;
9800     }
9801
9802     if (instant) return;
9803
9804     DisplayMove(currentMove - 1);
9805     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9806             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9807                 SetHighlights(fromX, fromY, toX, toY);
9808             }
9809     }
9810     DrawPosition(FALSE, boards[currentMove]);
9811     DisplayBothClocks();
9812     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9813 }
9814
9815 void
9816 SendEgtPath (ChessProgramState *cps)
9817 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9818         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9819
9820         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9821
9822         while(*p) {
9823             char c, *q = name+1, *r, *s;
9824
9825             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9826             while(*p && *p != ',') *q++ = *p++;
9827             *q++ = ':'; *q = 0;
9828             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9829                 strcmp(name, ",nalimov:") == 0 ) {
9830                 // take nalimov path from the menu-changeable option first, if it is defined
9831               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9832                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9833             } else
9834             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9835                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9836                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9837                 s = r = StrStr(s, ":") + 1; // beginning of path info
9838                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9839                 c = *r; *r = 0;             // temporarily null-terminate path info
9840                     *--q = 0;               // strip of trailig ':' from name
9841                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9842                 *r = c;
9843                 SendToProgram(buf,cps);     // send egtbpath command for this format
9844             }
9845             if(*p == ',') p++; // read away comma to position for next format name
9846         }
9847 }
9848
9849 void
9850 InitChessProgram (ChessProgramState *cps, int setup)
9851 /* setup needed to setup FRC opening position */
9852 {
9853     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9854     if (appData.noChessProgram) return;
9855     hintRequested = FALSE;
9856     bookRequested = FALSE;
9857
9858     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9859     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9860     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9861     if(cps->memSize) { /* [HGM] memory */
9862       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9863         SendToProgram(buf, cps);
9864     }
9865     SendEgtPath(cps); /* [HGM] EGT */
9866     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9867       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9868         SendToProgram(buf, cps);
9869     }
9870
9871     SendToProgram(cps->initString, cps);
9872     if (gameInfo.variant != VariantNormal &&
9873         gameInfo.variant != VariantLoadable
9874         /* [HGM] also send variant if board size non-standard */
9875         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9876                                             ) {
9877       char *v = VariantName(gameInfo.variant);
9878       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9879         /* [HGM] in protocol 1 we have to assume all variants valid */
9880         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9881         DisplayFatalError(buf, 0, 1);
9882         return;
9883       }
9884
9885       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9886       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9887       if( gameInfo.variant == VariantXiangqi )
9888            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9889       if( gameInfo.variant == VariantShogi )
9890            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9891       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9892            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9893       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9894           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9895            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9896       if( gameInfo.variant == VariantCourier )
9897            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9898       if( gameInfo.variant == VariantSuper )
9899            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9900       if( gameInfo.variant == VariantGreat )
9901            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9902       if( gameInfo.variant == VariantSChess )
9903            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9904       if( gameInfo.variant == VariantGrand )
9905            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9906
9907       if(overruled) {
9908         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9909                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9910            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9911            if(StrStr(cps->variants, b) == NULL) {
9912                // specific sized variant not known, check if general sizing allowed
9913                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9914                    if(StrStr(cps->variants, "boardsize") == NULL) {
9915                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9916                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9917                        DisplayFatalError(buf, 0, 1);
9918                        return;
9919                    }
9920                    /* [HGM] here we really should compare with the maximum supported board size */
9921                }
9922            }
9923       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9924       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9925       SendToProgram(buf, cps);
9926     }
9927     currentlyInitializedVariant = gameInfo.variant;
9928
9929     /* [HGM] send opening position in FRC to first engine */
9930     if(setup) {
9931           SendToProgram("force\n", cps);
9932           SendBoard(cps, 0);
9933           /* engine is now in force mode! Set flag to wake it up after first move. */
9934           setboardSpoiledMachineBlack = 1;
9935     }
9936
9937     if (cps->sendICS) {
9938       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9939       SendToProgram(buf, cps);
9940     }
9941     cps->maybeThinking = FALSE;
9942     cps->offeredDraw = 0;
9943     if (!appData.icsActive) {
9944         SendTimeControl(cps, movesPerSession, timeControl,
9945                         timeIncrement, appData.searchDepth,
9946                         searchTime);
9947     }
9948     if (appData.showThinking
9949         // [HGM] thinking: four options require thinking output to be sent
9950         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9951                                 ) {
9952         SendToProgram("post\n", cps);
9953     }
9954     SendToProgram("hard\n", cps);
9955     if (!appData.ponderNextMove) {
9956         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9957            it without being sure what state we are in first.  "hard"
9958            is not a toggle, so that one is OK.
9959          */
9960         SendToProgram("easy\n", cps);
9961     }
9962     if (cps->usePing) {
9963       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9964       SendToProgram(buf, cps);
9965     }
9966     cps->initDone = TRUE;
9967     ClearEngineOutputPane(cps == &second);
9968 }
9969
9970
9971 void
9972 ResendOptions (ChessProgramState *cps)
9973 { // send the stored value of the options
9974   int i;
9975   char buf[MSG_SIZ];
9976   Option *opt = cps->option;
9977   for(i=0; i<cps->nrOptions; i++, opt++) {
9978       switch(opt->type) {
9979         case Spin:
9980         case Slider:
9981         case CheckBox:
9982             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9983           break;
9984         case ComboBox:
9985           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9986           break;
9987         default:
9988             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9989           break;
9990         case Button:
9991         case SaveButton:
9992           continue;
9993       }
9994       SendToProgram(buf, cps);
9995   }
9996 }
9997
9998 void
9999 StartChessProgram (ChessProgramState *cps)
10000 {
10001     char buf[MSG_SIZ];
10002     int err;
10003
10004     if (appData.noChessProgram) return;
10005     cps->initDone = FALSE;
10006
10007     if (strcmp(cps->host, "localhost") == 0) {
10008         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10009     } else if (*appData.remoteShell == NULLCHAR) {
10010         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10011     } else {
10012         if (*appData.remoteUser == NULLCHAR) {
10013           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10014                     cps->program);
10015         } else {
10016           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10017                     cps->host, appData.remoteUser, cps->program);
10018         }
10019         err = StartChildProcess(buf, "", &cps->pr);
10020     }
10021
10022     if (err != 0) {
10023       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10024         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10025         if(cps != &first) return;
10026         appData.noChessProgram = TRUE;
10027         ThawUI();
10028         SetNCPMode();
10029 //      DisplayFatalError(buf, err, 1);
10030 //      cps->pr = NoProc;
10031 //      cps->isr = NULL;
10032         return;
10033     }
10034
10035     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10036     if (cps->protocolVersion > 1) {
10037       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10038       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10039         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10040         cps->comboCnt = 0;  //                and values of combo boxes
10041       }
10042       SendToProgram(buf, cps);
10043       if(cps->reload) ResendOptions(cps);
10044     } else {
10045       SendToProgram("xboard\n", cps);
10046     }
10047 }
10048
10049 void
10050 TwoMachinesEventIfReady P((void))
10051 {
10052   static int curMess = 0;
10053   if (first.lastPing != first.lastPong) {
10054     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10055     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10056     return;
10057   }
10058   if (second.lastPing != second.lastPong) {
10059     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10060     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10061     return;
10062   }
10063   DisplayMessage("", ""); curMess = 0;
10064   TwoMachinesEvent();
10065 }
10066
10067 char *
10068 MakeName (char *template)
10069 {
10070     time_t clock;
10071     struct tm *tm;
10072     static char buf[MSG_SIZ];
10073     char *p = buf;
10074     int i;
10075
10076     clock = time((time_t *)NULL);
10077     tm = localtime(&clock);
10078
10079     while(*p++ = *template++) if(p[-1] == '%') {
10080         switch(*template++) {
10081           case 0:   *p = 0; return buf;
10082           case 'Y': i = tm->tm_year+1900; break;
10083           case 'y': i = tm->tm_year-100; break;
10084           case 'M': i = tm->tm_mon+1; break;
10085           case 'd': i = tm->tm_mday; break;
10086           case 'h': i = tm->tm_hour; break;
10087           case 'm': i = tm->tm_min; break;
10088           case 's': i = tm->tm_sec; break;
10089           default:  i = 0;
10090         }
10091         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10092     }
10093     return buf;
10094 }
10095
10096 int
10097 CountPlayers (char *p)
10098 {
10099     int n = 0;
10100     while(p = strchr(p, '\n')) p++, n++; // count participants
10101     return n;
10102 }
10103
10104 FILE *
10105 WriteTourneyFile (char *results, FILE *f)
10106 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10107     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10108     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10109         // create a file with tournament description
10110         fprintf(f, "-participants {%s}\n", appData.participants);
10111         fprintf(f, "-seedBase %d\n", appData.seedBase);
10112         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10113         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10114         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10115         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10116         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10117         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10118         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10119         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10120         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10121         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10122         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10123         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10124         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10125         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10126         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10127         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10128         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10129         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10130         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10131         fprintf(f, "-smpCores %d\n", appData.smpCores);
10132         if(searchTime > 0)
10133                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10134         else {
10135                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10136                 fprintf(f, "-tc %s\n", appData.timeControl);
10137                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10138         }
10139         fprintf(f, "-results \"%s\"\n", results);
10140     }
10141     return f;
10142 }
10143
10144 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10145
10146 void
10147 Substitute (char *participants, int expunge)
10148 {
10149     int i, changed, changes=0, nPlayers=0;
10150     char *p, *q, *r, buf[MSG_SIZ];
10151     if(participants == NULL) return;
10152     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10153     r = p = participants; q = appData.participants;
10154     while(*p && *p == *q) {
10155         if(*p == '\n') r = p+1, nPlayers++;
10156         p++; q++;
10157     }
10158     if(*p) { // difference
10159         while(*p && *p++ != '\n');
10160         while(*q && *q++ != '\n');
10161       changed = nPlayers;
10162         changes = 1 + (strcmp(p, q) != 0);
10163     }
10164     if(changes == 1) { // a single engine mnemonic was changed
10165         q = r; while(*q) nPlayers += (*q++ == '\n');
10166         p = buf; while(*r && (*p = *r++) != '\n') p++;
10167         *p = NULLCHAR;
10168         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10169         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10170         if(mnemonic[i]) { // The substitute is valid
10171             FILE *f;
10172             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10173                 flock(fileno(f), LOCK_EX);
10174                 ParseArgsFromFile(f);
10175                 fseek(f, 0, SEEK_SET);
10176                 FREE(appData.participants); appData.participants = participants;
10177                 if(expunge) { // erase results of replaced engine
10178                     int len = strlen(appData.results), w, b, dummy;
10179                     for(i=0; i<len; i++) {
10180                         Pairing(i, nPlayers, &w, &b, &dummy);
10181                         if((w == changed || b == changed) && appData.results[i] == '*') {
10182                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10183                             fclose(f);
10184                             return;
10185                         }
10186                     }
10187                     for(i=0; i<len; i++) {
10188                         Pairing(i, nPlayers, &w, &b, &dummy);
10189                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10190                     }
10191                 }
10192                 WriteTourneyFile(appData.results, f);
10193                 fclose(f); // release lock
10194                 return;
10195             }
10196         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10197     }
10198     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10199     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10200     free(participants);
10201     return;
10202 }
10203
10204 int
10205 CheckPlayers (char *participants)
10206 {
10207         int i;
10208         char buf[MSG_SIZ], *p;
10209         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10210         while(p = strchr(participants, '\n')) {
10211             *p = NULLCHAR;
10212             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10213             if(!mnemonic[i]) {
10214                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10215                 *p = '\n';
10216                 DisplayError(buf, 0);
10217                 return 1;
10218             }
10219             *p = '\n';
10220             participants = p + 1;
10221         }
10222         return 0;
10223 }
10224
10225 int
10226 CreateTourney (char *name)
10227 {
10228         FILE *f;
10229         if(matchMode && strcmp(name, appData.tourneyFile)) {
10230              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10231         }
10232         if(name[0] == NULLCHAR) {
10233             if(appData.participants[0])
10234                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10235             return 0;
10236         }
10237         f = fopen(name, "r");
10238         if(f) { // file exists
10239             ASSIGN(appData.tourneyFile, name);
10240             ParseArgsFromFile(f); // parse it
10241         } else {
10242             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10243             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10244                 DisplayError(_("Not enough participants"), 0);
10245                 return 0;
10246             }
10247             if(CheckPlayers(appData.participants)) return 0;
10248             ASSIGN(appData.tourneyFile, name);
10249             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10250             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10251         }
10252         fclose(f);
10253         appData.noChessProgram = FALSE;
10254         appData.clockMode = TRUE;
10255         SetGNUMode();
10256         return 1;
10257 }
10258
10259 int
10260 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10261 {
10262     char buf[MSG_SIZ], *p, *q;
10263     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10264     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10265     skip = !all && group[0]; // if group requested, we start in skip mode
10266     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10267         p = names; q = buf; header = 0;
10268         while(*p && *p != '\n') *q++ = *p++;
10269         *q = 0;
10270         if(*p == '\n') p++;
10271         if(buf[0] == '#') {
10272             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10273             depth++; // we must be entering a new group
10274             if(all) continue; // suppress printing group headers when complete list requested
10275             header = 1;
10276             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10277         }
10278         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10279         if(engineList[i]) free(engineList[i]);
10280         engineList[i] = strdup(buf);
10281         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10282         if(engineMnemonic[i]) free(engineMnemonic[i]);
10283         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10284             strcat(buf, " (");
10285             sscanf(q + 8, "%s", buf + strlen(buf));
10286             strcat(buf, ")");
10287         }
10288         engineMnemonic[i] = strdup(buf);
10289         i++;
10290     }
10291     engineList[i] = engineMnemonic[i] = NULL;
10292     return i;
10293 }
10294
10295 // following implemented as macro to avoid type limitations
10296 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10297
10298 void
10299 SwapEngines (int n)
10300 {   // swap settings for first engine and other engine (so far only some selected options)
10301     int h;
10302     char *p;
10303     if(n == 0) return;
10304     SWAP(directory, p)
10305     SWAP(chessProgram, p)
10306     SWAP(isUCI, h)
10307     SWAP(hasOwnBookUCI, h)
10308     SWAP(protocolVersion, h)
10309     SWAP(reuse, h)
10310     SWAP(scoreIsAbsolute, h)
10311     SWAP(timeOdds, h)
10312     SWAP(logo, p)
10313     SWAP(pgnName, p)
10314     SWAP(pvSAN, h)
10315     SWAP(engOptions, p)
10316     SWAP(engInitString, p)
10317     SWAP(computerString, p)
10318     SWAP(features, p)
10319     SWAP(fenOverride, p)
10320     SWAP(NPS, h)
10321     SWAP(accumulateTC, h)
10322     SWAP(host, p)
10323 }
10324
10325 int
10326 GetEngineLine (char *s, int n)
10327 {
10328     int i;
10329     char buf[MSG_SIZ];
10330     extern char *icsNames;
10331     if(!s || !*s) return 0;
10332     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10333     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10334     if(!mnemonic[i]) return 0;
10335     if(n == 11) return 1; // just testing if there was a match
10336     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10337     if(n == 1) SwapEngines(n);
10338     ParseArgsFromString(buf);
10339     if(n == 1) SwapEngines(n);
10340     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10341         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10342         ParseArgsFromString(buf);
10343     }
10344     return 1;
10345 }
10346
10347 int
10348 SetPlayer (int player, char *p)
10349 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10350     int i;
10351     char buf[MSG_SIZ], *engineName;
10352     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10353     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10354     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10355     if(mnemonic[i]) {
10356         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10357         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10358         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10359         ParseArgsFromString(buf);
10360     } else { // no engine with this nickname is installed!
10361         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10362         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10363         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10364         ModeHighlight();
10365         DisplayError(buf, 0);
10366         return 0;
10367     }
10368     free(engineName);
10369     return i;
10370 }
10371
10372 char *recentEngines;
10373
10374 void
10375 RecentEngineEvent (int nr)
10376 {
10377     int n;
10378 //    SwapEngines(1); // bump first to second
10379 //    ReplaceEngine(&second, 1); // and load it there
10380     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10381     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10382     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10383         ReplaceEngine(&first, 0);
10384         FloatToFront(&appData.recentEngineList, command[n]);
10385     }
10386 }
10387
10388 int
10389 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10390 {   // determine players from game number
10391     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10392
10393     if(appData.tourneyType == 0) {
10394         roundsPerCycle = (nPlayers - 1) | 1;
10395         pairingsPerRound = nPlayers / 2;
10396     } else if(appData.tourneyType > 0) {
10397         roundsPerCycle = nPlayers - appData.tourneyType;
10398         pairingsPerRound = appData.tourneyType;
10399     }
10400     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10401     gamesPerCycle = gamesPerRound * roundsPerCycle;
10402     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10403     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10404     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10405     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10406     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10407     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10408
10409     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10410     if(appData.roundSync) *syncInterval = gamesPerRound;
10411
10412     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10413
10414     if(appData.tourneyType == 0) {
10415         if(curPairing == (nPlayers-1)/2 ) {
10416             *whitePlayer = curRound;
10417             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10418         } else {
10419             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10420             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10421             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10422             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10423         }
10424     } else if(appData.tourneyType > 1) {
10425         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10426         *whitePlayer = curRound + appData.tourneyType;
10427     } else if(appData.tourneyType > 0) {
10428         *whitePlayer = curPairing;
10429         *blackPlayer = curRound + appData.tourneyType;
10430     }
10431
10432     // take care of white/black alternation per round.
10433     // For cycles and games this is already taken care of by default, derived from matchGame!
10434     return curRound & 1;
10435 }
10436
10437 int
10438 NextTourneyGame (int nr, int *swapColors)
10439 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10440     char *p, *q;
10441     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10442     FILE *tf;
10443     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10444     tf = fopen(appData.tourneyFile, "r");
10445     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10446     ParseArgsFromFile(tf); fclose(tf);
10447     InitTimeControls(); // TC might be altered from tourney file
10448
10449     nPlayers = CountPlayers(appData.participants); // count participants
10450     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10451     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10452
10453     if(syncInterval) {
10454         p = q = appData.results;
10455         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10456         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10457             DisplayMessage(_("Waiting for other game(s)"),"");
10458             waitingForGame = TRUE;
10459             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10460             return 0;
10461         }
10462         waitingForGame = FALSE;
10463     }
10464
10465     if(appData.tourneyType < 0) {
10466         if(nr>=0 && !pairingReceived) {
10467             char buf[1<<16];
10468             if(pairing.pr == NoProc) {
10469                 if(!appData.pairingEngine[0]) {
10470                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10471                     return 0;
10472                 }
10473                 StartChessProgram(&pairing); // starts the pairing engine
10474             }
10475             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10476             SendToProgram(buf, &pairing);
10477             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10478             SendToProgram(buf, &pairing);
10479             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10480         }
10481         pairingReceived = 0;                              // ... so we continue here
10482         *swapColors = 0;
10483         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10484         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10485         matchGame = 1; roundNr = nr / syncInterval + 1;
10486     }
10487
10488     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10489
10490     // redefine engines, engine dir, etc.
10491     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10492     if(first.pr == NoProc) {
10493       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10494       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10495     }
10496     if(second.pr == NoProc) {
10497       SwapEngines(1);
10498       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10499       SwapEngines(1);         // and make that valid for second engine by swapping
10500       InitEngine(&second, 1);
10501     }
10502     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10503     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10504     return OK;
10505 }
10506
10507 void
10508 NextMatchGame ()
10509 {   // performs game initialization that does not invoke engines, and then tries to start the game
10510     int res, firstWhite, swapColors = 0;
10511     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10512     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
10513         char buf[MSG_SIZ];
10514         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10515         if(strcmp(buf, currentDebugFile)) { // name has changed
10516             FILE *f = fopen(buf, "w");
10517             if(f) { // if opening the new file failed, just keep using the old one
10518                 ASSIGN(currentDebugFile, buf);
10519                 fclose(debugFP);
10520                 debugFP = f;
10521             }
10522             if(appData.serverFileName) {
10523                 if(serverFP) fclose(serverFP);
10524                 serverFP = fopen(appData.serverFileName, "w");
10525                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10526                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10527             }
10528         }
10529     }
10530     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10531     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10532     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10533     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10534     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10535     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10536     Reset(FALSE, first.pr != NoProc);
10537     res = LoadGameOrPosition(matchGame); // setup game
10538     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10539     if(!res) return; // abort when bad game/pos file
10540     TwoMachinesEvent();
10541 }
10542
10543 void
10544 UserAdjudicationEvent (int result)
10545 {
10546     ChessMove gameResult = GameIsDrawn;
10547
10548     if( result > 0 ) {
10549         gameResult = WhiteWins;
10550     }
10551     else if( result < 0 ) {
10552         gameResult = BlackWins;
10553     }
10554
10555     if( gameMode == TwoMachinesPlay ) {
10556         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10557     }
10558 }
10559
10560
10561 // [HGM] save: calculate checksum of game to make games easily identifiable
10562 int
10563 StringCheckSum (char *s)
10564 {
10565         int i = 0;
10566         if(s==NULL) return 0;
10567         while(*s) i = i*259 + *s++;
10568         return i;
10569 }
10570
10571 int
10572 GameCheckSum ()
10573 {
10574         int i, sum=0;
10575         for(i=backwardMostMove; i<forwardMostMove; i++) {
10576                 sum += pvInfoList[i].depth;
10577                 sum += StringCheckSum(parseList[i]);
10578                 sum += StringCheckSum(commentList[i]);
10579                 sum *= 261;
10580         }
10581         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10582         return sum + StringCheckSum(commentList[i]);
10583 } // end of save patch
10584
10585 void
10586 GameEnds (ChessMove result, char *resultDetails, int whosays)
10587 {
10588     GameMode nextGameMode;
10589     int isIcsGame;
10590     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10591
10592     if(endingGame) return; /* [HGM] crash: forbid recursion */
10593     endingGame = 1;
10594     if(twoBoards) { // [HGM] dual: switch back to one board
10595         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10596         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10597     }
10598     if (appData.debugMode) {
10599       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10600               result, resultDetails ? resultDetails : "(null)", whosays);
10601     }
10602
10603     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10604
10605     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10606
10607     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10608         /* If we are playing on ICS, the server decides when the
10609            game is over, but the engine can offer to draw, claim
10610            a draw, or resign.
10611          */
10612 #if ZIPPY
10613         if (appData.zippyPlay && first.initDone) {
10614             if (result == GameIsDrawn) {
10615                 /* In case draw still needs to be claimed */
10616                 SendToICS(ics_prefix);
10617                 SendToICS("draw\n");
10618             } else if (StrCaseStr(resultDetails, "resign")) {
10619                 SendToICS(ics_prefix);
10620                 SendToICS("resign\n");
10621             }
10622         }
10623 #endif
10624         endingGame = 0; /* [HGM] crash */
10625         return;
10626     }
10627
10628     /* If we're loading the game from a file, stop */
10629     if (whosays == GE_FILE) {
10630       (void) StopLoadGameTimer();
10631       gameFileFP = NULL;
10632     }
10633
10634     /* Cancel draw offers */
10635     first.offeredDraw = second.offeredDraw = 0;
10636
10637     /* If this is an ICS game, only ICS can really say it's done;
10638        if not, anyone can. */
10639     isIcsGame = (gameMode == IcsPlayingWhite ||
10640                  gameMode == IcsPlayingBlack ||
10641                  gameMode == IcsObserving    ||
10642                  gameMode == IcsExamining);
10643
10644     if (!isIcsGame || whosays == GE_ICS) {
10645         /* OK -- not an ICS game, or ICS said it was done */
10646         StopClocks();
10647         if (!isIcsGame && !appData.noChessProgram)
10648           SetUserThinkingEnables();
10649
10650         /* [HGM] if a machine claims the game end we verify this claim */
10651         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10652             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10653                 char claimer;
10654                 ChessMove trueResult = (ChessMove) -1;
10655
10656                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10657                                             first.twoMachinesColor[0] :
10658                                             second.twoMachinesColor[0] ;
10659
10660                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10661                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10662                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10663                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10664                 } else
10665                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10666                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10667                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10668                 } else
10669                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10670                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10671                 }
10672
10673                 // now verify win claims, but not in drop games, as we don't understand those yet
10674                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10675                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10676                     (result == WhiteWins && claimer == 'w' ||
10677                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10678                       if (appData.debugMode) {
10679                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10680                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10681                       }
10682                       if(result != trueResult) {
10683                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10684                               result = claimer == 'w' ? BlackWins : WhiteWins;
10685                               resultDetails = buf;
10686                       }
10687                 } else
10688                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10689                     && (forwardMostMove <= backwardMostMove ||
10690                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10691                         (claimer=='b')==(forwardMostMove&1))
10692                                                                                   ) {
10693                       /* [HGM] verify: draws that were not flagged are false claims */
10694                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10695                       result = claimer == 'w' ? BlackWins : WhiteWins;
10696                       resultDetails = buf;
10697                 }
10698                 /* (Claiming a loss is accepted no questions asked!) */
10699             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10700                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10701                 result = GameUnfinished;
10702                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10703             }
10704             /* [HGM] bare: don't allow bare King to win */
10705             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10706                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10707                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10708                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10709                && result != GameIsDrawn)
10710             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10711                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10712                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10713                         if(p >= 0 && p <= (int)WhiteKing) k++;
10714                 }
10715                 if (appData.debugMode) {
10716                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10717                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10718                 }
10719                 if(k <= 1) {
10720                         result = GameIsDrawn;
10721                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10722                         resultDetails = buf;
10723                 }
10724             }
10725         }
10726
10727
10728         if(serverMoves != NULL && !loadFlag) { char c = '=';
10729             if(result==WhiteWins) c = '+';
10730             if(result==BlackWins) c = '-';
10731             if(resultDetails != NULL)
10732                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10733         }
10734         if (resultDetails != NULL) {
10735             gameInfo.result = result;
10736             gameInfo.resultDetails = StrSave(resultDetails);
10737
10738             /* display last move only if game was not loaded from file */
10739             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10740                 DisplayMove(currentMove - 1);
10741
10742             if (forwardMostMove != 0) {
10743                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10744                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10745                                                                 ) {
10746                     if (*appData.saveGameFile != NULLCHAR) {
10747                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10748                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10749                         else
10750                         SaveGameToFile(appData.saveGameFile, TRUE);
10751                     } else if (appData.autoSaveGames) {
10752                         AutoSaveGame();
10753                     }
10754                     if (*appData.savePositionFile != NULLCHAR) {
10755                         SavePositionToFile(appData.savePositionFile);
10756                     }
10757                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10758                 }
10759             }
10760
10761             /* Tell program how game ended in case it is learning */
10762             /* [HGM] Moved this to after saving the PGN, just in case */
10763             /* engine died and we got here through time loss. In that */
10764             /* case we will get a fatal error writing the pipe, which */
10765             /* would otherwise lose us the PGN.                       */
10766             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10767             /* output during GameEnds should never be fatal anymore   */
10768             if (gameMode == MachinePlaysWhite ||
10769                 gameMode == MachinePlaysBlack ||
10770                 gameMode == TwoMachinesPlay ||
10771                 gameMode == IcsPlayingWhite ||
10772                 gameMode == IcsPlayingBlack ||
10773                 gameMode == BeginningOfGame) {
10774                 char buf[MSG_SIZ];
10775                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10776                         resultDetails);
10777                 if (first.pr != NoProc) {
10778                     SendToProgram(buf, &first);
10779                 }
10780                 if (second.pr != NoProc &&
10781                     gameMode == TwoMachinesPlay) {
10782                     SendToProgram(buf, &second);
10783                 }
10784             }
10785         }
10786
10787         if (appData.icsActive) {
10788             if (appData.quietPlay &&
10789                 (gameMode == IcsPlayingWhite ||
10790                  gameMode == IcsPlayingBlack)) {
10791                 SendToICS(ics_prefix);
10792                 SendToICS("set shout 1\n");
10793             }
10794             nextGameMode = IcsIdle;
10795             ics_user_moved = FALSE;
10796             /* clean up premove.  It's ugly when the game has ended and the
10797              * premove highlights are still on the board.
10798              */
10799             if (gotPremove) {
10800               gotPremove = FALSE;
10801               ClearPremoveHighlights();
10802               DrawPosition(FALSE, boards[currentMove]);
10803             }
10804             if (whosays == GE_ICS) {
10805                 switch (result) {
10806                 case WhiteWins:
10807                     if (gameMode == IcsPlayingWhite)
10808                         PlayIcsWinSound();
10809                     else if(gameMode == IcsPlayingBlack)
10810                         PlayIcsLossSound();
10811                     break;
10812                 case BlackWins:
10813                     if (gameMode == IcsPlayingBlack)
10814                         PlayIcsWinSound();
10815                     else if(gameMode == IcsPlayingWhite)
10816                         PlayIcsLossSound();
10817                     break;
10818                 case GameIsDrawn:
10819                     PlayIcsDrawSound();
10820                     break;
10821                 default:
10822                     PlayIcsUnfinishedSound();
10823                 }
10824             }
10825         } else if (gameMode == EditGame ||
10826                    gameMode == PlayFromGameFile ||
10827                    gameMode == AnalyzeMode ||
10828                    gameMode == AnalyzeFile) {
10829             nextGameMode = gameMode;
10830         } else {
10831             nextGameMode = EndOfGame;
10832         }
10833         pausing = FALSE;
10834         ModeHighlight();
10835     } else {
10836         nextGameMode = gameMode;
10837     }
10838
10839     if (appData.noChessProgram) {
10840         gameMode = nextGameMode;
10841         ModeHighlight();
10842         endingGame = 0; /* [HGM] crash */
10843         return;
10844     }
10845
10846     if (first.reuse) {
10847         /* Put first chess program into idle state */
10848         if (first.pr != NoProc &&
10849             (gameMode == MachinePlaysWhite ||
10850              gameMode == MachinePlaysBlack ||
10851              gameMode == TwoMachinesPlay ||
10852              gameMode == IcsPlayingWhite ||
10853              gameMode == IcsPlayingBlack ||
10854              gameMode == BeginningOfGame)) {
10855             SendToProgram("force\n", &first);
10856             if (first.usePing) {
10857               char buf[MSG_SIZ];
10858               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10859               SendToProgram(buf, &first);
10860             }
10861         }
10862     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10863         /* Kill off first chess program */
10864         if (first.isr != NULL)
10865           RemoveInputSource(first.isr);
10866         first.isr = NULL;
10867
10868         if (first.pr != NoProc) {
10869             ExitAnalyzeMode();
10870             DoSleep( appData.delayBeforeQuit );
10871             SendToProgram("quit\n", &first);
10872             DoSleep( appData.delayAfterQuit );
10873             DestroyChildProcess(first.pr, first.useSigterm);
10874             first.reload = TRUE;
10875         }
10876         first.pr = NoProc;
10877     }
10878     if (second.reuse) {
10879         /* Put second chess program into idle state */
10880         if (second.pr != NoProc &&
10881             gameMode == TwoMachinesPlay) {
10882             SendToProgram("force\n", &second);
10883             if (second.usePing) {
10884               char buf[MSG_SIZ];
10885               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10886               SendToProgram(buf, &second);
10887             }
10888         }
10889     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10890         /* Kill off second chess program */
10891         if (second.isr != NULL)
10892           RemoveInputSource(second.isr);
10893         second.isr = NULL;
10894
10895         if (second.pr != NoProc) {
10896             DoSleep( appData.delayBeforeQuit );
10897             SendToProgram("quit\n", &second);
10898             DoSleep( appData.delayAfterQuit );
10899             DestroyChildProcess(second.pr, second.useSigterm);
10900             second.reload = TRUE;
10901         }
10902         second.pr = NoProc;
10903     }
10904
10905     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10906         char resChar = '=';
10907         switch (result) {
10908         case WhiteWins:
10909           resChar = '+';
10910           if (first.twoMachinesColor[0] == 'w') {
10911             first.matchWins++;
10912           } else {
10913             second.matchWins++;
10914           }
10915           break;
10916         case BlackWins:
10917           resChar = '-';
10918           if (first.twoMachinesColor[0] == 'b') {
10919             first.matchWins++;
10920           } else {
10921             second.matchWins++;
10922           }
10923           break;
10924         case GameUnfinished:
10925           resChar = ' ';
10926         default:
10927           break;
10928         }
10929
10930         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10931         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10932             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10933             ReserveGame(nextGame, resChar); // sets nextGame
10934             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10935             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10936         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10937
10938         if (nextGame <= appData.matchGames && !abortMatch) {
10939             gameMode = nextGameMode;
10940             matchGame = nextGame; // this will be overruled in tourney mode!
10941             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10942             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10943             endingGame = 0; /* [HGM] crash */
10944             return;
10945         } else {
10946             gameMode = nextGameMode;
10947             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10948                      first.tidy, second.tidy,
10949                      first.matchWins, second.matchWins,
10950                      appData.matchGames - (first.matchWins + second.matchWins));
10951             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10952             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10953             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10954             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10955                 first.twoMachinesColor = "black\n";
10956                 second.twoMachinesColor = "white\n";
10957             } else {
10958                 first.twoMachinesColor = "white\n";
10959                 second.twoMachinesColor = "black\n";
10960             }
10961         }
10962     }
10963     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10964         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10965       ExitAnalyzeMode();
10966     gameMode = nextGameMode;
10967     ModeHighlight();
10968     endingGame = 0;  /* [HGM] crash */
10969     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10970         if(matchMode == TRUE) { // match through command line: exit with or without popup
10971             if(ranking) {
10972                 ToNrEvent(forwardMostMove);
10973                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10974                 else ExitEvent(0);
10975             } else DisplayFatalError(buf, 0, 0);
10976         } else { // match through menu; just stop, with or without popup
10977             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10978             ModeHighlight();
10979             if(ranking){
10980                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10981             } else DisplayNote(buf);
10982       }
10983       if(ranking) free(ranking);
10984     }
10985 }
10986
10987 /* Assumes program was just initialized (initString sent).
10988    Leaves program in force mode. */
10989 void
10990 FeedMovesToProgram (ChessProgramState *cps, int upto)
10991 {
10992     int i;
10993
10994     if (appData.debugMode)
10995       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10996               startedFromSetupPosition ? "position and " : "",
10997               backwardMostMove, upto, cps->which);
10998     if(currentlyInitializedVariant != gameInfo.variant) {
10999       char buf[MSG_SIZ];
11000         // [HGM] variantswitch: make engine aware of new variant
11001         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11002                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11003         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11004         SendToProgram(buf, cps);
11005         currentlyInitializedVariant = gameInfo.variant;
11006     }
11007     SendToProgram("force\n", cps);
11008     if (startedFromSetupPosition) {
11009         SendBoard(cps, backwardMostMove);
11010     if (appData.debugMode) {
11011         fprintf(debugFP, "feedMoves\n");
11012     }
11013     }
11014     for (i = backwardMostMove; i < upto; i++) {
11015         SendMoveToProgram(i, cps);
11016     }
11017 }
11018
11019
11020 int
11021 ResurrectChessProgram ()
11022 {
11023      /* The chess program may have exited.
11024         If so, restart it and feed it all the moves made so far. */
11025     static int doInit = 0;
11026
11027     if (appData.noChessProgram) return 1;
11028
11029     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11030         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11031         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11032         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11033     } else {
11034         if (first.pr != NoProc) return 1;
11035         StartChessProgram(&first);
11036     }
11037     InitChessProgram(&first, FALSE);
11038     FeedMovesToProgram(&first, currentMove);
11039
11040     if (!first.sendTime) {
11041         /* can't tell gnuchess what its clock should read,
11042            so we bow to its notion. */
11043         ResetClocks();
11044         timeRemaining[0][currentMove] = whiteTimeRemaining;
11045         timeRemaining[1][currentMove] = blackTimeRemaining;
11046     }
11047
11048     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11049                 appData.icsEngineAnalyze) && first.analysisSupport) {
11050       SendToProgram("analyze\n", &first);
11051       first.analyzing = TRUE;
11052     }
11053     return 1;
11054 }
11055
11056 /*
11057  * Button procedures
11058  */
11059 void
11060 Reset (int redraw, int init)
11061 {
11062     int i;
11063
11064     if (appData.debugMode) {
11065         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11066                 redraw, init, gameMode);
11067     }
11068     CleanupTail(); // [HGM] vari: delete any stored variations
11069     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11070     pausing = pauseExamInvalid = FALSE;
11071     startedFromSetupPosition = blackPlaysFirst = FALSE;
11072     firstMove = TRUE;
11073     whiteFlag = blackFlag = FALSE;
11074     userOfferedDraw = FALSE;
11075     hintRequested = bookRequested = FALSE;
11076     first.maybeThinking = FALSE;
11077     second.maybeThinking = FALSE;
11078     first.bookSuspend = FALSE; // [HGM] book
11079     second.bookSuspend = FALSE;
11080     thinkOutput[0] = NULLCHAR;
11081     lastHint[0] = NULLCHAR;
11082     ClearGameInfo(&gameInfo);
11083     gameInfo.variant = StringToVariant(appData.variant);
11084     ics_user_moved = ics_clock_paused = FALSE;
11085     ics_getting_history = H_FALSE;
11086     ics_gamenum = -1;
11087     white_holding[0] = black_holding[0] = NULLCHAR;
11088     ClearProgramStats();
11089     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11090
11091     ResetFrontEnd();
11092     ClearHighlights();
11093     flipView = appData.flipView;
11094     ClearPremoveHighlights();
11095     gotPremove = FALSE;
11096     alarmSounded = FALSE;
11097
11098     GameEnds(EndOfFile, NULL, GE_PLAYER);
11099     if(appData.serverMovesName != NULL) {
11100         /* [HGM] prepare to make moves file for broadcasting */
11101         clock_t t = clock();
11102         if(serverMoves != NULL) fclose(serverMoves);
11103         serverMoves = fopen(appData.serverMovesName, "r");
11104         if(serverMoves != NULL) {
11105             fclose(serverMoves);
11106             /* delay 15 sec before overwriting, so all clients can see end */
11107             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11108         }
11109         serverMoves = fopen(appData.serverMovesName, "w");
11110     }
11111
11112     ExitAnalyzeMode();
11113     gameMode = BeginningOfGame;
11114     ModeHighlight();
11115     if(appData.icsActive) gameInfo.variant = VariantNormal;
11116     currentMove = forwardMostMove = backwardMostMove = 0;
11117     MarkTargetSquares(1);
11118     InitPosition(redraw);
11119     for (i = 0; i < MAX_MOVES; i++) {
11120         if (commentList[i] != NULL) {
11121             free(commentList[i]);
11122             commentList[i] = NULL;
11123         }
11124     }
11125     ResetClocks();
11126     timeRemaining[0][0] = whiteTimeRemaining;
11127     timeRemaining[1][0] = blackTimeRemaining;
11128
11129     if (first.pr == NoProc) {
11130         StartChessProgram(&first);
11131     }
11132     if (init) {
11133             InitChessProgram(&first, startedFromSetupPosition);
11134     }
11135     DisplayTitle("");
11136     DisplayMessage("", "");
11137     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11138     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11139     ClearMap();        // [HGM] exclude: invalidate map
11140 }
11141
11142 void
11143 AutoPlayGameLoop ()
11144 {
11145     for (;;) {
11146         if (!AutoPlayOneMove())
11147           return;
11148         if (matchMode || appData.timeDelay == 0)
11149           continue;
11150         if (appData.timeDelay < 0)
11151           return;
11152         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11153         break;
11154     }
11155 }
11156
11157 void
11158 AnalyzeNextGame()
11159 {
11160     ReloadGame(1); // next game
11161 }
11162
11163 int
11164 AutoPlayOneMove ()
11165 {
11166     int fromX, fromY, toX, toY;
11167
11168     if (appData.debugMode) {
11169       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11170     }
11171
11172     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11173       return FALSE;
11174
11175     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11176       pvInfoList[currentMove].depth = programStats.depth;
11177       pvInfoList[currentMove].score = programStats.score;
11178       pvInfoList[currentMove].time  = 0;
11179       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11180     }
11181
11182     if (currentMove >= forwardMostMove) {
11183       if(gameMode == AnalyzeFile) {
11184           if(appData.loadGameIndex == -1) {
11185             GameEnds(EndOfFile, NULL, GE_FILE);
11186           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11187           } else {
11188           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11189         }
11190       }
11191 //      gameMode = EndOfGame;
11192 //      ModeHighlight();
11193
11194       /* [AS] Clear current move marker at the end of a game */
11195       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11196
11197       return FALSE;
11198     }
11199
11200     toX = moveList[currentMove][2] - AAA;
11201     toY = moveList[currentMove][3] - ONE;
11202
11203     if (moveList[currentMove][1] == '@') {
11204         if (appData.highlightLastMove) {
11205             SetHighlights(-1, -1, toX, toY);
11206         }
11207     } else {
11208         fromX = moveList[currentMove][0] - AAA;
11209         fromY = moveList[currentMove][1] - ONE;
11210
11211         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11212
11213         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11214
11215         if (appData.highlightLastMove) {
11216             SetHighlights(fromX, fromY, toX, toY);
11217         }
11218     }
11219     DisplayMove(currentMove);
11220     SendMoveToProgram(currentMove++, &first);
11221     DisplayBothClocks();
11222     DrawPosition(FALSE, boards[currentMove]);
11223     // [HGM] PV info: always display, routine tests if empty
11224     DisplayComment(currentMove - 1, commentList[currentMove]);
11225     return TRUE;
11226 }
11227
11228
11229 int
11230 LoadGameOneMove (ChessMove readAhead)
11231 {
11232     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11233     char promoChar = NULLCHAR;
11234     ChessMove moveType;
11235     char move[MSG_SIZ];
11236     char *p, *q;
11237
11238     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11239         gameMode != AnalyzeMode && gameMode != Training) {
11240         gameFileFP = NULL;
11241         return FALSE;
11242     }
11243
11244     yyboardindex = forwardMostMove;
11245     if (readAhead != EndOfFile) {
11246       moveType = readAhead;
11247     } else {
11248       if (gameFileFP == NULL)
11249           return FALSE;
11250       moveType = (ChessMove) Myylex();
11251     }
11252
11253     done = FALSE;
11254     switch (moveType) {
11255       case Comment:
11256         if (appData.debugMode)
11257           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11258         p = yy_text;
11259
11260         /* append the comment but don't display it */
11261         AppendComment(currentMove, p, FALSE);
11262         return TRUE;
11263
11264       case WhiteCapturesEnPassant:
11265       case BlackCapturesEnPassant:
11266       case WhitePromotion:
11267       case BlackPromotion:
11268       case WhiteNonPromotion:
11269       case BlackNonPromotion:
11270       case NormalMove:
11271       case WhiteKingSideCastle:
11272       case WhiteQueenSideCastle:
11273       case BlackKingSideCastle:
11274       case BlackQueenSideCastle:
11275       case WhiteKingSideCastleWild:
11276       case WhiteQueenSideCastleWild:
11277       case BlackKingSideCastleWild:
11278       case BlackQueenSideCastleWild:
11279       /* PUSH Fabien */
11280       case WhiteHSideCastleFR:
11281       case WhiteASideCastleFR:
11282       case BlackHSideCastleFR:
11283       case BlackASideCastleFR:
11284       /* POP Fabien */
11285         if (appData.debugMode)
11286           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11287         fromX = currentMoveString[0] - AAA;
11288         fromY = currentMoveString[1] - ONE;
11289         toX = currentMoveString[2] - AAA;
11290         toY = currentMoveString[3] - ONE;
11291         promoChar = currentMoveString[4];
11292         break;
11293
11294       case WhiteDrop:
11295       case BlackDrop:
11296         if (appData.debugMode)
11297           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11298         fromX = moveType == WhiteDrop ?
11299           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11300         (int) CharToPiece(ToLower(currentMoveString[0]));
11301         fromY = DROP_RANK;
11302         toX = currentMoveString[2] - AAA;
11303         toY = currentMoveString[3] - ONE;
11304         break;
11305
11306       case WhiteWins:
11307       case BlackWins:
11308       case GameIsDrawn:
11309       case GameUnfinished:
11310         if (appData.debugMode)
11311           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11312         p = strchr(yy_text, '{');
11313         if (p == NULL) p = strchr(yy_text, '(');
11314         if (p == NULL) {
11315             p = yy_text;
11316             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11317         } else {
11318             q = strchr(p, *p == '{' ? '}' : ')');
11319             if (q != NULL) *q = NULLCHAR;
11320             p++;
11321         }
11322         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11323         GameEnds(moveType, p, GE_FILE);
11324         done = TRUE;
11325         if (cmailMsgLoaded) {
11326             ClearHighlights();
11327             flipView = WhiteOnMove(currentMove);
11328             if (moveType == GameUnfinished) flipView = !flipView;
11329             if (appData.debugMode)
11330               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11331         }
11332         break;
11333
11334       case EndOfFile:
11335         if (appData.debugMode)
11336           fprintf(debugFP, "Parser hit end of file\n");
11337         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11338           case MT_NONE:
11339           case MT_CHECK:
11340             break;
11341           case MT_CHECKMATE:
11342           case MT_STAINMATE:
11343             if (WhiteOnMove(currentMove)) {
11344                 GameEnds(BlackWins, "Black mates", GE_FILE);
11345             } else {
11346                 GameEnds(WhiteWins, "White mates", GE_FILE);
11347             }
11348             break;
11349           case MT_STALEMATE:
11350             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11351             break;
11352         }
11353         done = TRUE;
11354         break;
11355
11356       case MoveNumberOne:
11357         if (lastLoadGameStart == GNUChessGame) {
11358             /* GNUChessGames have numbers, but they aren't move numbers */
11359             if (appData.debugMode)
11360               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11361                       yy_text, (int) moveType);
11362             return LoadGameOneMove(EndOfFile); /* tail recursion */
11363         }
11364         /* else fall thru */
11365
11366       case XBoardGame:
11367       case GNUChessGame:
11368       case PGNTag:
11369         /* Reached start of next game in file */
11370         if (appData.debugMode)
11371           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11372         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11373           case MT_NONE:
11374           case MT_CHECK:
11375             break;
11376           case MT_CHECKMATE:
11377           case MT_STAINMATE:
11378             if (WhiteOnMove(currentMove)) {
11379                 GameEnds(BlackWins, "Black mates", GE_FILE);
11380             } else {
11381                 GameEnds(WhiteWins, "White mates", GE_FILE);
11382             }
11383             break;
11384           case MT_STALEMATE:
11385             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11386             break;
11387         }
11388         done = TRUE;
11389         break;
11390
11391       case PositionDiagram:     /* should not happen; ignore */
11392       case ElapsedTime:         /* ignore */
11393       case NAG:                 /* ignore */
11394         if (appData.debugMode)
11395           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11396                   yy_text, (int) moveType);
11397         return LoadGameOneMove(EndOfFile); /* tail recursion */
11398
11399       case IllegalMove:
11400         if (appData.testLegality) {
11401             if (appData.debugMode)
11402               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11403             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11404                     (forwardMostMove / 2) + 1,
11405                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11406             DisplayError(move, 0);
11407             done = TRUE;
11408         } else {
11409             if (appData.debugMode)
11410               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11411                       yy_text, currentMoveString);
11412             fromX = currentMoveString[0] - AAA;
11413             fromY = currentMoveString[1] - ONE;
11414             toX = currentMoveString[2] - AAA;
11415             toY = currentMoveString[3] - ONE;
11416             promoChar = currentMoveString[4];
11417         }
11418         break;
11419
11420       case AmbiguousMove:
11421         if (appData.debugMode)
11422           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11423         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11424                 (forwardMostMove / 2) + 1,
11425                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11426         DisplayError(move, 0);
11427         done = TRUE;
11428         break;
11429
11430       default:
11431       case ImpossibleMove:
11432         if (appData.debugMode)
11433           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11434         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11435                 (forwardMostMove / 2) + 1,
11436                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11437         DisplayError(move, 0);
11438         done = TRUE;
11439         break;
11440     }
11441
11442     if (done) {
11443         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11444             DrawPosition(FALSE, boards[currentMove]);
11445             DisplayBothClocks();
11446             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11447               DisplayComment(currentMove - 1, commentList[currentMove]);
11448         }
11449         (void) StopLoadGameTimer();
11450         gameFileFP = NULL;
11451         cmailOldMove = forwardMostMove;
11452         return FALSE;
11453     } else {
11454         /* currentMoveString is set as a side-effect of yylex */
11455
11456         thinkOutput[0] = NULLCHAR;
11457         MakeMove(fromX, fromY, toX, toY, promoChar);
11458         currentMove = forwardMostMove;
11459         return TRUE;
11460     }
11461 }
11462
11463 /* Load the nth game from the given file */
11464 int
11465 LoadGameFromFile (char *filename, int n, char *title, int useList)
11466 {
11467     FILE *f;
11468     char buf[MSG_SIZ];
11469
11470     if (strcmp(filename, "-") == 0) {
11471         f = stdin;
11472         title = "stdin";
11473     } else {
11474         f = fopen(filename, "rb");
11475         if (f == NULL) {
11476           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11477             DisplayError(buf, errno);
11478             return FALSE;
11479         }
11480     }
11481     if (fseek(f, 0, 0) == -1) {
11482         /* f is not seekable; probably a pipe */
11483         useList = FALSE;
11484     }
11485     if (useList && n == 0) {
11486         int error = GameListBuild(f);
11487         if (error) {
11488             DisplayError(_("Cannot build game list"), error);
11489         } else if (!ListEmpty(&gameList) &&
11490                    ((ListGame *) gameList.tailPred)->number > 1) {
11491             GameListPopUp(f, title);
11492             return TRUE;
11493         }
11494         GameListDestroy();
11495         n = 1;
11496     }
11497     if (n == 0) n = 1;
11498     return LoadGame(f, n, title, FALSE);
11499 }
11500
11501
11502 void
11503 MakeRegisteredMove ()
11504 {
11505     int fromX, fromY, toX, toY;
11506     char promoChar;
11507     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11508         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11509           case CMAIL_MOVE:
11510           case CMAIL_DRAW:
11511             if (appData.debugMode)
11512               fprintf(debugFP, "Restoring %s for game %d\n",
11513                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11514
11515             thinkOutput[0] = NULLCHAR;
11516             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11517             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11518             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11519             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11520             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11521             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11522             MakeMove(fromX, fromY, toX, toY, promoChar);
11523             ShowMove(fromX, fromY, toX, toY);
11524
11525             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11526               case MT_NONE:
11527               case MT_CHECK:
11528                 break;
11529
11530               case MT_CHECKMATE:
11531               case MT_STAINMATE:
11532                 if (WhiteOnMove(currentMove)) {
11533                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11534                 } else {
11535                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11536                 }
11537                 break;
11538
11539               case MT_STALEMATE:
11540                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11541                 break;
11542             }
11543
11544             break;
11545
11546           case CMAIL_RESIGN:
11547             if (WhiteOnMove(currentMove)) {
11548                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11549             } else {
11550                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11551             }
11552             break;
11553
11554           case CMAIL_ACCEPT:
11555             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11556             break;
11557
11558           default:
11559             break;
11560         }
11561     }
11562
11563     return;
11564 }
11565
11566 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11567 int
11568 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11569 {
11570     int retVal;
11571
11572     if (gameNumber > nCmailGames) {
11573         DisplayError(_("No more games in this message"), 0);
11574         return FALSE;
11575     }
11576     if (f == lastLoadGameFP) {
11577         int offset = gameNumber - lastLoadGameNumber;
11578         if (offset == 0) {
11579             cmailMsg[0] = NULLCHAR;
11580             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11581                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11582                 nCmailMovesRegistered--;
11583             }
11584             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11585             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11586                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11587             }
11588         } else {
11589             if (! RegisterMove()) return FALSE;
11590         }
11591     }
11592
11593     retVal = LoadGame(f, gameNumber, title, useList);
11594
11595     /* Make move registered during previous look at this game, if any */
11596     MakeRegisteredMove();
11597
11598     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11599         commentList[currentMove]
11600           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11601         DisplayComment(currentMove - 1, commentList[currentMove]);
11602     }
11603
11604     return retVal;
11605 }
11606
11607 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11608 int
11609 ReloadGame (int offset)
11610 {
11611     int gameNumber = lastLoadGameNumber + offset;
11612     if (lastLoadGameFP == NULL) {
11613         DisplayError(_("No game has been loaded yet"), 0);
11614         return FALSE;
11615     }
11616     if (gameNumber <= 0) {
11617         DisplayError(_("Can't back up any further"), 0);
11618         return FALSE;
11619     }
11620     if (cmailMsgLoaded) {
11621         return CmailLoadGame(lastLoadGameFP, gameNumber,
11622                              lastLoadGameTitle, lastLoadGameUseList);
11623     } else {
11624         return LoadGame(lastLoadGameFP, gameNumber,
11625                         lastLoadGameTitle, lastLoadGameUseList);
11626     }
11627 }
11628
11629 int keys[EmptySquare+1];
11630
11631 int
11632 PositionMatches (Board b1, Board b2)
11633 {
11634     int r, f, sum=0;
11635     switch(appData.searchMode) {
11636         case 1: return CompareWithRights(b1, b2);
11637         case 2:
11638             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11639                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11640             }
11641             return TRUE;
11642         case 3:
11643             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11644               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11645                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11646             }
11647             return sum==0;
11648         case 4:
11649             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11650                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11651             }
11652             return sum==0;
11653     }
11654     return TRUE;
11655 }
11656
11657 #define Q_PROMO  4
11658 #define Q_EP     3
11659 #define Q_BCASTL 2
11660 #define Q_WCASTL 1
11661
11662 int pieceList[256], quickBoard[256];
11663 ChessSquare pieceType[256] = { EmptySquare };
11664 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11665 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11666 int soughtTotal, turn;
11667 Boolean epOK, flipSearch;
11668
11669 typedef struct {
11670     unsigned char piece, to;
11671 } Move;
11672
11673 #define DSIZE (250000)
11674
11675 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11676 Move *moveDatabase = initialSpace;
11677 unsigned int movePtr, dataSize = DSIZE;
11678
11679 int
11680 MakePieceList (Board board, int *counts)
11681 {
11682     int r, f, n=Q_PROMO, total=0;
11683     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11684     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11685         int sq = f + (r<<4);
11686         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11687             quickBoard[sq] = ++n;
11688             pieceList[n] = sq;
11689             pieceType[n] = board[r][f];
11690             counts[board[r][f]]++;
11691             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11692             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11693             total++;
11694         }
11695     }
11696     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11697     return total;
11698 }
11699
11700 void
11701 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11702 {
11703     int sq = fromX + (fromY<<4);
11704     int piece = quickBoard[sq];
11705     quickBoard[sq] = 0;
11706     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11707     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11708         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11709         moveDatabase[movePtr++].piece = Q_WCASTL;
11710         quickBoard[sq] = piece;
11711         piece = quickBoard[from]; quickBoard[from] = 0;
11712         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11713     } else
11714     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11715         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11716         moveDatabase[movePtr++].piece = Q_BCASTL;
11717         quickBoard[sq] = piece;
11718         piece = quickBoard[from]; quickBoard[from] = 0;
11719         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11720     } else
11721     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11722         quickBoard[(fromY<<4)+toX] = 0;
11723         moveDatabase[movePtr].piece = Q_EP;
11724         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11725         moveDatabase[movePtr].to = sq;
11726     } else
11727     if(promoPiece != pieceType[piece]) {
11728         moveDatabase[movePtr++].piece = Q_PROMO;
11729         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11730     }
11731     moveDatabase[movePtr].piece = piece;
11732     quickBoard[sq] = piece;
11733     movePtr++;
11734 }
11735
11736 int
11737 PackGame (Board board)
11738 {
11739     Move *newSpace = NULL;
11740     moveDatabase[movePtr].piece = 0; // terminate previous game
11741     if(movePtr > dataSize) {
11742         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11743         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11744         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11745         if(newSpace) {
11746             int i;
11747             Move *p = moveDatabase, *q = newSpace;
11748             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11749             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11750             moveDatabase = newSpace;
11751         } else { // calloc failed, we must be out of memory. Too bad...
11752             dataSize = 0; // prevent calloc events for all subsequent games
11753             return 0;     // and signal this one isn't cached
11754         }
11755     }
11756     movePtr++;
11757     MakePieceList(board, counts);
11758     return movePtr;
11759 }
11760
11761 int
11762 QuickCompare (Board board, int *minCounts, int *maxCounts)
11763 {   // compare according to search mode
11764     int r, f;
11765     switch(appData.searchMode)
11766     {
11767       case 1: // exact position match
11768         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11769         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11770             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11771         }
11772         break;
11773       case 2: // can have extra material on empty squares
11774         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11775             if(board[r][f] == EmptySquare) continue;
11776             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11777         }
11778         break;
11779       case 3: // material with exact Pawn structure
11780         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11781             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11782             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11783         } // fall through to material comparison
11784       case 4: // exact material
11785         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11786         break;
11787       case 6: // material range with given imbalance
11788         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11789         // fall through to range comparison
11790       case 5: // material range
11791         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11792     }
11793     return TRUE;
11794 }
11795
11796 int
11797 QuickScan (Board board, Move *move)
11798 {   // reconstruct game,and compare all positions in it
11799     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11800     do {
11801         int piece = move->piece;
11802         int to = move->to, from = pieceList[piece];
11803         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11804           if(!piece) return -1;
11805           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11806             piece = (++move)->piece;
11807             from = pieceList[piece];
11808             counts[pieceType[piece]]--;
11809             pieceType[piece] = (ChessSquare) move->to;
11810             counts[move->to]++;
11811           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11812             counts[pieceType[quickBoard[to]]]--;
11813             quickBoard[to] = 0; total--;
11814             move++;
11815             continue;
11816           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11817             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11818             from  = pieceList[piece]; // so this must be King
11819             quickBoard[from] = 0;
11820             pieceList[piece] = to;
11821             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11822             quickBoard[from] = 0; // rook
11823             quickBoard[to] = piece;
11824             to = move->to; piece = move->piece;
11825             goto aftercastle;
11826           }
11827         }
11828         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11829         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11830         quickBoard[from] = 0;
11831       aftercastle:
11832         quickBoard[to] = piece;
11833         pieceList[piece] = to;
11834         cnt++; turn ^= 3;
11835         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11836            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11837            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11838                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11839           ) {
11840             static int lastCounts[EmptySquare+1];
11841             int i;
11842             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11843             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11844         } else stretch = 0;
11845         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11846         move++;
11847     } while(1);
11848 }
11849
11850 void
11851 InitSearch ()
11852 {
11853     int r, f;
11854     flipSearch = FALSE;
11855     CopyBoard(soughtBoard, boards[currentMove]);
11856     soughtTotal = MakePieceList(soughtBoard, maxSought);
11857     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11858     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11859     CopyBoard(reverseBoard, boards[currentMove]);
11860     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11861         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11862         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11863         reverseBoard[r][f] = piece;
11864     }
11865     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11866     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11867     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11868                  || (boards[currentMove][CASTLING][2] == NoRights ||
11869                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11870                  && (boards[currentMove][CASTLING][5] == NoRights ||
11871                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11872       ) {
11873         flipSearch = TRUE;
11874         CopyBoard(flipBoard, soughtBoard);
11875         CopyBoard(rotateBoard, reverseBoard);
11876         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11877             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11878             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11879         }
11880     }
11881     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11882     if(appData.searchMode >= 5) {
11883         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11884         MakePieceList(soughtBoard, minSought);
11885         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11886     }
11887     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11888         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11889 }
11890
11891 GameInfo dummyInfo;
11892 static int creatingBook;
11893
11894 int
11895 GameContainsPosition (FILE *f, ListGame *lg)
11896 {
11897     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11898     int fromX, fromY, toX, toY;
11899     char promoChar;
11900     static int initDone=FALSE;
11901
11902     // weed out games based on numerical tag comparison
11903     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11904     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11905     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11906     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11907     if(!initDone) {
11908         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11909         initDone = TRUE;
11910     }
11911     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11912     else CopyBoard(boards[scratch], initialPosition); // default start position
11913     if(lg->moves) {
11914         turn = btm + 1;
11915         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11916         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11917     }
11918     if(btm) plyNr++;
11919     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11920     fseek(f, lg->offset, 0);
11921     yynewfile(f);
11922     while(1) {
11923         yyboardindex = scratch;
11924         quickFlag = plyNr+1;
11925         next = Myylex();
11926         quickFlag = 0;
11927         switch(next) {
11928             case PGNTag:
11929                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11930             default:
11931                 continue;
11932
11933             case XBoardGame:
11934             case GNUChessGame:
11935                 if(plyNr) return -1; // after we have seen moves, this is for new game
11936               continue;
11937
11938             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11939             case ImpossibleMove:
11940             case WhiteWins: // game ends here with these four
11941             case BlackWins:
11942             case GameIsDrawn:
11943             case GameUnfinished:
11944                 return -1;
11945
11946             case IllegalMove:
11947                 if(appData.testLegality) return -1;
11948             case WhiteCapturesEnPassant:
11949             case BlackCapturesEnPassant:
11950             case WhitePromotion:
11951             case BlackPromotion:
11952             case WhiteNonPromotion:
11953             case BlackNonPromotion:
11954             case NormalMove:
11955             case WhiteKingSideCastle:
11956             case WhiteQueenSideCastle:
11957             case BlackKingSideCastle:
11958             case BlackQueenSideCastle:
11959             case WhiteKingSideCastleWild:
11960             case WhiteQueenSideCastleWild:
11961             case BlackKingSideCastleWild:
11962             case BlackQueenSideCastleWild:
11963             case WhiteHSideCastleFR:
11964             case WhiteASideCastleFR:
11965             case BlackHSideCastleFR:
11966             case BlackASideCastleFR:
11967                 fromX = currentMoveString[0] - AAA;
11968                 fromY = currentMoveString[1] - ONE;
11969                 toX = currentMoveString[2] - AAA;
11970                 toY = currentMoveString[3] - ONE;
11971                 promoChar = currentMoveString[4];
11972                 break;
11973             case WhiteDrop:
11974             case BlackDrop:
11975                 fromX = next == WhiteDrop ?
11976                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11977                   (int) CharToPiece(ToLower(currentMoveString[0]));
11978                 fromY = DROP_RANK;
11979                 toX = currentMoveString[2] - AAA;
11980                 toY = currentMoveString[3] - ONE;
11981                 promoChar = 0;
11982                 break;
11983         }
11984         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11985         plyNr++;
11986         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11987         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11988         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11989         if(appData.findMirror) {
11990             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11991             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11992         }
11993     }
11994 }
11995
11996 /* Load the nth game from open file f */
11997 int
11998 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11999 {
12000     ChessMove cm;
12001     char buf[MSG_SIZ];
12002     int gn = gameNumber;
12003     ListGame *lg = NULL;
12004     int numPGNTags = 0;
12005     int err, pos = -1;
12006     GameMode oldGameMode;
12007     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12008
12009     if (appData.debugMode)
12010         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12011
12012     if (gameMode == Training )
12013         SetTrainingModeOff();
12014
12015     oldGameMode = gameMode;
12016     if (gameMode != BeginningOfGame) {
12017       Reset(FALSE, TRUE);
12018     }
12019
12020     gameFileFP = f;
12021     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12022         fclose(lastLoadGameFP);
12023     }
12024
12025     if (useList) {
12026         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12027
12028         if (lg) {
12029             fseek(f, lg->offset, 0);
12030             GameListHighlight(gameNumber);
12031             pos = lg->position;
12032             gn = 1;
12033         }
12034         else {
12035             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12036               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12037             else
12038             DisplayError(_("Game number out of range"), 0);
12039             return FALSE;
12040         }
12041     } else {
12042         GameListDestroy();
12043         if (fseek(f, 0, 0) == -1) {
12044             if (f == lastLoadGameFP ?
12045                 gameNumber == lastLoadGameNumber + 1 :
12046                 gameNumber == 1) {
12047                 gn = 1;
12048             } else {
12049                 DisplayError(_("Can't seek on game file"), 0);
12050                 return FALSE;
12051             }
12052         }
12053     }
12054     lastLoadGameFP = f;
12055     lastLoadGameNumber = gameNumber;
12056     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12057     lastLoadGameUseList = useList;
12058
12059     yynewfile(f);
12060
12061     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12062       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12063                 lg->gameInfo.black);
12064             DisplayTitle(buf);
12065     } else if (*title != NULLCHAR) {
12066         if (gameNumber > 1) {
12067           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12068             DisplayTitle(buf);
12069         } else {
12070             DisplayTitle(title);
12071         }
12072     }
12073
12074     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12075         gameMode = PlayFromGameFile;
12076         ModeHighlight();
12077     }
12078
12079     currentMove = forwardMostMove = backwardMostMove = 0;
12080     CopyBoard(boards[0], initialPosition);
12081     StopClocks();
12082
12083     /*
12084      * Skip the first gn-1 games in the file.
12085      * Also skip over anything that precedes an identifiable
12086      * start of game marker, to avoid being confused by
12087      * garbage at the start of the file.  Currently
12088      * recognized start of game markers are the move number "1",
12089      * the pattern "gnuchess .* game", the pattern
12090      * "^[#;%] [^ ]* game file", and a PGN tag block.
12091      * A game that starts with one of the latter two patterns
12092      * will also have a move number 1, possibly
12093      * following a position diagram.
12094      * 5-4-02: Let's try being more lenient and allowing a game to
12095      * start with an unnumbered move.  Does that break anything?
12096      */
12097     cm = lastLoadGameStart = EndOfFile;
12098     while (gn > 0) {
12099         yyboardindex = forwardMostMove;
12100         cm = (ChessMove) Myylex();
12101         switch (cm) {
12102           case EndOfFile:
12103             if (cmailMsgLoaded) {
12104                 nCmailGames = CMAIL_MAX_GAMES - gn;
12105             } else {
12106                 Reset(TRUE, TRUE);
12107                 DisplayError(_("Game not found in file"), 0);
12108             }
12109             return FALSE;
12110
12111           case GNUChessGame:
12112           case XBoardGame:
12113             gn--;
12114             lastLoadGameStart = cm;
12115             break;
12116
12117           case MoveNumberOne:
12118             switch (lastLoadGameStart) {
12119               case GNUChessGame:
12120               case XBoardGame:
12121               case PGNTag:
12122                 break;
12123               case MoveNumberOne:
12124               case EndOfFile:
12125                 gn--;           /* count this game */
12126                 lastLoadGameStart = cm;
12127                 break;
12128               default:
12129                 /* impossible */
12130                 break;
12131             }
12132             break;
12133
12134           case PGNTag:
12135             switch (lastLoadGameStart) {
12136               case GNUChessGame:
12137               case PGNTag:
12138               case MoveNumberOne:
12139               case EndOfFile:
12140                 gn--;           /* count this game */
12141                 lastLoadGameStart = cm;
12142                 break;
12143               case XBoardGame:
12144                 lastLoadGameStart = cm; /* game counted already */
12145                 break;
12146               default:
12147                 /* impossible */
12148                 break;
12149             }
12150             if (gn > 0) {
12151                 do {
12152                     yyboardindex = forwardMostMove;
12153                     cm = (ChessMove) Myylex();
12154                 } while (cm == PGNTag || cm == Comment);
12155             }
12156             break;
12157
12158           case WhiteWins:
12159           case BlackWins:
12160           case GameIsDrawn:
12161             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12162                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12163                     != CMAIL_OLD_RESULT) {
12164                     nCmailResults ++ ;
12165                     cmailResult[  CMAIL_MAX_GAMES
12166                                 - gn - 1] = CMAIL_OLD_RESULT;
12167                 }
12168             }
12169             break;
12170
12171           case NormalMove:
12172             /* Only a NormalMove can be at the start of a game
12173              * without a position diagram. */
12174             if (lastLoadGameStart == EndOfFile ) {
12175               gn--;
12176               lastLoadGameStart = MoveNumberOne;
12177             }
12178             break;
12179
12180           default:
12181             break;
12182         }
12183     }
12184
12185     if (appData.debugMode)
12186       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12187
12188     if (cm == XBoardGame) {
12189         /* Skip any header junk before position diagram and/or move 1 */
12190         for (;;) {
12191             yyboardindex = forwardMostMove;
12192             cm = (ChessMove) Myylex();
12193
12194             if (cm == EndOfFile ||
12195                 cm == GNUChessGame || cm == XBoardGame) {
12196                 /* Empty game; pretend end-of-file and handle later */
12197                 cm = EndOfFile;
12198                 break;
12199             }
12200
12201             if (cm == MoveNumberOne || cm == PositionDiagram ||
12202                 cm == PGNTag || cm == Comment)
12203               break;
12204         }
12205     } else if (cm == GNUChessGame) {
12206         if (gameInfo.event != NULL) {
12207             free(gameInfo.event);
12208         }
12209         gameInfo.event = StrSave(yy_text);
12210     }
12211
12212     startedFromSetupPosition = FALSE;
12213     while (cm == PGNTag) {
12214         if (appData.debugMode)
12215           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12216         err = ParsePGNTag(yy_text, &gameInfo);
12217         if (!err) numPGNTags++;
12218
12219         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12220         if(gameInfo.variant != oldVariant) {
12221             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12222             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12223             InitPosition(TRUE);
12224             oldVariant = gameInfo.variant;
12225             if (appData.debugMode)
12226               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12227         }
12228
12229
12230         if (gameInfo.fen != NULL) {
12231           Board initial_position;
12232           startedFromSetupPosition = TRUE;
12233           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12234             Reset(TRUE, TRUE);
12235             DisplayError(_("Bad FEN position in file"), 0);
12236             return FALSE;
12237           }
12238           CopyBoard(boards[0], initial_position);
12239           if (blackPlaysFirst) {
12240             currentMove = forwardMostMove = backwardMostMove = 1;
12241             CopyBoard(boards[1], initial_position);
12242             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12243             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12244             timeRemaining[0][1] = whiteTimeRemaining;
12245             timeRemaining[1][1] = blackTimeRemaining;
12246             if (commentList[0] != NULL) {
12247               commentList[1] = commentList[0];
12248               commentList[0] = NULL;
12249             }
12250           } else {
12251             currentMove = forwardMostMove = backwardMostMove = 0;
12252           }
12253           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12254           {   int i;
12255               initialRulePlies = FENrulePlies;
12256               for( i=0; i< nrCastlingRights; i++ )
12257                   initialRights[i] = initial_position[CASTLING][i];
12258           }
12259           yyboardindex = forwardMostMove;
12260           free(gameInfo.fen);
12261           gameInfo.fen = NULL;
12262         }
12263
12264         yyboardindex = forwardMostMove;
12265         cm = (ChessMove) Myylex();
12266
12267         /* Handle comments interspersed among the tags */
12268         while (cm == Comment) {
12269             char *p;
12270             if (appData.debugMode)
12271               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12272             p = yy_text;
12273             AppendComment(currentMove, p, FALSE);
12274             yyboardindex = forwardMostMove;
12275             cm = (ChessMove) Myylex();
12276         }
12277     }
12278
12279     /* don't rely on existence of Event tag since if game was
12280      * pasted from clipboard the Event tag may not exist
12281      */
12282     if (numPGNTags > 0){
12283         char *tags;
12284         if (gameInfo.variant == VariantNormal) {
12285           VariantClass v = StringToVariant(gameInfo.event);
12286           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12287           if(v < VariantShogi) gameInfo.variant = v;
12288         }
12289         if (!matchMode) {
12290           if( appData.autoDisplayTags ) {
12291             tags = PGNTags(&gameInfo);
12292             TagsPopUp(tags, CmailMsg());
12293             free(tags);
12294           }
12295         }
12296     } else {
12297         /* Make something up, but don't display it now */
12298         SetGameInfo();
12299         TagsPopDown();
12300     }
12301
12302     if (cm == PositionDiagram) {
12303         int i, j;
12304         char *p;
12305         Board initial_position;
12306
12307         if (appData.debugMode)
12308           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12309
12310         if (!startedFromSetupPosition) {
12311             p = yy_text;
12312             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12313               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12314                 switch (*p) {
12315                   case '{':
12316                   case '[':
12317                   case '-':
12318                   case ' ':
12319                   case '\t':
12320                   case '\n':
12321                   case '\r':
12322                     break;
12323                   default:
12324                     initial_position[i][j++] = CharToPiece(*p);
12325                     break;
12326                 }
12327             while (*p == ' ' || *p == '\t' ||
12328                    *p == '\n' || *p == '\r') p++;
12329
12330             if (strncmp(p, "black", strlen("black"))==0)
12331               blackPlaysFirst = TRUE;
12332             else
12333               blackPlaysFirst = FALSE;
12334             startedFromSetupPosition = TRUE;
12335
12336             CopyBoard(boards[0], initial_position);
12337             if (blackPlaysFirst) {
12338                 currentMove = forwardMostMove = backwardMostMove = 1;
12339                 CopyBoard(boards[1], initial_position);
12340                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12341                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12342                 timeRemaining[0][1] = whiteTimeRemaining;
12343                 timeRemaining[1][1] = blackTimeRemaining;
12344                 if (commentList[0] != NULL) {
12345                     commentList[1] = commentList[0];
12346                     commentList[0] = NULL;
12347                 }
12348             } else {
12349                 currentMove = forwardMostMove = backwardMostMove = 0;
12350             }
12351         }
12352         yyboardindex = forwardMostMove;
12353         cm = (ChessMove) Myylex();
12354     }
12355
12356   if(!creatingBook) {
12357     if (first.pr == NoProc) {
12358         StartChessProgram(&first);
12359     }
12360     InitChessProgram(&first, FALSE);
12361     SendToProgram("force\n", &first);
12362     if (startedFromSetupPosition) {
12363         SendBoard(&first, forwardMostMove);
12364     if (appData.debugMode) {
12365         fprintf(debugFP, "Load Game\n");
12366     }
12367         DisplayBothClocks();
12368     }
12369   }
12370
12371     /* [HGM] server: flag to write setup moves in broadcast file as one */
12372     loadFlag = appData.suppressLoadMoves;
12373
12374     while (cm == Comment) {
12375         char *p;
12376         if (appData.debugMode)
12377           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12378         p = yy_text;
12379         AppendComment(currentMove, p, FALSE);
12380         yyboardindex = forwardMostMove;
12381         cm = (ChessMove) Myylex();
12382     }
12383
12384     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12385         cm == WhiteWins || cm == BlackWins ||
12386         cm == GameIsDrawn || cm == GameUnfinished) {
12387         DisplayMessage("", _("No moves in game"));
12388         if (cmailMsgLoaded) {
12389             if (appData.debugMode)
12390               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12391             ClearHighlights();
12392             flipView = FALSE;
12393         }
12394         DrawPosition(FALSE, boards[currentMove]);
12395         DisplayBothClocks();
12396         gameMode = EditGame;
12397         ModeHighlight();
12398         gameFileFP = NULL;
12399         cmailOldMove = 0;
12400         return TRUE;
12401     }
12402
12403     // [HGM] PV info: routine tests if comment empty
12404     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12405         DisplayComment(currentMove - 1, commentList[currentMove]);
12406     }
12407     if (!matchMode && appData.timeDelay != 0)
12408       DrawPosition(FALSE, boards[currentMove]);
12409
12410     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12411       programStats.ok_to_send = 1;
12412     }
12413
12414     /* if the first token after the PGN tags is a move
12415      * and not move number 1, retrieve it from the parser
12416      */
12417     if (cm != MoveNumberOne)
12418         LoadGameOneMove(cm);
12419
12420     /* load the remaining moves from the file */
12421     while (LoadGameOneMove(EndOfFile)) {
12422       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12423       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12424     }
12425
12426     /* rewind to the start of the game */
12427     currentMove = backwardMostMove;
12428
12429     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12430
12431     if (oldGameMode == AnalyzeFile ||
12432         oldGameMode == AnalyzeMode) {
12433       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12434       AnalyzeFileEvent();
12435     }
12436
12437     if(creatingBook) return TRUE;
12438     if (!matchMode && pos > 0) {
12439         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12440     } else
12441     if (matchMode || appData.timeDelay == 0) {
12442       ToEndEvent();
12443     } else if (appData.timeDelay > 0) {
12444       AutoPlayGameLoop();
12445     }
12446
12447     if (appData.debugMode)
12448         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12449
12450     loadFlag = 0; /* [HGM] true game starts */
12451     return TRUE;
12452 }
12453
12454 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12455 int
12456 ReloadPosition (int offset)
12457 {
12458     int positionNumber = lastLoadPositionNumber + offset;
12459     if (lastLoadPositionFP == NULL) {
12460         DisplayError(_("No position has been loaded yet"), 0);
12461         return FALSE;
12462     }
12463     if (positionNumber <= 0) {
12464         DisplayError(_("Can't back up any further"), 0);
12465         return FALSE;
12466     }
12467     return LoadPosition(lastLoadPositionFP, positionNumber,
12468                         lastLoadPositionTitle);
12469 }
12470
12471 /* Load the nth position from the given file */
12472 int
12473 LoadPositionFromFile (char *filename, int n, char *title)
12474 {
12475     FILE *f;
12476     char buf[MSG_SIZ];
12477
12478     if (strcmp(filename, "-") == 0) {
12479         return LoadPosition(stdin, n, "stdin");
12480     } else {
12481         f = fopen(filename, "rb");
12482         if (f == NULL) {
12483             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12484             DisplayError(buf, errno);
12485             return FALSE;
12486         } else {
12487             return LoadPosition(f, n, title);
12488         }
12489     }
12490 }
12491
12492 /* Load the nth position from the given open file, and close it */
12493 int
12494 LoadPosition (FILE *f, int positionNumber, char *title)
12495 {
12496     char *p, line[MSG_SIZ];
12497     Board initial_position;
12498     int i, j, fenMode, pn;
12499
12500     if (gameMode == Training )
12501         SetTrainingModeOff();
12502
12503     if (gameMode != BeginningOfGame) {
12504         Reset(FALSE, TRUE);
12505     }
12506     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12507         fclose(lastLoadPositionFP);
12508     }
12509     if (positionNumber == 0) positionNumber = 1;
12510     lastLoadPositionFP = f;
12511     lastLoadPositionNumber = positionNumber;
12512     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12513     if (first.pr == NoProc && !appData.noChessProgram) {
12514       StartChessProgram(&first);
12515       InitChessProgram(&first, FALSE);
12516     }
12517     pn = positionNumber;
12518     if (positionNumber < 0) {
12519         /* Negative position number means to seek to that byte offset */
12520         if (fseek(f, -positionNumber, 0) == -1) {
12521             DisplayError(_("Can't seek on position file"), 0);
12522             return FALSE;
12523         };
12524         pn = 1;
12525     } else {
12526         if (fseek(f, 0, 0) == -1) {
12527             if (f == lastLoadPositionFP ?
12528                 positionNumber == lastLoadPositionNumber + 1 :
12529                 positionNumber == 1) {
12530                 pn = 1;
12531             } else {
12532                 DisplayError(_("Can't seek on position file"), 0);
12533                 return FALSE;
12534             }
12535         }
12536     }
12537     /* See if this file is FEN or old-style xboard */
12538     if (fgets(line, MSG_SIZ, f) == NULL) {
12539         DisplayError(_("Position not found in file"), 0);
12540         return FALSE;
12541     }
12542     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12543     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12544
12545     if (pn >= 2) {
12546         if (fenMode || line[0] == '#') pn--;
12547         while (pn > 0) {
12548             /* skip positions before number pn */
12549             if (fgets(line, MSG_SIZ, f) == NULL) {
12550                 Reset(TRUE, TRUE);
12551                 DisplayError(_("Position not found in file"), 0);
12552                 return FALSE;
12553             }
12554             if (fenMode || line[0] == '#') pn--;
12555         }
12556     }
12557
12558     if (fenMode) {
12559         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12560             DisplayError(_("Bad FEN position in file"), 0);
12561             return FALSE;
12562         }
12563     } else {
12564         (void) fgets(line, MSG_SIZ, f);
12565         (void) fgets(line, MSG_SIZ, f);
12566
12567         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12568             (void) fgets(line, MSG_SIZ, f);
12569             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12570                 if (*p == ' ')
12571                   continue;
12572                 initial_position[i][j++] = CharToPiece(*p);
12573             }
12574         }
12575
12576         blackPlaysFirst = FALSE;
12577         if (!feof(f)) {
12578             (void) fgets(line, MSG_SIZ, f);
12579             if (strncmp(line, "black", strlen("black"))==0)
12580               blackPlaysFirst = TRUE;
12581         }
12582     }
12583     startedFromSetupPosition = TRUE;
12584
12585     CopyBoard(boards[0], initial_position);
12586     if (blackPlaysFirst) {
12587         currentMove = forwardMostMove = backwardMostMove = 1;
12588         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12589         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12590         CopyBoard(boards[1], initial_position);
12591         DisplayMessage("", _("Black to play"));
12592     } else {
12593         currentMove = forwardMostMove = backwardMostMove = 0;
12594         DisplayMessage("", _("White to play"));
12595     }
12596     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12597     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12598         SendToProgram("force\n", &first);
12599         SendBoard(&first, forwardMostMove);
12600     }
12601     if (appData.debugMode) {
12602 int i, j;
12603   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12604   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12605         fprintf(debugFP, "Load Position\n");
12606     }
12607
12608     if (positionNumber > 1) {
12609       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12610         DisplayTitle(line);
12611     } else {
12612         DisplayTitle(title);
12613     }
12614     gameMode = EditGame;
12615     ModeHighlight();
12616     ResetClocks();
12617     timeRemaining[0][1] = whiteTimeRemaining;
12618     timeRemaining[1][1] = blackTimeRemaining;
12619     DrawPosition(FALSE, boards[currentMove]);
12620
12621     return TRUE;
12622 }
12623
12624
12625 void
12626 CopyPlayerNameIntoFileName (char **dest, char *src)
12627 {
12628     while (*src != NULLCHAR && *src != ',') {
12629         if (*src == ' ') {
12630             *(*dest)++ = '_';
12631             src++;
12632         } else {
12633             *(*dest)++ = *src++;
12634         }
12635     }
12636 }
12637
12638 char *
12639 DefaultFileName (char *ext)
12640 {
12641     static char def[MSG_SIZ];
12642     char *p;
12643
12644     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12645         p = def;
12646         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12647         *p++ = '-';
12648         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12649         *p++ = '.';
12650         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12651     } else {
12652         def[0] = NULLCHAR;
12653     }
12654     return def;
12655 }
12656
12657 /* Save the current game to the given file */
12658 int
12659 SaveGameToFile (char *filename, int append)
12660 {
12661     FILE *f;
12662     char buf[MSG_SIZ];
12663     int result, i, t,tot=0;
12664
12665     if (strcmp(filename, "-") == 0) {
12666         return SaveGame(stdout, 0, NULL);
12667     } else {
12668         for(i=0; i<10; i++) { // upto 10 tries
12669              f = fopen(filename, append ? "a" : "w");
12670              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12671              if(f || errno != 13) break;
12672              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12673              tot += t;
12674         }
12675         if (f == NULL) {
12676             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12677             DisplayError(buf, errno);
12678             return FALSE;
12679         } else {
12680             safeStrCpy(buf, lastMsg, MSG_SIZ);
12681             DisplayMessage(_("Waiting for access to save file"), "");
12682             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12683             DisplayMessage(_("Saving game"), "");
12684             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12685             result = SaveGame(f, 0, NULL);
12686             DisplayMessage(buf, "");
12687             return result;
12688         }
12689     }
12690 }
12691
12692 char *
12693 SavePart (char *str)
12694 {
12695     static char buf[MSG_SIZ];
12696     char *p;
12697
12698     p = strchr(str, ' ');
12699     if (p == NULL) return str;
12700     strncpy(buf, str, p - str);
12701     buf[p - str] = NULLCHAR;
12702     return buf;
12703 }
12704
12705 #define PGN_MAX_LINE 75
12706
12707 #define PGN_SIDE_WHITE  0
12708 #define PGN_SIDE_BLACK  1
12709
12710 static int
12711 FindFirstMoveOutOfBook (int side)
12712 {
12713     int result = -1;
12714
12715     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12716         int index = backwardMostMove;
12717         int has_book_hit = 0;
12718
12719         if( (index % 2) != side ) {
12720             index++;
12721         }
12722
12723         while( index < forwardMostMove ) {
12724             /* Check to see if engine is in book */
12725             int depth = pvInfoList[index].depth;
12726             int score = pvInfoList[index].score;
12727             int in_book = 0;
12728
12729             if( depth <= 2 ) {
12730                 in_book = 1;
12731             }
12732             else if( score == 0 && depth == 63 ) {
12733                 in_book = 1; /* Zappa */
12734             }
12735             else if( score == 2 && depth == 99 ) {
12736                 in_book = 1; /* Abrok */
12737             }
12738
12739             has_book_hit += in_book;
12740
12741             if( ! in_book ) {
12742                 result = index;
12743
12744                 break;
12745             }
12746
12747             index += 2;
12748         }
12749     }
12750
12751     return result;
12752 }
12753
12754 void
12755 GetOutOfBookInfo (char * buf)
12756 {
12757     int oob[2];
12758     int i;
12759     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12760
12761     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12762     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12763
12764     *buf = '\0';
12765
12766     if( oob[0] >= 0 || oob[1] >= 0 ) {
12767         for( i=0; i<2; i++ ) {
12768             int idx = oob[i];
12769
12770             if( idx >= 0 ) {
12771                 if( i > 0 && oob[0] >= 0 ) {
12772                     strcat( buf, "   " );
12773                 }
12774
12775                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12776                 sprintf( buf+strlen(buf), "%s%.2f",
12777                     pvInfoList[idx].score >= 0 ? "+" : "",
12778                     pvInfoList[idx].score / 100.0 );
12779             }
12780         }
12781     }
12782 }
12783
12784 /* Save game in PGN style and close the file */
12785 int
12786 SaveGamePGN (FILE *f)
12787 {
12788     int i, offset, linelen, newblock;
12789 //    char *movetext;
12790     char numtext[32];
12791     int movelen, numlen, blank;
12792     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12793
12794     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12795
12796     PrintPGNTags(f, &gameInfo);
12797
12798     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12799
12800     if (backwardMostMove > 0 || startedFromSetupPosition) {
12801         char *fen = PositionToFEN(backwardMostMove, NULL);
12802         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12803         fprintf(f, "\n{--------------\n");
12804         PrintPosition(f, backwardMostMove);
12805         fprintf(f, "--------------}\n");
12806         free(fen);
12807     }
12808     else {
12809         /* [AS] Out of book annotation */
12810         if( appData.saveOutOfBookInfo ) {
12811             char buf[64];
12812
12813             GetOutOfBookInfo( buf );
12814
12815             if( buf[0] != '\0' ) {
12816                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12817             }
12818         }
12819
12820         fprintf(f, "\n");
12821     }
12822
12823     i = backwardMostMove;
12824     linelen = 0;
12825     newblock = TRUE;
12826
12827     while (i < forwardMostMove) {
12828         /* Print comments preceding this move */
12829         if (commentList[i] != NULL) {
12830             if (linelen > 0) fprintf(f, "\n");
12831             fprintf(f, "%s", commentList[i]);
12832             linelen = 0;
12833             newblock = TRUE;
12834         }
12835
12836         /* Format move number */
12837         if ((i % 2) == 0)
12838           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12839         else
12840           if (newblock)
12841             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12842           else
12843             numtext[0] = NULLCHAR;
12844
12845         numlen = strlen(numtext);
12846         newblock = FALSE;
12847
12848         /* Print move number */
12849         blank = linelen > 0 && numlen > 0;
12850         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12851             fprintf(f, "\n");
12852             linelen = 0;
12853             blank = 0;
12854         }
12855         if (blank) {
12856             fprintf(f, " ");
12857             linelen++;
12858         }
12859         fprintf(f, "%s", numtext);
12860         linelen += numlen;
12861
12862         /* Get move */
12863         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12864         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12865
12866         /* Print move */
12867         blank = linelen > 0 && movelen > 0;
12868         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12869             fprintf(f, "\n");
12870             linelen = 0;
12871             blank = 0;
12872         }
12873         if (blank) {
12874             fprintf(f, " ");
12875             linelen++;
12876         }
12877         fprintf(f, "%s", move_buffer);
12878         linelen += movelen;
12879
12880         /* [AS] Add PV info if present */
12881         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12882             /* [HGM] add time */
12883             char buf[MSG_SIZ]; int seconds;
12884
12885             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12886
12887             if( seconds <= 0)
12888               buf[0] = 0;
12889             else
12890               if( seconds < 30 )
12891                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12892               else
12893                 {
12894                   seconds = (seconds + 4)/10; // round to full seconds
12895                   if( seconds < 60 )
12896                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12897                   else
12898                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12899                 }
12900
12901             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12902                       pvInfoList[i].score >= 0 ? "+" : "",
12903                       pvInfoList[i].score / 100.0,
12904                       pvInfoList[i].depth,
12905                       buf );
12906
12907             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12908
12909             /* Print score/depth */
12910             blank = linelen > 0 && movelen > 0;
12911             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12912                 fprintf(f, "\n");
12913                 linelen = 0;
12914                 blank = 0;
12915             }
12916             if (blank) {
12917                 fprintf(f, " ");
12918                 linelen++;
12919             }
12920             fprintf(f, "%s", move_buffer);
12921             linelen += movelen;
12922         }
12923
12924         i++;
12925     }
12926
12927     /* Start a new line */
12928     if (linelen > 0) fprintf(f, "\n");
12929
12930     /* Print comments after last move */
12931     if (commentList[i] != NULL) {
12932         fprintf(f, "%s\n", commentList[i]);
12933     }
12934
12935     /* Print result */
12936     if (gameInfo.resultDetails != NULL &&
12937         gameInfo.resultDetails[0] != NULLCHAR) {
12938         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12939                 PGNResult(gameInfo.result));
12940     } else {
12941         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12942     }
12943
12944     fclose(f);
12945     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12946     return TRUE;
12947 }
12948
12949 /* Save game in old style and close the file */
12950 int
12951 SaveGameOldStyle (FILE *f)
12952 {
12953     int i, offset;
12954     time_t tm;
12955
12956     tm = time((time_t *) NULL);
12957
12958     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12959     PrintOpponents(f);
12960
12961     if (backwardMostMove > 0 || startedFromSetupPosition) {
12962         fprintf(f, "\n[--------------\n");
12963         PrintPosition(f, backwardMostMove);
12964         fprintf(f, "--------------]\n");
12965     } else {
12966         fprintf(f, "\n");
12967     }
12968
12969     i = backwardMostMove;
12970     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12971
12972     while (i < forwardMostMove) {
12973         if (commentList[i] != NULL) {
12974             fprintf(f, "[%s]\n", commentList[i]);
12975         }
12976
12977         if ((i % 2) == 1) {
12978             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12979             i++;
12980         } else {
12981             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12982             i++;
12983             if (commentList[i] != NULL) {
12984                 fprintf(f, "\n");
12985                 continue;
12986             }
12987             if (i >= forwardMostMove) {
12988                 fprintf(f, "\n");
12989                 break;
12990             }
12991             fprintf(f, "%s\n", parseList[i]);
12992             i++;
12993         }
12994     }
12995
12996     if (commentList[i] != NULL) {
12997         fprintf(f, "[%s]\n", commentList[i]);
12998     }
12999
13000     /* This isn't really the old style, but it's close enough */
13001     if (gameInfo.resultDetails != NULL &&
13002         gameInfo.resultDetails[0] != NULLCHAR) {
13003         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13004                 gameInfo.resultDetails);
13005     } else {
13006         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13007     }
13008
13009     fclose(f);
13010     return TRUE;
13011 }
13012
13013 /* Save the current game to open file f and close the file */
13014 int
13015 SaveGame (FILE *f, int dummy, char *dummy2)
13016 {
13017     if (gameMode == EditPosition) EditPositionDone(TRUE);
13018     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13019     if (appData.oldSaveStyle)
13020       return SaveGameOldStyle(f);
13021     else
13022       return SaveGamePGN(f);
13023 }
13024
13025 /* Save the current position to the given file */
13026 int
13027 SavePositionToFile (char *filename)
13028 {
13029     FILE *f;
13030     char buf[MSG_SIZ];
13031
13032     if (strcmp(filename, "-") == 0) {
13033         return SavePosition(stdout, 0, NULL);
13034     } else {
13035         f = fopen(filename, "a");
13036         if (f == NULL) {
13037             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13038             DisplayError(buf, errno);
13039             return FALSE;
13040         } else {
13041             safeStrCpy(buf, lastMsg, MSG_SIZ);
13042             DisplayMessage(_("Waiting for access to save file"), "");
13043             flock(fileno(f), LOCK_EX); // [HGM] lock
13044             DisplayMessage(_("Saving position"), "");
13045             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13046             SavePosition(f, 0, NULL);
13047             DisplayMessage(buf, "");
13048             return TRUE;
13049         }
13050     }
13051 }
13052
13053 /* Save the current position to the given open file and close the file */
13054 int
13055 SavePosition (FILE *f, int dummy, char *dummy2)
13056 {
13057     time_t tm;
13058     char *fen;
13059
13060     if (gameMode == EditPosition) EditPositionDone(TRUE);
13061     if (appData.oldSaveStyle) {
13062         tm = time((time_t *) NULL);
13063
13064         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13065         PrintOpponents(f);
13066         fprintf(f, "[--------------\n");
13067         PrintPosition(f, currentMove);
13068         fprintf(f, "--------------]\n");
13069     } else {
13070         fen = PositionToFEN(currentMove, NULL);
13071         fprintf(f, "%s\n", fen);
13072         free(fen);
13073     }
13074     fclose(f);
13075     return TRUE;
13076 }
13077
13078 void
13079 ReloadCmailMsgEvent (int unregister)
13080 {
13081 #if !WIN32
13082     static char *inFilename = NULL;
13083     static char *outFilename;
13084     int i;
13085     struct stat inbuf, outbuf;
13086     int status;
13087
13088     /* Any registered moves are unregistered if unregister is set, */
13089     /* i.e. invoked by the signal handler */
13090     if (unregister) {
13091         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13092             cmailMoveRegistered[i] = FALSE;
13093             if (cmailCommentList[i] != NULL) {
13094                 free(cmailCommentList[i]);
13095                 cmailCommentList[i] = NULL;
13096             }
13097         }
13098         nCmailMovesRegistered = 0;
13099     }
13100
13101     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13102         cmailResult[i] = CMAIL_NOT_RESULT;
13103     }
13104     nCmailResults = 0;
13105
13106     if (inFilename == NULL) {
13107         /* Because the filenames are static they only get malloced once  */
13108         /* and they never get freed                                      */
13109         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13110         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13111
13112         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13113         sprintf(outFilename, "%s.out", appData.cmailGameName);
13114     }
13115
13116     status = stat(outFilename, &outbuf);
13117     if (status < 0) {
13118         cmailMailedMove = FALSE;
13119     } else {
13120         status = stat(inFilename, &inbuf);
13121         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13122     }
13123
13124     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13125        counts the games, notes how each one terminated, etc.
13126
13127        It would be nice to remove this kludge and instead gather all
13128        the information while building the game list.  (And to keep it
13129        in the game list nodes instead of having a bunch of fixed-size
13130        parallel arrays.)  Note this will require getting each game's
13131        termination from the PGN tags, as the game list builder does
13132        not process the game moves.  --mann
13133        */
13134     cmailMsgLoaded = TRUE;
13135     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13136
13137     /* Load first game in the file or popup game menu */
13138     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13139
13140 #endif /* !WIN32 */
13141     return;
13142 }
13143
13144 int
13145 RegisterMove ()
13146 {
13147     FILE *f;
13148     char string[MSG_SIZ];
13149
13150     if (   cmailMailedMove
13151         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13152         return TRUE;            /* Allow free viewing  */
13153     }
13154
13155     /* Unregister move to ensure that we don't leave RegisterMove        */
13156     /* with the move registered when the conditions for registering no   */
13157     /* longer hold                                                       */
13158     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13159         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13160         nCmailMovesRegistered --;
13161
13162         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13163           {
13164               free(cmailCommentList[lastLoadGameNumber - 1]);
13165               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13166           }
13167     }
13168
13169     if (cmailOldMove == -1) {
13170         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13171         return FALSE;
13172     }
13173
13174     if (currentMove > cmailOldMove + 1) {
13175         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13176         return FALSE;
13177     }
13178
13179     if (currentMove < cmailOldMove) {
13180         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13181         return FALSE;
13182     }
13183
13184     if (forwardMostMove > currentMove) {
13185         /* Silently truncate extra moves */
13186         TruncateGame();
13187     }
13188
13189     if (   (currentMove == cmailOldMove + 1)
13190         || (   (currentMove == cmailOldMove)
13191             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13192                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13193         if (gameInfo.result != GameUnfinished) {
13194             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13195         }
13196
13197         if (commentList[currentMove] != NULL) {
13198             cmailCommentList[lastLoadGameNumber - 1]
13199               = StrSave(commentList[currentMove]);
13200         }
13201         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13202
13203         if (appData.debugMode)
13204           fprintf(debugFP, "Saving %s for game %d\n",
13205                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13206
13207         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13208
13209         f = fopen(string, "w");
13210         if (appData.oldSaveStyle) {
13211             SaveGameOldStyle(f); /* also closes the file */
13212
13213             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13214             f = fopen(string, "w");
13215             SavePosition(f, 0, NULL); /* also closes the file */
13216         } else {
13217             fprintf(f, "{--------------\n");
13218             PrintPosition(f, currentMove);
13219             fprintf(f, "--------------}\n\n");
13220
13221             SaveGame(f, 0, NULL); /* also closes the file*/
13222         }
13223
13224         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13225         nCmailMovesRegistered ++;
13226     } else if (nCmailGames == 1) {
13227         DisplayError(_("You have not made a move yet"), 0);
13228         return FALSE;
13229     }
13230
13231     return TRUE;
13232 }
13233
13234 void
13235 MailMoveEvent ()
13236 {
13237 #if !WIN32
13238     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13239     FILE *commandOutput;
13240     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13241     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13242     int nBuffers;
13243     int i;
13244     int archived;
13245     char *arcDir;
13246
13247     if (! cmailMsgLoaded) {
13248         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13249         return;
13250     }
13251
13252     if (nCmailGames == nCmailResults) {
13253         DisplayError(_("No unfinished games"), 0);
13254         return;
13255     }
13256
13257 #if CMAIL_PROHIBIT_REMAIL
13258     if (cmailMailedMove) {
13259       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);
13260         DisplayError(msg, 0);
13261         return;
13262     }
13263 #endif
13264
13265     if (! (cmailMailedMove || RegisterMove())) return;
13266
13267     if (   cmailMailedMove
13268         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13269       snprintf(string, MSG_SIZ, partCommandString,
13270                appData.debugMode ? " -v" : "", appData.cmailGameName);
13271         commandOutput = popen(string, "r");
13272
13273         if (commandOutput == NULL) {
13274             DisplayError(_("Failed to invoke cmail"), 0);
13275         } else {
13276             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13277                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13278             }
13279             if (nBuffers > 1) {
13280                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13281                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13282                 nBytes = MSG_SIZ - 1;
13283             } else {
13284                 (void) memcpy(msg, buffer, nBytes);
13285             }
13286             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13287
13288             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13289                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13290
13291                 archived = TRUE;
13292                 for (i = 0; i < nCmailGames; i ++) {
13293                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13294                         archived = FALSE;
13295                     }
13296                 }
13297                 if (   archived
13298                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13299                         != NULL)) {
13300                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13301                            arcDir,
13302                            appData.cmailGameName,
13303                            gameInfo.date);
13304                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13305                     cmailMsgLoaded = FALSE;
13306                 }
13307             }
13308
13309             DisplayInformation(msg);
13310             pclose(commandOutput);
13311         }
13312     } else {
13313         if ((*cmailMsg) != '\0') {
13314             DisplayInformation(cmailMsg);
13315         }
13316     }
13317
13318     return;
13319 #endif /* !WIN32 */
13320 }
13321
13322 char *
13323 CmailMsg ()
13324 {
13325 #if WIN32
13326     return NULL;
13327 #else
13328     int  prependComma = 0;
13329     char number[5];
13330     char string[MSG_SIZ];       /* Space for game-list */
13331     int  i;
13332
13333     if (!cmailMsgLoaded) return "";
13334
13335     if (cmailMailedMove) {
13336       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13337     } else {
13338         /* Create a list of games left */
13339       snprintf(string, MSG_SIZ, "[");
13340         for (i = 0; i < nCmailGames; i ++) {
13341             if (! (   cmailMoveRegistered[i]
13342                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13343                 if (prependComma) {
13344                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13345                 } else {
13346                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13347                     prependComma = 1;
13348                 }
13349
13350                 strcat(string, number);
13351             }
13352         }
13353         strcat(string, "]");
13354
13355         if (nCmailMovesRegistered + nCmailResults == 0) {
13356             switch (nCmailGames) {
13357               case 1:
13358                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13359                 break;
13360
13361               case 2:
13362                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13363                 break;
13364
13365               default:
13366                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13367                          nCmailGames);
13368                 break;
13369             }
13370         } else {
13371             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13372               case 1:
13373                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13374                          string);
13375                 break;
13376
13377               case 0:
13378                 if (nCmailResults == nCmailGames) {
13379                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13380                 } else {
13381                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13382                 }
13383                 break;
13384
13385               default:
13386                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13387                          string);
13388             }
13389         }
13390     }
13391     return cmailMsg;
13392 #endif /* WIN32 */
13393 }
13394
13395 void
13396 ResetGameEvent ()
13397 {
13398     if (gameMode == Training)
13399       SetTrainingModeOff();
13400
13401     Reset(TRUE, TRUE);
13402     cmailMsgLoaded = FALSE;
13403     if (appData.icsActive) {
13404       SendToICS(ics_prefix);
13405       SendToICS("refresh\n");
13406     }
13407 }
13408
13409 void
13410 ExitEvent (int status)
13411 {
13412     exiting++;
13413     if (exiting > 2) {
13414       /* Give up on clean exit */
13415       exit(status);
13416     }
13417     if (exiting > 1) {
13418       /* Keep trying for clean exit */
13419       return;
13420     }
13421
13422     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13423
13424     if (telnetISR != NULL) {
13425       RemoveInputSource(telnetISR);
13426     }
13427     if (icsPR != NoProc) {
13428       DestroyChildProcess(icsPR, TRUE);
13429     }
13430
13431     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13432     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13433
13434     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13435     /* make sure this other one finishes before killing it!                  */
13436     if(endingGame) { int count = 0;
13437         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13438         while(endingGame && count++ < 10) DoSleep(1);
13439         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13440     }
13441
13442     /* Kill off chess programs */
13443     if (first.pr != NoProc) {
13444         ExitAnalyzeMode();
13445
13446         DoSleep( appData.delayBeforeQuit );
13447         SendToProgram("quit\n", &first);
13448         DoSleep( appData.delayAfterQuit );
13449         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13450     }
13451     if (second.pr != NoProc) {
13452         DoSleep( appData.delayBeforeQuit );
13453         SendToProgram("quit\n", &second);
13454         DoSleep( appData.delayAfterQuit );
13455         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13456     }
13457     if (first.isr != NULL) {
13458         RemoveInputSource(first.isr);
13459     }
13460     if (second.isr != NULL) {
13461         RemoveInputSource(second.isr);
13462     }
13463
13464     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13465     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13466
13467     ShutDownFrontEnd();
13468     exit(status);
13469 }
13470
13471 void
13472 PauseEngine (ChessProgramState *cps)
13473 {
13474     SendToProgram("pause\n", cps);
13475     cps->pause = 2;
13476 }
13477
13478 void
13479 UnPauseEngine (ChessProgramState *cps)
13480 {
13481     SendToProgram("resume\n", cps);
13482     cps->pause = 1;
13483 }
13484
13485 void
13486 PauseEvent ()
13487 {
13488     if (appData.debugMode)
13489         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13490     if (pausing) {
13491         pausing = FALSE;
13492         ModeHighlight();
13493         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13494             StartClocks();
13495             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13496                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13497                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13498             }
13499             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13500             HandleMachineMove(stashedInputMove, stalledEngine);
13501             stalledEngine = NULL;
13502             return;
13503         }
13504         if (gameMode == MachinePlaysWhite ||
13505             gameMode == TwoMachinesPlay   ||
13506             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13507             if(first.pause)  UnPauseEngine(&first);
13508             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13509             if(second.pause) UnPauseEngine(&second);
13510             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13511             StartClocks();
13512         } else {
13513             DisplayBothClocks();
13514         }
13515         if (gameMode == PlayFromGameFile) {
13516             if (appData.timeDelay >= 0)
13517                 AutoPlayGameLoop();
13518         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13519             Reset(FALSE, TRUE);
13520             SendToICS(ics_prefix);
13521             SendToICS("refresh\n");
13522         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13523             ForwardInner(forwardMostMove);
13524         }
13525         pauseExamInvalid = FALSE;
13526     } else {
13527         switch (gameMode) {
13528           default:
13529             return;
13530           case IcsExamining:
13531             pauseExamForwardMostMove = forwardMostMove;
13532             pauseExamInvalid = FALSE;
13533             /* fall through */
13534           case IcsObserving:
13535           case IcsPlayingWhite:
13536           case IcsPlayingBlack:
13537             pausing = TRUE;
13538             ModeHighlight();
13539             return;
13540           case PlayFromGameFile:
13541             (void) StopLoadGameTimer();
13542             pausing = TRUE;
13543             ModeHighlight();
13544             break;
13545           case BeginningOfGame:
13546             if (appData.icsActive) return;
13547             /* else fall through */
13548           case MachinePlaysWhite:
13549           case MachinePlaysBlack:
13550           case TwoMachinesPlay:
13551             if (forwardMostMove == 0)
13552               return;           /* don't pause if no one has moved */
13553             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13554                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13555                 if(onMove->pause) {           // thinking engine can be paused
13556                     PauseEngine(onMove);      // do it
13557                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13558                         PauseEngine(onMove->other);
13559                     else
13560                         SendToProgram("easy\n", onMove->other);
13561                     StopClocks();
13562                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13563             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13564                 if(first.pause) {
13565                     PauseEngine(&first);
13566                     StopClocks();
13567                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13568             } else { // human on move, pause pondering by either method
13569                 if(first.pause)
13570                     PauseEngine(&first);
13571                 else if(appData.ponderNextMove)
13572                     SendToProgram("easy\n", &first);
13573                 StopClocks();
13574             }
13575             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13576           case AnalyzeMode:
13577             pausing = TRUE;
13578             ModeHighlight();
13579             break;
13580         }
13581     }
13582 }
13583
13584 void
13585 EditCommentEvent ()
13586 {
13587     char title[MSG_SIZ];
13588
13589     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13590       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13591     } else {
13592       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13593                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13594                parseList[currentMove - 1]);
13595     }
13596
13597     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13598 }
13599
13600
13601 void
13602 EditTagsEvent ()
13603 {
13604     char *tags = PGNTags(&gameInfo);
13605     bookUp = FALSE;
13606     EditTagsPopUp(tags, NULL);
13607     free(tags);
13608 }
13609
13610 void
13611 ToggleSecond ()
13612 {
13613   if(second.analyzing) {
13614     SendToProgram("exit\n", &second);
13615     second.analyzing = FALSE;
13616   } else {
13617     if (second.pr == NoProc) StartChessProgram(&second);
13618     InitChessProgram(&second, FALSE);
13619     FeedMovesToProgram(&second, currentMove);
13620
13621     SendToProgram("analyze\n", &second);
13622     second.analyzing = TRUE;
13623   }
13624 }
13625
13626 /* Toggle ShowThinking */
13627 void
13628 ToggleShowThinking()
13629 {
13630   appData.showThinking = !appData.showThinking;
13631   ShowThinkingEvent();
13632 }
13633
13634 int
13635 AnalyzeModeEvent ()
13636 {
13637     char buf[MSG_SIZ];
13638
13639     if (!first.analysisSupport) {
13640       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13641       DisplayError(buf, 0);
13642       return 0;
13643     }
13644     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13645     if (appData.icsActive) {
13646         if (gameMode != IcsObserving) {
13647           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13648             DisplayError(buf, 0);
13649             /* secure check */
13650             if (appData.icsEngineAnalyze) {
13651                 if (appData.debugMode)
13652                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13653                 ExitAnalyzeMode();
13654                 ModeHighlight();
13655             }
13656             return 0;
13657         }
13658         /* if enable, user wants to disable icsEngineAnalyze */
13659         if (appData.icsEngineAnalyze) {
13660                 ExitAnalyzeMode();
13661                 ModeHighlight();
13662                 return 0;
13663         }
13664         appData.icsEngineAnalyze = TRUE;
13665         if (appData.debugMode)
13666             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13667     }
13668
13669     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13670     if (appData.noChessProgram || gameMode == AnalyzeMode)
13671       return 0;
13672
13673     if (gameMode != AnalyzeFile) {
13674         if (!appData.icsEngineAnalyze) {
13675                EditGameEvent();
13676                if (gameMode != EditGame) return 0;
13677         }
13678         if (!appData.showThinking) ToggleShowThinking();
13679         ResurrectChessProgram();
13680         SendToProgram("analyze\n", &first);
13681         first.analyzing = TRUE;
13682         /*first.maybeThinking = TRUE;*/
13683         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13684         EngineOutputPopUp();
13685     }
13686     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13687     pausing = FALSE;
13688     ModeHighlight();
13689     SetGameInfo();
13690
13691     StartAnalysisClock();
13692     GetTimeMark(&lastNodeCountTime);
13693     lastNodeCount = 0;
13694     return 1;
13695 }
13696
13697 void
13698 AnalyzeFileEvent ()
13699 {
13700     if (appData.noChessProgram || gameMode == AnalyzeFile)
13701       return;
13702
13703     if (!first.analysisSupport) {
13704       char buf[MSG_SIZ];
13705       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13706       DisplayError(buf, 0);
13707       return;
13708     }
13709
13710     if (gameMode != AnalyzeMode) {
13711         keepInfo = 1; // mere annotating should not alter PGN tags
13712         EditGameEvent();
13713         keepInfo = 0;
13714         if (gameMode != EditGame) return;
13715         if (!appData.showThinking) ToggleShowThinking();
13716         ResurrectChessProgram();
13717         SendToProgram("analyze\n", &first);
13718         first.analyzing = TRUE;
13719         /*first.maybeThinking = TRUE;*/
13720         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13721         EngineOutputPopUp();
13722     }
13723     gameMode = AnalyzeFile;
13724     pausing = FALSE;
13725     ModeHighlight();
13726
13727     StartAnalysisClock();
13728     GetTimeMark(&lastNodeCountTime);
13729     lastNodeCount = 0;
13730     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13731     AnalysisPeriodicEvent(1);
13732 }
13733
13734 void
13735 MachineWhiteEvent ()
13736 {
13737     char buf[MSG_SIZ];
13738     char *bookHit = NULL;
13739
13740     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13741       return;
13742
13743
13744     if (gameMode == PlayFromGameFile ||
13745         gameMode == TwoMachinesPlay  ||
13746         gameMode == Training         ||
13747         gameMode == AnalyzeMode      ||
13748         gameMode == EndOfGame)
13749         EditGameEvent();
13750
13751     if (gameMode == EditPosition)
13752         EditPositionDone(TRUE);
13753
13754     if (!WhiteOnMove(currentMove)) {
13755         DisplayError(_("It is not White's turn"), 0);
13756         return;
13757     }
13758
13759     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13760       ExitAnalyzeMode();
13761
13762     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13763         gameMode == AnalyzeFile)
13764         TruncateGame();
13765
13766     ResurrectChessProgram();    /* in case it isn't running */
13767     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13768         gameMode = MachinePlaysWhite;
13769         ResetClocks();
13770     } else
13771     gameMode = MachinePlaysWhite;
13772     pausing = FALSE;
13773     ModeHighlight();
13774     SetGameInfo();
13775     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13776     DisplayTitle(buf);
13777     if (first.sendName) {
13778       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13779       SendToProgram(buf, &first);
13780     }
13781     if (first.sendTime) {
13782       if (first.useColors) {
13783         SendToProgram("black\n", &first); /*gnu kludge*/
13784       }
13785       SendTimeRemaining(&first, TRUE);
13786     }
13787     if (first.useColors) {
13788       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13789     }
13790     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13791     SetMachineThinkingEnables();
13792     first.maybeThinking = TRUE;
13793     StartClocks();
13794     firstMove = FALSE;
13795
13796     if (appData.autoFlipView && !flipView) {
13797       flipView = !flipView;
13798       DrawPosition(FALSE, NULL);
13799       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13800     }
13801
13802     if(bookHit) { // [HGM] book: simulate book reply
13803         static char bookMove[MSG_SIZ]; // a bit generous?
13804
13805         programStats.nodes = programStats.depth = programStats.time =
13806         programStats.score = programStats.got_only_move = 0;
13807         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13808
13809         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13810         strcat(bookMove, bookHit);
13811         HandleMachineMove(bookMove, &first);
13812     }
13813 }
13814
13815 void
13816 MachineBlackEvent ()
13817 {
13818   char buf[MSG_SIZ];
13819   char *bookHit = NULL;
13820
13821     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13822         return;
13823
13824
13825     if (gameMode == PlayFromGameFile ||
13826         gameMode == TwoMachinesPlay  ||
13827         gameMode == Training         ||
13828         gameMode == AnalyzeMode      ||
13829         gameMode == EndOfGame)
13830         EditGameEvent();
13831
13832     if (gameMode == EditPosition)
13833         EditPositionDone(TRUE);
13834
13835     if (WhiteOnMove(currentMove)) {
13836         DisplayError(_("It is not Black's turn"), 0);
13837         return;
13838     }
13839
13840     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13841       ExitAnalyzeMode();
13842
13843     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13844         gameMode == AnalyzeFile)
13845         TruncateGame();
13846
13847     ResurrectChessProgram();    /* in case it isn't running */
13848     gameMode = MachinePlaysBlack;
13849     pausing = FALSE;
13850     ModeHighlight();
13851     SetGameInfo();
13852     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13853     DisplayTitle(buf);
13854     if (first.sendName) {
13855       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13856       SendToProgram(buf, &first);
13857     }
13858     if (first.sendTime) {
13859       if (first.useColors) {
13860         SendToProgram("white\n", &first); /*gnu kludge*/
13861       }
13862       SendTimeRemaining(&first, FALSE);
13863     }
13864     if (first.useColors) {
13865       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13866     }
13867     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13868     SetMachineThinkingEnables();
13869     first.maybeThinking = TRUE;
13870     StartClocks();
13871
13872     if (appData.autoFlipView && flipView) {
13873       flipView = !flipView;
13874       DrawPosition(FALSE, NULL);
13875       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13876     }
13877     if(bookHit) { // [HGM] book: simulate book reply
13878         static char bookMove[MSG_SIZ]; // a bit generous?
13879
13880         programStats.nodes = programStats.depth = programStats.time =
13881         programStats.score = programStats.got_only_move = 0;
13882         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13883
13884         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13885         strcat(bookMove, bookHit);
13886         HandleMachineMove(bookMove, &first);
13887     }
13888 }
13889
13890
13891 void
13892 DisplayTwoMachinesTitle ()
13893 {
13894     char buf[MSG_SIZ];
13895     if (appData.matchGames > 0) {
13896         if(appData.tourneyFile[0]) {
13897           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13898                    gameInfo.white, _("vs."), gameInfo.black,
13899                    nextGame+1, appData.matchGames+1,
13900                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13901         } else
13902         if (first.twoMachinesColor[0] == 'w') {
13903           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13904                    gameInfo.white, _("vs."),  gameInfo.black,
13905                    first.matchWins, second.matchWins,
13906                    matchGame - 1 - (first.matchWins + second.matchWins));
13907         } else {
13908           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13909                    gameInfo.white, _("vs."), gameInfo.black,
13910                    second.matchWins, first.matchWins,
13911                    matchGame - 1 - (first.matchWins + second.matchWins));
13912         }
13913     } else {
13914       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13915     }
13916     DisplayTitle(buf);
13917 }
13918
13919 void
13920 SettingsMenuIfReady ()
13921 {
13922   if (second.lastPing != second.lastPong) {
13923     DisplayMessage("", _("Waiting for second chess program"));
13924     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13925     return;
13926   }
13927   ThawUI();
13928   DisplayMessage("", "");
13929   SettingsPopUp(&second);
13930 }
13931
13932 int
13933 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13934 {
13935     char buf[MSG_SIZ];
13936     if (cps->pr == NoProc) {
13937         StartChessProgram(cps);
13938         if (cps->protocolVersion == 1) {
13939           retry();
13940           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13941         } else {
13942           /* kludge: allow timeout for initial "feature" command */
13943           if(retry != TwoMachinesEventIfReady) FreezeUI();
13944           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13945           DisplayMessage("", buf);
13946           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13947         }
13948         return 1;
13949     }
13950     return 0;
13951 }
13952
13953 void
13954 TwoMachinesEvent P((void))
13955 {
13956     int i;
13957     char buf[MSG_SIZ];
13958     ChessProgramState *onmove;
13959     char *bookHit = NULL;
13960     static int stalling = 0;
13961     TimeMark now;
13962     long wait;
13963
13964     if (appData.noChessProgram) return;
13965
13966     switch (gameMode) {
13967       case TwoMachinesPlay:
13968         return;
13969       case MachinePlaysWhite:
13970       case MachinePlaysBlack:
13971         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13972             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13973             return;
13974         }
13975         /* fall through */
13976       case BeginningOfGame:
13977       case PlayFromGameFile:
13978       case EndOfGame:
13979         EditGameEvent();
13980         if (gameMode != EditGame) return;
13981         break;
13982       case EditPosition:
13983         EditPositionDone(TRUE);
13984         break;
13985       case AnalyzeMode:
13986       case AnalyzeFile:
13987         ExitAnalyzeMode();
13988         break;
13989       case EditGame:
13990       default:
13991         break;
13992     }
13993
13994 //    forwardMostMove = currentMove;
13995     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13996     startingEngine = TRUE;
13997
13998     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13999
14000     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14001     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14002       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14003       return;
14004     }
14005     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14006
14007     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14008         startingEngine = FALSE;
14009         DisplayError("second engine does not play this", 0);
14010         return;
14011     }
14012
14013     if(!stalling) {
14014       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14015       SendToProgram("force\n", &second);
14016       stalling = 1;
14017       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14018       return;
14019     }
14020     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14021     if(appData.matchPause>10000 || appData.matchPause<10)
14022                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14023     wait = SubtractTimeMarks(&now, &pauseStart);
14024     if(wait < appData.matchPause) {
14025         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14026         return;
14027     }
14028     // we are now committed to starting the game
14029     stalling = 0;
14030     DisplayMessage("", "");
14031     if (startedFromSetupPosition) {
14032         SendBoard(&second, backwardMostMove);
14033     if (appData.debugMode) {
14034         fprintf(debugFP, "Two Machines\n");
14035     }
14036     }
14037     for (i = backwardMostMove; i < forwardMostMove; i++) {
14038         SendMoveToProgram(i, &second);
14039     }
14040
14041     gameMode = TwoMachinesPlay;
14042     pausing = startingEngine = FALSE;
14043     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14044     SetGameInfo();
14045     DisplayTwoMachinesTitle();
14046     firstMove = TRUE;
14047     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14048         onmove = &first;
14049     } else {
14050         onmove = &second;
14051     }
14052     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14053     SendToProgram(first.computerString, &first);
14054     if (first.sendName) {
14055       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14056       SendToProgram(buf, &first);
14057     }
14058     SendToProgram(second.computerString, &second);
14059     if (second.sendName) {
14060       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14061       SendToProgram(buf, &second);
14062     }
14063
14064     ResetClocks();
14065     if (!first.sendTime || !second.sendTime) {
14066         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14067         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14068     }
14069     if (onmove->sendTime) {
14070       if (onmove->useColors) {
14071         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14072       }
14073       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14074     }
14075     if (onmove->useColors) {
14076       SendToProgram(onmove->twoMachinesColor, onmove);
14077     }
14078     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14079 //    SendToProgram("go\n", onmove);
14080     onmove->maybeThinking = TRUE;
14081     SetMachineThinkingEnables();
14082
14083     StartClocks();
14084
14085     if(bookHit) { // [HGM] book: simulate book reply
14086         static char bookMove[MSG_SIZ]; // a bit generous?
14087
14088         programStats.nodes = programStats.depth = programStats.time =
14089         programStats.score = programStats.got_only_move = 0;
14090         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14091
14092         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14093         strcat(bookMove, bookHit);
14094         savedMessage = bookMove; // args for deferred call
14095         savedState = onmove;
14096         ScheduleDelayedEvent(DeferredBookMove, 1);
14097     }
14098 }
14099
14100 void
14101 TrainingEvent ()
14102 {
14103     if (gameMode == Training) {
14104       SetTrainingModeOff();
14105       gameMode = PlayFromGameFile;
14106       DisplayMessage("", _("Training mode off"));
14107     } else {
14108       gameMode = Training;
14109       animateTraining = appData.animate;
14110
14111       /* make sure we are not already at the end of the game */
14112       if (currentMove < forwardMostMove) {
14113         SetTrainingModeOn();
14114         DisplayMessage("", _("Training mode on"));
14115       } else {
14116         gameMode = PlayFromGameFile;
14117         DisplayError(_("Already at end of game"), 0);
14118       }
14119     }
14120     ModeHighlight();
14121 }
14122
14123 void
14124 IcsClientEvent ()
14125 {
14126     if (!appData.icsActive) return;
14127     switch (gameMode) {
14128       case IcsPlayingWhite:
14129       case IcsPlayingBlack:
14130       case IcsObserving:
14131       case IcsIdle:
14132       case BeginningOfGame:
14133       case IcsExamining:
14134         return;
14135
14136       case EditGame:
14137         break;
14138
14139       case EditPosition:
14140         EditPositionDone(TRUE);
14141         break;
14142
14143       case AnalyzeMode:
14144       case AnalyzeFile:
14145         ExitAnalyzeMode();
14146         break;
14147
14148       default:
14149         EditGameEvent();
14150         break;
14151     }
14152
14153     gameMode = IcsIdle;
14154     ModeHighlight();
14155     return;
14156 }
14157
14158 void
14159 EditGameEvent ()
14160 {
14161     int i;
14162
14163     switch (gameMode) {
14164       case Training:
14165         SetTrainingModeOff();
14166         break;
14167       case MachinePlaysWhite:
14168       case MachinePlaysBlack:
14169       case BeginningOfGame:
14170         SendToProgram("force\n", &first);
14171         SetUserThinkingEnables();
14172         break;
14173       case PlayFromGameFile:
14174         (void) StopLoadGameTimer();
14175         if (gameFileFP != NULL) {
14176             gameFileFP = NULL;
14177         }
14178         break;
14179       case EditPosition:
14180         EditPositionDone(TRUE);
14181         break;
14182       case AnalyzeMode:
14183       case AnalyzeFile:
14184         ExitAnalyzeMode();
14185         SendToProgram("force\n", &first);
14186         break;
14187       case TwoMachinesPlay:
14188         GameEnds(EndOfFile, NULL, GE_PLAYER);
14189         ResurrectChessProgram();
14190         SetUserThinkingEnables();
14191         break;
14192       case EndOfGame:
14193         ResurrectChessProgram();
14194         break;
14195       case IcsPlayingBlack:
14196       case IcsPlayingWhite:
14197         DisplayError(_("Warning: You are still playing a game"), 0);
14198         break;
14199       case IcsObserving:
14200         DisplayError(_("Warning: You are still observing a game"), 0);
14201         break;
14202       case IcsExamining:
14203         DisplayError(_("Warning: You are still examining a game"), 0);
14204         break;
14205       case IcsIdle:
14206         break;
14207       case EditGame:
14208       default:
14209         return;
14210     }
14211
14212     pausing = FALSE;
14213     StopClocks();
14214     first.offeredDraw = second.offeredDraw = 0;
14215
14216     if (gameMode == PlayFromGameFile) {
14217         whiteTimeRemaining = timeRemaining[0][currentMove];
14218         blackTimeRemaining = timeRemaining[1][currentMove];
14219         DisplayTitle("");
14220     }
14221
14222     if (gameMode == MachinePlaysWhite ||
14223         gameMode == MachinePlaysBlack ||
14224         gameMode == TwoMachinesPlay ||
14225         gameMode == EndOfGame) {
14226         i = forwardMostMove;
14227         while (i > currentMove) {
14228             SendToProgram("undo\n", &first);
14229             i--;
14230         }
14231         if(!adjustedClock) {
14232         whiteTimeRemaining = timeRemaining[0][currentMove];
14233         blackTimeRemaining = timeRemaining[1][currentMove];
14234         DisplayBothClocks();
14235         }
14236         if (whiteFlag || blackFlag) {
14237             whiteFlag = blackFlag = 0;
14238         }
14239         DisplayTitle("");
14240     }
14241
14242     gameMode = EditGame;
14243     ModeHighlight();
14244     SetGameInfo();
14245 }
14246
14247
14248 void
14249 EditPositionEvent ()
14250 {
14251     if (gameMode == EditPosition) {
14252         EditGameEvent();
14253         return;
14254     }
14255
14256     EditGameEvent();
14257     if (gameMode != EditGame) return;
14258
14259     gameMode = EditPosition;
14260     ModeHighlight();
14261     SetGameInfo();
14262     if (currentMove > 0)
14263       CopyBoard(boards[0], boards[currentMove]);
14264
14265     blackPlaysFirst = !WhiteOnMove(currentMove);
14266     ResetClocks();
14267     currentMove = forwardMostMove = backwardMostMove = 0;
14268     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14269     DisplayMove(-1);
14270     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14271 }
14272
14273 void
14274 ExitAnalyzeMode ()
14275 {
14276     /* [DM] icsEngineAnalyze - possible call from other functions */
14277     if (appData.icsEngineAnalyze) {
14278         appData.icsEngineAnalyze = FALSE;
14279
14280         DisplayMessage("",_("Close ICS engine analyze..."));
14281     }
14282     if (first.analysisSupport && first.analyzing) {
14283       SendToBoth("exit\n");
14284       first.analyzing = second.analyzing = FALSE;
14285     }
14286     thinkOutput[0] = NULLCHAR;
14287 }
14288
14289 void
14290 EditPositionDone (Boolean fakeRights)
14291 {
14292     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14293
14294     startedFromSetupPosition = TRUE;
14295     InitChessProgram(&first, FALSE);
14296     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14297       boards[0][EP_STATUS] = EP_NONE;
14298       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14299       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14300         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14301         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14302       } else boards[0][CASTLING][2] = NoRights;
14303       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14304         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14305         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14306       } else boards[0][CASTLING][5] = NoRights;
14307       if(gameInfo.variant == VariantSChess) {
14308         int i;
14309         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14310           boards[0][VIRGIN][i] = 0;
14311           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14312           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14313         }
14314       }
14315     }
14316     SendToProgram("force\n", &first);
14317     if (blackPlaysFirst) {
14318         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14319         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14320         currentMove = forwardMostMove = backwardMostMove = 1;
14321         CopyBoard(boards[1], boards[0]);
14322     } else {
14323         currentMove = forwardMostMove = backwardMostMove = 0;
14324     }
14325     SendBoard(&first, forwardMostMove);
14326     if (appData.debugMode) {
14327         fprintf(debugFP, "EditPosDone\n");
14328     }
14329     DisplayTitle("");
14330     DisplayMessage("", "");
14331     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14332     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14333     gameMode = EditGame;
14334     ModeHighlight();
14335     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14336     ClearHighlights(); /* [AS] */
14337 }
14338
14339 /* Pause for `ms' milliseconds */
14340 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14341 void
14342 TimeDelay (long ms)
14343 {
14344     TimeMark m1, m2;
14345
14346     GetTimeMark(&m1);
14347     do {
14348         GetTimeMark(&m2);
14349     } while (SubtractTimeMarks(&m2, &m1) < ms);
14350 }
14351
14352 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14353 void
14354 SendMultiLineToICS (char *buf)
14355 {
14356     char temp[MSG_SIZ+1], *p;
14357     int len;
14358
14359     len = strlen(buf);
14360     if (len > MSG_SIZ)
14361       len = MSG_SIZ;
14362
14363     strncpy(temp, buf, len);
14364     temp[len] = 0;
14365
14366     p = temp;
14367     while (*p) {
14368         if (*p == '\n' || *p == '\r')
14369           *p = ' ';
14370         ++p;
14371     }
14372
14373     strcat(temp, "\n");
14374     SendToICS(temp);
14375     SendToPlayer(temp, strlen(temp));
14376 }
14377
14378 void
14379 SetWhiteToPlayEvent ()
14380 {
14381     if (gameMode == EditPosition) {
14382         blackPlaysFirst = FALSE;
14383         DisplayBothClocks();    /* works because currentMove is 0 */
14384     } else if (gameMode == IcsExamining) {
14385         SendToICS(ics_prefix);
14386         SendToICS("tomove white\n");
14387     }
14388 }
14389
14390 void
14391 SetBlackToPlayEvent ()
14392 {
14393     if (gameMode == EditPosition) {
14394         blackPlaysFirst = TRUE;
14395         currentMove = 1;        /* kludge */
14396         DisplayBothClocks();
14397         currentMove = 0;
14398     } else if (gameMode == IcsExamining) {
14399         SendToICS(ics_prefix);
14400         SendToICS("tomove black\n");
14401     }
14402 }
14403
14404 void
14405 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14406 {
14407     char buf[MSG_SIZ];
14408     ChessSquare piece = boards[0][y][x];
14409
14410     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14411
14412     switch (selection) {
14413       case ClearBoard:
14414         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14415             SendToICS(ics_prefix);
14416             SendToICS("bsetup clear\n");
14417         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14418             SendToICS(ics_prefix);
14419             SendToICS("clearboard\n");
14420         } else {
14421             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14422                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14423                 for (y = 0; y < BOARD_HEIGHT; y++) {
14424                     if (gameMode == IcsExamining) {
14425                         if (boards[currentMove][y][x] != EmptySquare) {
14426                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14427                                     AAA + x, ONE + y);
14428                             SendToICS(buf);
14429                         }
14430                     } else {
14431                         boards[0][y][x] = p;
14432                     }
14433                 }
14434             }
14435         }
14436         if (gameMode == EditPosition) {
14437             DrawPosition(FALSE, boards[0]);
14438         }
14439         break;
14440
14441       case WhitePlay:
14442         SetWhiteToPlayEvent();
14443         break;
14444
14445       case BlackPlay:
14446         SetBlackToPlayEvent();
14447         break;
14448
14449       case EmptySquare:
14450         if (gameMode == IcsExamining) {
14451             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14452             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14453             SendToICS(buf);
14454         } else {
14455             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14456                 if(x == BOARD_LEFT-2) {
14457                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14458                     boards[0][y][1] = 0;
14459                 } else
14460                 if(x == BOARD_RGHT+1) {
14461                     if(y >= gameInfo.holdingsSize) break;
14462                     boards[0][y][BOARD_WIDTH-2] = 0;
14463                 } else break;
14464             }
14465             boards[0][y][x] = EmptySquare;
14466             DrawPosition(FALSE, boards[0]);
14467         }
14468         break;
14469
14470       case PromotePiece:
14471         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14472            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14473             selection = (ChessSquare) (PROMOTED piece);
14474         } else if(piece == EmptySquare) selection = WhiteSilver;
14475         else selection = (ChessSquare)((int)piece - 1);
14476         goto defaultlabel;
14477
14478       case DemotePiece:
14479         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14480            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14481             selection = (ChessSquare) (DEMOTED piece);
14482         } else if(piece == EmptySquare) selection = BlackSilver;
14483         else selection = (ChessSquare)((int)piece + 1);
14484         goto defaultlabel;
14485
14486       case WhiteQueen:
14487       case BlackQueen:
14488         if(gameInfo.variant == VariantShatranj ||
14489            gameInfo.variant == VariantXiangqi  ||
14490            gameInfo.variant == VariantCourier  ||
14491            gameInfo.variant == VariantMakruk     )
14492             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14493         goto defaultlabel;
14494
14495       case WhiteKing:
14496       case BlackKing:
14497         if(gameInfo.variant == VariantXiangqi)
14498             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14499         if(gameInfo.variant == VariantKnightmate)
14500             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14501       default:
14502         defaultlabel:
14503         if (gameMode == IcsExamining) {
14504             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14505             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14506                      PieceToChar(selection), AAA + x, ONE + y);
14507             SendToICS(buf);
14508         } else {
14509             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14510                 int n;
14511                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14512                     n = PieceToNumber(selection - BlackPawn);
14513                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14514                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14515                     boards[0][BOARD_HEIGHT-1-n][1]++;
14516                 } else
14517                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14518                     n = PieceToNumber(selection);
14519                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14520                     boards[0][n][BOARD_WIDTH-1] = selection;
14521                     boards[0][n][BOARD_WIDTH-2]++;
14522                 }
14523             } else
14524             boards[0][y][x] = selection;
14525             DrawPosition(TRUE, boards[0]);
14526             ClearHighlights();
14527             fromX = fromY = -1;
14528         }
14529         break;
14530     }
14531 }
14532
14533
14534 void
14535 DropMenuEvent (ChessSquare selection, int x, int y)
14536 {
14537     ChessMove moveType;
14538
14539     switch (gameMode) {
14540       case IcsPlayingWhite:
14541       case MachinePlaysBlack:
14542         if (!WhiteOnMove(currentMove)) {
14543             DisplayMoveError(_("It is Black's turn"));
14544             return;
14545         }
14546         moveType = WhiteDrop;
14547         break;
14548       case IcsPlayingBlack:
14549       case MachinePlaysWhite:
14550         if (WhiteOnMove(currentMove)) {
14551             DisplayMoveError(_("It is White's turn"));
14552             return;
14553         }
14554         moveType = BlackDrop;
14555         break;
14556       case EditGame:
14557         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14558         break;
14559       default:
14560         return;
14561     }
14562
14563     if (moveType == BlackDrop && selection < BlackPawn) {
14564       selection = (ChessSquare) ((int) selection
14565                                  + (int) BlackPawn - (int) WhitePawn);
14566     }
14567     if (boards[currentMove][y][x] != EmptySquare) {
14568         DisplayMoveError(_("That square is occupied"));
14569         return;
14570     }
14571
14572     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14573 }
14574
14575 void
14576 AcceptEvent ()
14577 {
14578     /* Accept a pending offer of any kind from opponent */
14579
14580     if (appData.icsActive) {
14581         SendToICS(ics_prefix);
14582         SendToICS("accept\n");
14583     } else if (cmailMsgLoaded) {
14584         if (currentMove == cmailOldMove &&
14585             commentList[cmailOldMove] != NULL &&
14586             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14587                    "Black offers a draw" : "White offers a draw")) {
14588             TruncateGame();
14589             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14590             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14591         } else {
14592             DisplayError(_("There is no pending offer on this move"), 0);
14593             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14594         }
14595     } else {
14596         /* Not used for offers from chess program */
14597     }
14598 }
14599
14600 void
14601 DeclineEvent ()
14602 {
14603     /* Decline a pending offer of any kind from opponent */
14604
14605     if (appData.icsActive) {
14606         SendToICS(ics_prefix);
14607         SendToICS("decline\n");
14608     } else if (cmailMsgLoaded) {
14609         if (currentMove == cmailOldMove &&
14610             commentList[cmailOldMove] != NULL &&
14611             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14612                    "Black offers a draw" : "White offers a draw")) {
14613 #ifdef NOTDEF
14614             AppendComment(cmailOldMove, "Draw declined", TRUE);
14615             DisplayComment(cmailOldMove - 1, "Draw declined");
14616 #endif /*NOTDEF*/
14617         } else {
14618             DisplayError(_("There is no pending offer on this move"), 0);
14619         }
14620     } else {
14621         /* Not used for offers from chess program */
14622     }
14623 }
14624
14625 void
14626 RematchEvent ()
14627 {
14628     /* Issue ICS rematch command */
14629     if (appData.icsActive) {
14630         SendToICS(ics_prefix);
14631         SendToICS("rematch\n");
14632     }
14633 }
14634
14635 void
14636 CallFlagEvent ()
14637 {
14638     /* Call your opponent's flag (claim a win on time) */
14639     if (appData.icsActive) {
14640         SendToICS(ics_prefix);
14641         SendToICS("flag\n");
14642     } else {
14643         switch (gameMode) {
14644           default:
14645             return;
14646           case MachinePlaysWhite:
14647             if (whiteFlag) {
14648                 if (blackFlag)
14649                   GameEnds(GameIsDrawn, "Both players ran out of time",
14650                            GE_PLAYER);
14651                 else
14652                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14653             } else {
14654                 DisplayError(_("Your opponent is not out of time"), 0);
14655             }
14656             break;
14657           case MachinePlaysBlack:
14658             if (blackFlag) {
14659                 if (whiteFlag)
14660                   GameEnds(GameIsDrawn, "Both players ran out of time",
14661                            GE_PLAYER);
14662                 else
14663                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14664             } else {
14665                 DisplayError(_("Your opponent is not out of time"), 0);
14666             }
14667             break;
14668         }
14669     }
14670 }
14671
14672 void
14673 ClockClick (int which)
14674 {       // [HGM] code moved to back-end from winboard.c
14675         if(which) { // black clock
14676           if (gameMode == EditPosition || gameMode == IcsExamining) {
14677             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14678             SetBlackToPlayEvent();
14679           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14680           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14681           } else if (shiftKey) {
14682             AdjustClock(which, -1);
14683           } else if (gameMode == IcsPlayingWhite ||
14684                      gameMode == MachinePlaysBlack) {
14685             CallFlagEvent();
14686           }
14687         } else { // white clock
14688           if (gameMode == EditPosition || gameMode == IcsExamining) {
14689             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14690             SetWhiteToPlayEvent();
14691           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14692           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14693           } else if (shiftKey) {
14694             AdjustClock(which, -1);
14695           } else if (gameMode == IcsPlayingBlack ||
14696                    gameMode == MachinePlaysWhite) {
14697             CallFlagEvent();
14698           }
14699         }
14700 }
14701
14702 void
14703 DrawEvent ()
14704 {
14705     /* Offer draw or accept pending draw offer from opponent */
14706
14707     if (appData.icsActive) {
14708         /* Note: tournament rules require draw offers to be
14709            made after you make your move but before you punch
14710            your clock.  Currently ICS doesn't let you do that;
14711            instead, you immediately punch your clock after making
14712            a move, but you can offer a draw at any time. */
14713
14714         SendToICS(ics_prefix);
14715         SendToICS("draw\n");
14716         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14717     } else if (cmailMsgLoaded) {
14718         if (currentMove == cmailOldMove &&
14719             commentList[cmailOldMove] != NULL &&
14720             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14721                    "Black offers a draw" : "White offers a draw")) {
14722             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14723             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14724         } else if (currentMove == cmailOldMove + 1) {
14725             char *offer = WhiteOnMove(cmailOldMove) ?
14726               "White offers a draw" : "Black offers a draw";
14727             AppendComment(currentMove, offer, TRUE);
14728             DisplayComment(currentMove - 1, offer);
14729             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14730         } else {
14731             DisplayError(_("You must make your move before offering a draw"), 0);
14732             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14733         }
14734     } else if (first.offeredDraw) {
14735         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14736     } else {
14737         if (first.sendDrawOffers) {
14738             SendToProgram("draw\n", &first);
14739             userOfferedDraw = TRUE;
14740         }
14741     }
14742 }
14743
14744 void
14745 AdjournEvent ()
14746 {
14747     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14748
14749     if (appData.icsActive) {
14750         SendToICS(ics_prefix);
14751         SendToICS("adjourn\n");
14752     } else {
14753         /* Currently GNU Chess doesn't offer or accept Adjourns */
14754     }
14755 }
14756
14757
14758 void
14759 AbortEvent ()
14760 {
14761     /* Offer Abort or accept pending Abort offer from opponent */
14762
14763     if (appData.icsActive) {
14764         SendToICS(ics_prefix);
14765         SendToICS("abort\n");
14766     } else {
14767         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14768     }
14769 }
14770
14771 void
14772 ResignEvent ()
14773 {
14774     /* Resign.  You can do this even if it's not your turn. */
14775
14776     if (appData.icsActive) {
14777         SendToICS(ics_prefix);
14778         SendToICS("resign\n");
14779     } else {
14780         switch (gameMode) {
14781           case MachinePlaysWhite:
14782             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14783             break;
14784           case MachinePlaysBlack:
14785             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14786             break;
14787           case EditGame:
14788             if (cmailMsgLoaded) {
14789                 TruncateGame();
14790                 if (WhiteOnMove(cmailOldMove)) {
14791                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14792                 } else {
14793                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14794                 }
14795                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14796             }
14797             break;
14798           default:
14799             break;
14800         }
14801     }
14802 }
14803
14804
14805 void
14806 StopObservingEvent ()
14807 {
14808     /* Stop observing current games */
14809     SendToICS(ics_prefix);
14810     SendToICS("unobserve\n");
14811 }
14812
14813 void
14814 StopExaminingEvent ()
14815 {
14816     /* Stop observing current game */
14817     SendToICS(ics_prefix);
14818     SendToICS("unexamine\n");
14819 }
14820
14821 void
14822 ForwardInner (int target)
14823 {
14824     int limit; int oldSeekGraphUp = seekGraphUp;
14825
14826     if (appData.debugMode)
14827         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14828                 target, currentMove, forwardMostMove);
14829
14830     if (gameMode == EditPosition)
14831       return;
14832
14833     seekGraphUp = FALSE;
14834     MarkTargetSquares(1);
14835
14836     if (gameMode == PlayFromGameFile && !pausing)
14837       PauseEvent();
14838
14839     if (gameMode == IcsExamining && pausing)
14840       limit = pauseExamForwardMostMove;
14841     else
14842       limit = forwardMostMove;
14843
14844     if (target > limit) target = limit;
14845
14846     if (target > 0 && moveList[target - 1][0]) {
14847         int fromX, fromY, toX, toY;
14848         toX = moveList[target - 1][2] - AAA;
14849         toY = moveList[target - 1][3] - ONE;
14850         if (moveList[target - 1][1] == '@') {
14851             if (appData.highlightLastMove) {
14852                 SetHighlights(-1, -1, toX, toY);
14853             }
14854         } else {
14855             fromX = moveList[target - 1][0] - AAA;
14856             fromY = moveList[target - 1][1] - ONE;
14857             if (target == currentMove + 1) {
14858                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14859             }
14860             if (appData.highlightLastMove) {
14861                 SetHighlights(fromX, fromY, toX, toY);
14862             }
14863         }
14864     }
14865     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14866         gameMode == Training || gameMode == PlayFromGameFile ||
14867         gameMode == AnalyzeFile) {
14868         while (currentMove < target) {
14869             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14870             SendMoveToProgram(currentMove++, &first);
14871         }
14872     } else {
14873         currentMove = target;
14874     }
14875
14876     if (gameMode == EditGame || gameMode == EndOfGame) {
14877         whiteTimeRemaining = timeRemaining[0][currentMove];
14878         blackTimeRemaining = timeRemaining[1][currentMove];
14879     }
14880     DisplayBothClocks();
14881     DisplayMove(currentMove - 1);
14882     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14883     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14884     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14885         DisplayComment(currentMove - 1, commentList[currentMove]);
14886     }
14887     ClearMap(); // [HGM] exclude: invalidate map
14888 }
14889
14890
14891 void
14892 ForwardEvent ()
14893 {
14894     if (gameMode == IcsExamining && !pausing) {
14895         SendToICS(ics_prefix);
14896         SendToICS("forward\n");
14897     } else {
14898         ForwardInner(currentMove + 1);
14899     }
14900 }
14901
14902 void
14903 ToEndEvent ()
14904 {
14905     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14906         /* to optimze, we temporarily turn off analysis mode while we feed
14907          * the remaining moves to the engine. Otherwise we get analysis output
14908          * after each move.
14909          */
14910         if (first.analysisSupport) {
14911           SendToProgram("exit\nforce\n", &first);
14912           first.analyzing = FALSE;
14913         }
14914     }
14915
14916     if (gameMode == IcsExamining && !pausing) {
14917         SendToICS(ics_prefix);
14918         SendToICS("forward 999999\n");
14919     } else {
14920         ForwardInner(forwardMostMove);
14921     }
14922
14923     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14924         /* we have fed all the moves, so reactivate analysis mode */
14925         SendToProgram("analyze\n", &first);
14926         first.analyzing = TRUE;
14927         /*first.maybeThinking = TRUE;*/
14928         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14929     }
14930 }
14931
14932 void
14933 BackwardInner (int target)
14934 {
14935     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14936
14937     if (appData.debugMode)
14938         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14939                 target, currentMove, forwardMostMove);
14940
14941     if (gameMode == EditPosition) return;
14942     seekGraphUp = FALSE;
14943     MarkTargetSquares(1);
14944     if (currentMove <= backwardMostMove) {
14945         ClearHighlights();
14946         DrawPosition(full_redraw, boards[currentMove]);
14947         return;
14948     }
14949     if (gameMode == PlayFromGameFile && !pausing)
14950       PauseEvent();
14951
14952     if (moveList[target][0]) {
14953         int fromX, fromY, toX, toY;
14954         toX = moveList[target][2] - AAA;
14955         toY = moveList[target][3] - ONE;
14956         if (moveList[target][1] == '@') {
14957             if (appData.highlightLastMove) {
14958                 SetHighlights(-1, -1, toX, toY);
14959             }
14960         } else {
14961             fromX = moveList[target][0] - AAA;
14962             fromY = moveList[target][1] - ONE;
14963             if (target == currentMove - 1) {
14964                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14965             }
14966             if (appData.highlightLastMove) {
14967                 SetHighlights(fromX, fromY, toX, toY);
14968             }
14969         }
14970     }
14971     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14972         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14973         while (currentMove > target) {
14974             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14975                 // null move cannot be undone. Reload program with move history before it.
14976                 int i;
14977                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14978                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14979                 }
14980                 SendBoard(&first, i);
14981               if(second.analyzing) SendBoard(&second, i);
14982                 for(currentMove=i; currentMove<target; currentMove++) {
14983                     SendMoveToProgram(currentMove, &first);
14984                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14985                 }
14986                 break;
14987             }
14988             SendToBoth("undo\n");
14989             currentMove--;
14990         }
14991     } else {
14992         currentMove = target;
14993     }
14994
14995     if (gameMode == EditGame || gameMode == EndOfGame) {
14996         whiteTimeRemaining = timeRemaining[0][currentMove];
14997         blackTimeRemaining = timeRemaining[1][currentMove];
14998     }
14999     DisplayBothClocks();
15000     DisplayMove(currentMove - 1);
15001     DrawPosition(full_redraw, boards[currentMove]);
15002     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15003     // [HGM] PV info: routine tests if comment empty
15004     DisplayComment(currentMove - 1, commentList[currentMove]);
15005     ClearMap(); // [HGM] exclude: invalidate map
15006 }
15007
15008 void
15009 BackwardEvent ()
15010 {
15011     if (gameMode == IcsExamining && !pausing) {
15012         SendToICS(ics_prefix);
15013         SendToICS("backward\n");
15014     } else {
15015         BackwardInner(currentMove - 1);
15016     }
15017 }
15018
15019 void
15020 ToStartEvent ()
15021 {
15022     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15023         /* to optimize, we temporarily turn off analysis mode while we undo
15024          * all the moves. Otherwise we get analysis output after each undo.
15025          */
15026         if (first.analysisSupport) {
15027           SendToProgram("exit\nforce\n", &first);
15028           first.analyzing = FALSE;
15029         }
15030     }
15031
15032     if (gameMode == IcsExamining && !pausing) {
15033         SendToICS(ics_prefix);
15034         SendToICS("backward 999999\n");
15035     } else {
15036         BackwardInner(backwardMostMove);
15037     }
15038
15039     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15040         /* we have fed all the moves, so reactivate analysis mode */
15041         SendToProgram("analyze\n", &first);
15042         first.analyzing = TRUE;
15043         /*first.maybeThinking = TRUE;*/
15044         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15045     }
15046 }
15047
15048 void
15049 ToNrEvent (int to)
15050 {
15051   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15052   if (to >= forwardMostMove) to = forwardMostMove;
15053   if (to <= backwardMostMove) to = backwardMostMove;
15054   if (to < currentMove) {
15055     BackwardInner(to);
15056   } else {
15057     ForwardInner(to);
15058   }
15059 }
15060
15061 void
15062 RevertEvent (Boolean annotate)
15063 {
15064     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15065         return;
15066     }
15067     if (gameMode != IcsExamining) {
15068         DisplayError(_("You are not examining a game"), 0);
15069         return;
15070     }
15071     if (pausing) {
15072         DisplayError(_("You can't revert while pausing"), 0);
15073         return;
15074     }
15075     SendToICS(ics_prefix);
15076     SendToICS("revert\n");
15077 }
15078
15079 void
15080 RetractMoveEvent ()
15081 {
15082     switch (gameMode) {
15083       case MachinePlaysWhite:
15084       case MachinePlaysBlack:
15085         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15086             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15087             return;
15088         }
15089         if (forwardMostMove < 2) return;
15090         currentMove = forwardMostMove = forwardMostMove - 2;
15091         whiteTimeRemaining = timeRemaining[0][currentMove];
15092         blackTimeRemaining = timeRemaining[1][currentMove];
15093         DisplayBothClocks();
15094         DisplayMove(currentMove - 1);
15095         ClearHighlights();/*!! could figure this out*/
15096         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15097         SendToProgram("remove\n", &first);
15098         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15099         break;
15100
15101       case BeginningOfGame:
15102       default:
15103         break;
15104
15105       case IcsPlayingWhite:
15106       case IcsPlayingBlack:
15107         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15108             SendToICS(ics_prefix);
15109             SendToICS("takeback 2\n");
15110         } else {
15111             SendToICS(ics_prefix);
15112             SendToICS("takeback 1\n");
15113         }
15114         break;
15115     }
15116 }
15117
15118 void
15119 MoveNowEvent ()
15120 {
15121     ChessProgramState *cps;
15122
15123     switch (gameMode) {
15124       case MachinePlaysWhite:
15125         if (!WhiteOnMove(forwardMostMove)) {
15126             DisplayError(_("It is your turn"), 0);
15127             return;
15128         }
15129         cps = &first;
15130         break;
15131       case MachinePlaysBlack:
15132         if (WhiteOnMove(forwardMostMove)) {
15133             DisplayError(_("It is your turn"), 0);
15134             return;
15135         }
15136         cps = &first;
15137         break;
15138       case TwoMachinesPlay:
15139         if (WhiteOnMove(forwardMostMove) ==
15140             (first.twoMachinesColor[0] == 'w')) {
15141             cps = &first;
15142         } else {
15143             cps = &second;
15144         }
15145         break;
15146       case BeginningOfGame:
15147       default:
15148         return;
15149     }
15150     SendToProgram("?\n", cps);
15151 }
15152
15153 void
15154 TruncateGameEvent ()
15155 {
15156     EditGameEvent();
15157     if (gameMode != EditGame) return;
15158     TruncateGame();
15159 }
15160
15161 void
15162 TruncateGame ()
15163 {
15164     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15165     if (forwardMostMove > currentMove) {
15166         if (gameInfo.resultDetails != NULL) {
15167             free(gameInfo.resultDetails);
15168             gameInfo.resultDetails = NULL;
15169             gameInfo.result = GameUnfinished;
15170         }
15171         forwardMostMove = currentMove;
15172         HistorySet(parseList, backwardMostMove, forwardMostMove,
15173                    currentMove-1);
15174     }
15175 }
15176
15177 void
15178 HintEvent ()
15179 {
15180     if (appData.noChessProgram) return;
15181     switch (gameMode) {
15182       case MachinePlaysWhite:
15183         if (WhiteOnMove(forwardMostMove)) {
15184             DisplayError(_("Wait until your turn"), 0);
15185             return;
15186         }
15187         break;
15188       case BeginningOfGame:
15189       case MachinePlaysBlack:
15190         if (!WhiteOnMove(forwardMostMove)) {
15191             DisplayError(_("Wait until your turn"), 0);
15192             return;
15193         }
15194         break;
15195       default:
15196         DisplayError(_("No hint available"), 0);
15197         return;
15198     }
15199     SendToProgram("hint\n", &first);
15200     hintRequested = TRUE;
15201 }
15202
15203 void
15204 CreateBookEvent ()
15205 {
15206     ListGame * lg = (ListGame *) gameList.head;
15207     FILE *f;
15208     int nItem;
15209     static int secondTime = FALSE;
15210
15211     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15212         DisplayError(_("Game list not loaded or empty"), 0);
15213         return;
15214     }
15215
15216     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15217         fclose(f);
15218         secondTime++;
15219         DisplayNote(_("Book file exists! Try again for overwrite."));
15220         return;
15221     }
15222
15223     creatingBook = TRUE;
15224     secondTime = FALSE;
15225
15226     /* Get list size */
15227     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15228         LoadGame(f, nItem, "", TRUE);
15229         AddGameToBook(TRUE);
15230         lg = (ListGame *) lg->node.succ;
15231     }
15232
15233     creatingBook = FALSE;
15234     FlushBook();
15235 }
15236
15237 void
15238 BookEvent ()
15239 {
15240     if (appData.noChessProgram) return;
15241     switch (gameMode) {
15242       case MachinePlaysWhite:
15243         if (WhiteOnMove(forwardMostMove)) {
15244             DisplayError(_("Wait until your turn"), 0);
15245             return;
15246         }
15247         break;
15248       case BeginningOfGame:
15249       case MachinePlaysBlack:
15250         if (!WhiteOnMove(forwardMostMove)) {
15251             DisplayError(_("Wait until your turn"), 0);
15252             return;
15253         }
15254         break;
15255       case EditPosition:
15256         EditPositionDone(TRUE);
15257         break;
15258       case TwoMachinesPlay:
15259         return;
15260       default:
15261         break;
15262     }
15263     SendToProgram("bk\n", &first);
15264     bookOutput[0] = NULLCHAR;
15265     bookRequested = TRUE;
15266 }
15267
15268 void
15269 AboutGameEvent ()
15270 {
15271     char *tags = PGNTags(&gameInfo);
15272     TagsPopUp(tags, CmailMsg());
15273     free(tags);
15274 }
15275
15276 /* end button procedures */
15277
15278 void
15279 PrintPosition (FILE *fp, int move)
15280 {
15281     int i, j;
15282
15283     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15284         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15285             char c = PieceToChar(boards[move][i][j]);
15286             fputc(c == 'x' ? '.' : c, fp);
15287             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15288         }
15289     }
15290     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15291       fprintf(fp, "white to play\n");
15292     else
15293       fprintf(fp, "black to play\n");
15294 }
15295
15296 void
15297 PrintOpponents (FILE *fp)
15298 {
15299     if (gameInfo.white != NULL) {
15300         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15301     } else {
15302         fprintf(fp, "\n");
15303     }
15304 }
15305
15306 /* Find last component of program's own name, using some heuristics */
15307 void
15308 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15309 {
15310     char *p, *q, c;
15311     int local = (strcmp(host, "localhost") == 0);
15312     while (!local && (p = strchr(prog, ';')) != NULL) {
15313         p++;
15314         while (*p == ' ') p++;
15315         prog = p;
15316     }
15317     if (*prog == '"' || *prog == '\'') {
15318         q = strchr(prog + 1, *prog);
15319     } else {
15320         q = strchr(prog, ' ');
15321     }
15322     if (q == NULL) q = prog + strlen(prog);
15323     p = q;
15324     while (p >= prog && *p != '/' && *p != '\\') p--;
15325     p++;
15326     if(p == prog && *p == '"') p++;
15327     c = *q; *q = 0;
15328     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15329     memcpy(buf, p, q - p);
15330     buf[q - p] = NULLCHAR;
15331     if (!local) {
15332         strcat(buf, "@");
15333         strcat(buf, host);
15334     }
15335 }
15336
15337 char *
15338 TimeControlTagValue ()
15339 {
15340     char buf[MSG_SIZ];
15341     if (!appData.clockMode) {
15342       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15343     } else if (movesPerSession > 0) {
15344       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15345     } else if (timeIncrement == 0) {
15346       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15347     } else {
15348       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15349     }
15350     return StrSave(buf);
15351 }
15352
15353 void
15354 SetGameInfo ()
15355 {
15356     /* This routine is used only for certain modes */
15357     VariantClass v = gameInfo.variant;
15358     ChessMove r = GameUnfinished;
15359     char *p = NULL;
15360
15361     if(keepInfo) return;
15362
15363     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15364         r = gameInfo.result;
15365         p = gameInfo.resultDetails;
15366         gameInfo.resultDetails = NULL;
15367     }
15368     ClearGameInfo(&gameInfo);
15369     gameInfo.variant = v;
15370
15371     switch (gameMode) {
15372       case MachinePlaysWhite:
15373         gameInfo.event = StrSave( appData.pgnEventHeader );
15374         gameInfo.site = StrSave(HostName());
15375         gameInfo.date = PGNDate();
15376         gameInfo.round = StrSave("-");
15377         gameInfo.white = StrSave(first.tidy);
15378         gameInfo.black = StrSave(UserName());
15379         gameInfo.timeControl = TimeControlTagValue();
15380         break;
15381
15382       case MachinePlaysBlack:
15383         gameInfo.event = StrSave( appData.pgnEventHeader );
15384         gameInfo.site = StrSave(HostName());
15385         gameInfo.date = PGNDate();
15386         gameInfo.round = StrSave("-");
15387         gameInfo.white = StrSave(UserName());
15388         gameInfo.black = StrSave(first.tidy);
15389         gameInfo.timeControl = TimeControlTagValue();
15390         break;
15391
15392       case TwoMachinesPlay:
15393         gameInfo.event = StrSave( appData.pgnEventHeader );
15394         gameInfo.site = StrSave(HostName());
15395         gameInfo.date = PGNDate();
15396         if (roundNr > 0) {
15397             char buf[MSG_SIZ];
15398             snprintf(buf, MSG_SIZ, "%d", roundNr);
15399             gameInfo.round = StrSave(buf);
15400         } else {
15401             gameInfo.round = StrSave("-");
15402         }
15403         if (first.twoMachinesColor[0] == 'w') {
15404             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15405             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15406         } else {
15407             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15408             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15409         }
15410         gameInfo.timeControl = TimeControlTagValue();
15411         break;
15412
15413       case EditGame:
15414         gameInfo.event = StrSave("Edited game");
15415         gameInfo.site = StrSave(HostName());
15416         gameInfo.date = PGNDate();
15417         gameInfo.round = StrSave("-");
15418         gameInfo.white = StrSave("-");
15419         gameInfo.black = StrSave("-");
15420         gameInfo.result = r;
15421         gameInfo.resultDetails = p;
15422         break;
15423
15424       case EditPosition:
15425         gameInfo.event = StrSave("Edited position");
15426         gameInfo.site = StrSave(HostName());
15427         gameInfo.date = PGNDate();
15428         gameInfo.round = StrSave("-");
15429         gameInfo.white = StrSave("-");
15430         gameInfo.black = StrSave("-");
15431         break;
15432
15433       case IcsPlayingWhite:
15434       case IcsPlayingBlack:
15435       case IcsObserving:
15436       case IcsExamining:
15437         break;
15438
15439       case PlayFromGameFile:
15440         gameInfo.event = StrSave("Game from non-PGN file");
15441         gameInfo.site = StrSave(HostName());
15442         gameInfo.date = PGNDate();
15443         gameInfo.round = StrSave("-");
15444         gameInfo.white = StrSave("?");
15445         gameInfo.black = StrSave("?");
15446         break;
15447
15448       default:
15449         break;
15450     }
15451 }
15452
15453 void
15454 ReplaceComment (int index, char *text)
15455 {
15456     int len;
15457     char *p;
15458     float score;
15459
15460     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15461        pvInfoList[index-1].depth == len &&
15462        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15463        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15464     while (*text == '\n') text++;
15465     len = strlen(text);
15466     while (len > 0 && text[len - 1] == '\n') len--;
15467
15468     if (commentList[index] != NULL)
15469       free(commentList[index]);
15470
15471     if (len == 0) {
15472         commentList[index] = NULL;
15473         return;
15474     }
15475   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15476       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15477       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15478     commentList[index] = (char *) malloc(len + 2);
15479     strncpy(commentList[index], text, len);
15480     commentList[index][len] = '\n';
15481     commentList[index][len + 1] = NULLCHAR;
15482   } else {
15483     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15484     char *p;
15485     commentList[index] = (char *) malloc(len + 7);
15486     safeStrCpy(commentList[index], "{\n", 3);
15487     safeStrCpy(commentList[index]+2, text, len+1);
15488     commentList[index][len+2] = NULLCHAR;
15489     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15490     strcat(commentList[index], "\n}\n");
15491   }
15492 }
15493
15494 void
15495 CrushCRs (char *text)
15496 {
15497   char *p = text;
15498   char *q = text;
15499   char ch;
15500
15501   do {
15502     ch = *p++;
15503     if (ch == '\r') continue;
15504     *q++ = ch;
15505   } while (ch != '\0');
15506 }
15507
15508 void
15509 AppendComment (int index, char *text, Boolean addBraces)
15510 /* addBraces  tells if we should add {} */
15511 {
15512     int oldlen, len;
15513     char *old;
15514
15515 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15516     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15517
15518     CrushCRs(text);
15519     while (*text == '\n') text++;
15520     len = strlen(text);
15521     while (len > 0 && text[len - 1] == '\n') len--;
15522     text[len] = NULLCHAR;
15523
15524     if (len == 0) return;
15525
15526     if (commentList[index] != NULL) {
15527       Boolean addClosingBrace = addBraces;
15528         old = commentList[index];
15529         oldlen = strlen(old);
15530         while(commentList[index][oldlen-1] ==  '\n')
15531           commentList[index][--oldlen] = NULLCHAR;
15532         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15533         safeStrCpy(commentList[index], old, oldlen + len + 6);
15534         free(old);
15535         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15536         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15537           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15538           while (*text == '\n') { text++; len--; }
15539           commentList[index][--oldlen] = NULLCHAR;
15540       }
15541         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15542         else          strcat(commentList[index], "\n");
15543         strcat(commentList[index], text);
15544         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15545         else          strcat(commentList[index], "\n");
15546     } else {
15547         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15548         if(addBraces)
15549           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15550         else commentList[index][0] = NULLCHAR;
15551         strcat(commentList[index], text);
15552         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15553         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15554     }
15555 }
15556
15557 static char *
15558 FindStr (char * text, char * sub_text)
15559 {
15560     char * result = strstr( text, sub_text );
15561
15562     if( result != NULL ) {
15563         result += strlen( sub_text );
15564     }
15565
15566     return result;
15567 }
15568
15569 /* [AS] Try to extract PV info from PGN comment */
15570 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15571 char *
15572 GetInfoFromComment (int index, char * text)
15573 {
15574     char * sep = text, *p;
15575
15576     if( text != NULL && index > 0 ) {
15577         int score = 0;
15578         int depth = 0;
15579         int time = -1, sec = 0, deci;
15580         char * s_eval = FindStr( text, "[%eval " );
15581         char * s_emt = FindStr( text, "[%emt " );
15582
15583         if( s_eval != NULL || s_emt != NULL ) {
15584             /* New style */
15585             char delim;
15586
15587             if( s_eval != NULL ) {
15588                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15589                     return text;
15590                 }
15591
15592                 if( delim != ']' ) {
15593                     return text;
15594                 }
15595             }
15596
15597             if( s_emt != NULL ) {
15598             }
15599                 return text;
15600         }
15601         else {
15602             /* We expect something like: [+|-]nnn.nn/dd */
15603             int score_lo = 0;
15604
15605             if(*text != '{') return text; // [HGM] braces: must be normal comment
15606
15607             sep = strchr( text, '/' );
15608             if( sep == NULL || sep < (text+4) ) {
15609                 return text;
15610             }
15611
15612             p = text;
15613             if(p[1] == '(') { // comment starts with PV
15614                p = strchr(p, ')'); // locate end of PV
15615                if(p == NULL || sep < p+5) return text;
15616                // at this point we have something like "{(.*) +0.23/6 ..."
15617                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15618                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15619                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15620             }
15621             time = -1; sec = -1; deci = -1;
15622             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15623                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15624                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15625                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15626                 return text;
15627             }
15628
15629             if( score_lo < 0 || score_lo >= 100 ) {
15630                 return text;
15631             }
15632
15633             if(sec >= 0) time = 600*time + 10*sec; else
15634             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15635
15636             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15637
15638             /* [HGM] PV time: now locate end of PV info */
15639             while( *++sep >= '0' && *sep <= '9'); // strip depth
15640             if(time >= 0)
15641             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15642             if(sec >= 0)
15643             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15644             if(deci >= 0)
15645             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15646             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15647         }
15648
15649         if( depth <= 0 ) {
15650             return text;
15651         }
15652
15653         if( time < 0 ) {
15654             time = -1;
15655         }
15656
15657         pvInfoList[index-1].depth = depth;
15658         pvInfoList[index-1].score = score;
15659         pvInfoList[index-1].time  = 10*time; // centi-sec
15660         if(*sep == '}') *sep = 0; else *--sep = '{';
15661         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15662     }
15663     return sep;
15664 }
15665
15666 void
15667 SendToProgram (char *message, ChessProgramState *cps)
15668 {
15669     int count, outCount, error;
15670     char buf[MSG_SIZ];
15671
15672     if (cps->pr == NoProc) return;
15673     Attention(cps);
15674
15675     if (appData.debugMode) {
15676         TimeMark now;
15677         GetTimeMark(&now);
15678         fprintf(debugFP, "%ld >%-6s: %s",
15679                 SubtractTimeMarks(&now, &programStartTime),
15680                 cps->which, message);
15681         if(serverFP)
15682             fprintf(serverFP, "%ld >%-6s: %s",
15683                 SubtractTimeMarks(&now, &programStartTime),
15684                 cps->which, message), fflush(serverFP);
15685     }
15686
15687     count = strlen(message);
15688     outCount = OutputToProcess(cps->pr, message, count, &error);
15689     if (outCount < count && !exiting
15690                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15691       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15692       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15693         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15694             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15695                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15696                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15697                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15698             } else {
15699                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15700                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15701                 gameInfo.result = res;
15702             }
15703             gameInfo.resultDetails = StrSave(buf);
15704         }
15705         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15706         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15707     }
15708 }
15709
15710 void
15711 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15712 {
15713     char *end_str;
15714     char buf[MSG_SIZ];
15715     ChessProgramState *cps = (ChessProgramState *)closure;
15716
15717     if (isr != cps->isr) return; /* Killed intentionally */
15718     if (count <= 0) {
15719         if (count == 0) {
15720             RemoveInputSource(cps->isr);
15721             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15722                     _(cps->which), cps->program);
15723             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15724             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15725                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15726                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15727                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15728                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15729                 } else {
15730                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15731                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15732                     gameInfo.result = res;
15733                 }
15734                 gameInfo.resultDetails = StrSave(buf);
15735             }
15736             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15737             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15738         } else {
15739             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15740                     _(cps->which), cps->program);
15741             RemoveInputSource(cps->isr);
15742
15743             /* [AS] Program is misbehaving badly... kill it */
15744             if( count == -2 ) {
15745                 DestroyChildProcess( cps->pr, 9 );
15746                 cps->pr = NoProc;
15747             }
15748
15749             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15750         }
15751         return;
15752     }
15753
15754     if ((end_str = strchr(message, '\r')) != NULL)
15755       *end_str = NULLCHAR;
15756     if ((end_str = strchr(message, '\n')) != NULL)
15757       *end_str = NULLCHAR;
15758
15759     if (appData.debugMode) {
15760         TimeMark now; int print = 1;
15761         char *quote = ""; char c; int i;
15762
15763         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15764                 char start = message[0];
15765                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15766                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15767                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15768                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15769                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15770                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15771                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15772                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15773                    sscanf(message, "hint: %c", &c)!=1 &&
15774                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15775                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15776                     print = (appData.engineComments >= 2);
15777                 }
15778                 message[0] = start; // restore original message
15779         }
15780         if(print) {
15781                 GetTimeMark(&now);
15782                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15783                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15784                         quote,
15785                         message);
15786                 if(serverFP)
15787                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15788                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15789                         quote,
15790                         message), fflush(serverFP);
15791         }
15792     }
15793
15794     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15795     if (appData.icsEngineAnalyze) {
15796         if (strstr(message, "whisper") != NULL ||
15797              strstr(message, "kibitz") != NULL ||
15798             strstr(message, "tellics") != NULL) return;
15799     }
15800
15801     HandleMachineMove(message, cps);
15802 }
15803
15804
15805 void
15806 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15807 {
15808     char buf[MSG_SIZ];
15809     int seconds;
15810
15811     if( timeControl_2 > 0 ) {
15812         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15813             tc = timeControl_2;
15814         }
15815     }
15816     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15817     inc /= cps->timeOdds;
15818     st  /= cps->timeOdds;
15819
15820     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15821
15822     if (st > 0) {
15823       /* Set exact time per move, normally using st command */
15824       if (cps->stKludge) {
15825         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15826         seconds = st % 60;
15827         if (seconds == 0) {
15828           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15829         } else {
15830           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15831         }
15832       } else {
15833         snprintf(buf, MSG_SIZ, "st %d\n", st);
15834       }
15835     } else {
15836       /* Set conventional or incremental time control, using level command */
15837       if (seconds == 0) {
15838         /* Note old gnuchess bug -- minutes:seconds used to not work.
15839            Fixed in later versions, but still avoid :seconds
15840            when seconds is 0. */
15841         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15842       } else {
15843         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15844                  seconds, inc/1000.);
15845       }
15846     }
15847     SendToProgram(buf, cps);
15848
15849     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15850     /* Orthogonally, limit search to given depth */
15851     if (sd > 0) {
15852       if (cps->sdKludge) {
15853         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15854       } else {
15855         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15856       }
15857       SendToProgram(buf, cps);
15858     }
15859
15860     if(cps->nps >= 0) { /* [HGM] nps */
15861         if(cps->supportsNPS == FALSE)
15862           cps->nps = -1; // don't use if engine explicitly says not supported!
15863         else {
15864           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15865           SendToProgram(buf, cps);
15866         }
15867     }
15868 }
15869
15870 ChessProgramState *
15871 WhitePlayer ()
15872 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15873 {
15874     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15875        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15876         return &second;
15877     return &first;
15878 }
15879
15880 void
15881 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15882 {
15883     char message[MSG_SIZ];
15884     long time, otime;
15885
15886     /* Note: this routine must be called when the clocks are stopped
15887        or when they have *just* been set or switched; otherwise
15888        it will be off by the time since the current tick started.
15889     */
15890     if (machineWhite) {
15891         time = whiteTimeRemaining / 10;
15892         otime = blackTimeRemaining / 10;
15893     } else {
15894         time = blackTimeRemaining / 10;
15895         otime = whiteTimeRemaining / 10;
15896     }
15897     /* [HGM] translate opponent's time by time-odds factor */
15898     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15899
15900     if (time <= 0) time = 1;
15901     if (otime <= 0) otime = 1;
15902
15903     snprintf(message, MSG_SIZ, "time %ld\n", time);
15904     SendToProgram(message, cps);
15905
15906     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15907     SendToProgram(message, cps);
15908 }
15909
15910 int
15911 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15912 {
15913   char buf[MSG_SIZ];
15914   int len = strlen(name);
15915   int val;
15916
15917   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15918     (*p) += len + 1;
15919     sscanf(*p, "%d", &val);
15920     *loc = (val != 0);
15921     while (**p && **p != ' ')
15922       (*p)++;
15923     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15924     SendToProgram(buf, cps);
15925     return TRUE;
15926   }
15927   return FALSE;
15928 }
15929
15930 int
15931 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15932 {
15933   char buf[MSG_SIZ];
15934   int len = strlen(name);
15935   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15936     (*p) += len + 1;
15937     sscanf(*p, "%d", loc);
15938     while (**p && **p != ' ') (*p)++;
15939     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15940     SendToProgram(buf, cps);
15941     return TRUE;
15942   }
15943   return FALSE;
15944 }
15945
15946 int
15947 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15948 {
15949   char buf[MSG_SIZ];
15950   int len = strlen(name);
15951   if (strncmp((*p), name, len) == 0
15952       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15953     (*p) += len + 2;
15954     sscanf(*p, "%[^\"]", loc);
15955     while (**p && **p != '\"') (*p)++;
15956     if (**p == '\"') (*p)++;
15957     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15958     SendToProgram(buf, cps);
15959     return TRUE;
15960   }
15961   return FALSE;
15962 }
15963
15964 int
15965 ParseOption (Option *opt, ChessProgramState *cps)
15966 // [HGM] options: process the string that defines an engine option, and determine
15967 // name, type, default value, and allowed value range
15968 {
15969         char *p, *q, buf[MSG_SIZ];
15970         int n, min = (-1)<<31, max = 1<<31, def;
15971
15972         if(p = strstr(opt->name, " -spin ")) {
15973             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15974             if(max < min) max = min; // enforce consistency
15975             if(def < min) def = min;
15976             if(def > max) def = max;
15977             opt->value = def;
15978             opt->min = min;
15979             opt->max = max;
15980             opt->type = Spin;
15981         } else if((p = strstr(opt->name, " -slider "))) {
15982             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15983             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15984             if(max < min) max = min; // enforce consistency
15985             if(def < min) def = min;
15986             if(def > max) def = max;
15987             opt->value = def;
15988             opt->min = min;
15989             opt->max = max;
15990             opt->type = Spin; // Slider;
15991         } else if((p = strstr(opt->name, " -string "))) {
15992             opt->textValue = p+9;
15993             opt->type = TextBox;
15994         } else if((p = strstr(opt->name, " -file "))) {
15995             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15996             opt->textValue = p+7;
15997             opt->type = FileName; // FileName;
15998         } else if((p = strstr(opt->name, " -path "))) {
15999             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16000             opt->textValue = p+7;
16001             opt->type = PathName; // PathName;
16002         } else if(p = strstr(opt->name, " -check ")) {
16003             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16004             opt->value = (def != 0);
16005             opt->type = CheckBox;
16006         } else if(p = strstr(opt->name, " -combo ")) {
16007             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16008             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16009             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16010             opt->value = n = 0;
16011             while(q = StrStr(q, " /// ")) {
16012                 n++; *q = 0;    // count choices, and null-terminate each of them
16013                 q += 5;
16014                 if(*q == '*') { // remember default, which is marked with * prefix
16015                     q++;
16016                     opt->value = n;
16017                 }
16018                 cps->comboList[cps->comboCnt++] = q;
16019             }
16020             cps->comboList[cps->comboCnt++] = NULL;
16021             opt->max = n + 1;
16022             opt->type = ComboBox;
16023         } else if(p = strstr(opt->name, " -button")) {
16024             opt->type = Button;
16025         } else if(p = strstr(opt->name, " -save")) {
16026             opt->type = SaveButton;
16027         } else return FALSE;
16028         *p = 0; // terminate option name
16029         // now look if the command-line options define a setting for this engine option.
16030         if(cps->optionSettings && cps->optionSettings[0])
16031             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16032         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16033           snprintf(buf, MSG_SIZ, "option %s", p);
16034                 if(p = strstr(buf, ",")) *p = 0;
16035                 if(q = strchr(buf, '=')) switch(opt->type) {
16036                     case ComboBox:
16037                         for(n=0; n<opt->max; n++)
16038                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16039                         break;
16040                     case TextBox:
16041                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16042                         break;
16043                     case Spin:
16044                     case CheckBox:
16045                         opt->value = atoi(q+1);
16046                     default:
16047                         break;
16048                 }
16049                 strcat(buf, "\n");
16050                 SendToProgram(buf, cps);
16051         }
16052         return TRUE;
16053 }
16054
16055 void
16056 FeatureDone (ChessProgramState *cps, int val)
16057 {
16058   DelayedEventCallback cb = GetDelayedEvent();
16059   if ((cb == InitBackEnd3 && cps == &first) ||
16060       (cb == SettingsMenuIfReady && cps == &second) ||
16061       (cb == LoadEngine) ||
16062       (cb == TwoMachinesEventIfReady)) {
16063     CancelDelayedEvent();
16064     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16065   }
16066   cps->initDone = val;
16067   if(val) cps->reload = FALSE;
16068 }
16069
16070 /* Parse feature command from engine */
16071 void
16072 ParseFeatures (char *args, ChessProgramState *cps)
16073 {
16074   char *p = args;
16075   char *q;
16076   int val;
16077   char buf[MSG_SIZ];
16078
16079   for (;;) {
16080     while (*p == ' ') p++;
16081     if (*p == NULLCHAR) return;
16082
16083     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16084     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16085     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16086     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16087     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16088     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16089     if (BoolFeature(&p, "reuse", &val, cps)) {
16090       /* Engine can disable reuse, but can't enable it if user said no */
16091       if (!val) cps->reuse = FALSE;
16092       continue;
16093     }
16094     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16095     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16096       if (gameMode == TwoMachinesPlay) {
16097         DisplayTwoMachinesTitle();
16098       } else {
16099         DisplayTitle("");
16100       }
16101       continue;
16102     }
16103     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16104     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16105     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16106     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16107     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16108     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16109     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16110     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16111     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16112     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16113     if (IntFeature(&p, "done", &val, cps)) {
16114       FeatureDone(cps, val);
16115       continue;
16116     }
16117     /* Added by Tord: */
16118     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16119     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16120     /* End of additions by Tord */
16121
16122     /* [HGM] added features: */
16123     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16124     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16125     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16126     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16127     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16128     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16129     if (StringFeature(&p, "option", buf, cps)) {
16130         if(cps->reload) continue; // we are reloading because of xreuse
16131         FREE(cps->option[cps->nrOptions].name);
16132         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16133         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16134         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16135           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16136             SendToProgram(buf, cps);
16137             continue;
16138         }
16139         if(cps->nrOptions >= MAX_OPTIONS) {
16140             cps->nrOptions--;
16141             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16142             DisplayError(buf, 0);
16143         }
16144         continue;
16145     }
16146     /* End of additions by HGM */
16147
16148     /* unknown feature: complain and skip */
16149     q = p;
16150     while (*q && *q != '=') q++;
16151     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16152     SendToProgram(buf, cps);
16153     p = q;
16154     if (*p == '=') {
16155       p++;
16156       if (*p == '\"') {
16157         p++;
16158         while (*p && *p != '\"') p++;
16159         if (*p == '\"') p++;
16160       } else {
16161         while (*p && *p != ' ') p++;
16162       }
16163     }
16164   }
16165
16166 }
16167
16168 void
16169 PeriodicUpdatesEvent (int newState)
16170 {
16171     if (newState == appData.periodicUpdates)
16172       return;
16173
16174     appData.periodicUpdates=newState;
16175
16176     /* Display type changes, so update it now */
16177 //    DisplayAnalysis();
16178
16179     /* Get the ball rolling again... */
16180     if (newState) {
16181         AnalysisPeriodicEvent(1);
16182         StartAnalysisClock();
16183     }
16184 }
16185
16186 void
16187 PonderNextMoveEvent (int newState)
16188 {
16189     if (newState == appData.ponderNextMove) return;
16190     if (gameMode == EditPosition) EditPositionDone(TRUE);
16191     if (newState) {
16192         SendToProgram("hard\n", &first);
16193         if (gameMode == TwoMachinesPlay) {
16194             SendToProgram("hard\n", &second);
16195         }
16196     } else {
16197         SendToProgram("easy\n", &first);
16198         thinkOutput[0] = NULLCHAR;
16199         if (gameMode == TwoMachinesPlay) {
16200             SendToProgram("easy\n", &second);
16201         }
16202     }
16203     appData.ponderNextMove = newState;
16204 }
16205
16206 void
16207 NewSettingEvent (int option, int *feature, char *command, int value)
16208 {
16209     char buf[MSG_SIZ];
16210
16211     if (gameMode == EditPosition) EditPositionDone(TRUE);
16212     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16213     if(feature == NULL || *feature) SendToProgram(buf, &first);
16214     if (gameMode == TwoMachinesPlay) {
16215         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16216     }
16217 }
16218
16219 void
16220 ShowThinkingEvent ()
16221 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16222 {
16223     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16224     int newState = appData.showThinking
16225         // [HGM] thinking: other features now need thinking output as well
16226         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16227
16228     if (oldState == newState) return;
16229     oldState = newState;
16230     if (gameMode == EditPosition) EditPositionDone(TRUE);
16231     if (oldState) {
16232         SendToProgram("post\n", &first);
16233         if (gameMode == TwoMachinesPlay) {
16234             SendToProgram("post\n", &second);
16235         }
16236     } else {
16237         SendToProgram("nopost\n", &first);
16238         thinkOutput[0] = NULLCHAR;
16239         if (gameMode == TwoMachinesPlay) {
16240             SendToProgram("nopost\n", &second);
16241         }
16242     }
16243 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16244 }
16245
16246 void
16247 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16248 {
16249   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16250   if (pr == NoProc) return;
16251   AskQuestion(title, question, replyPrefix, pr);
16252 }
16253
16254 void
16255 TypeInEvent (char firstChar)
16256 {
16257     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16258         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16259         gameMode == AnalyzeMode || gameMode == EditGame ||
16260         gameMode == EditPosition || gameMode == IcsExamining ||
16261         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16262         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16263                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16264                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16265         gameMode == Training) PopUpMoveDialog(firstChar);
16266 }
16267
16268 void
16269 TypeInDoneEvent (char *move)
16270 {
16271         Board board;
16272         int n, fromX, fromY, toX, toY;
16273         char promoChar;
16274         ChessMove moveType;
16275
16276         // [HGM] FENedit
16277         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16278                 EditPositionPasteFEN(move);
16279                 return;
16280         }
16281         // [HGM] movenum: allow move number to be typed in any mode
16282         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16283           ToNrEvent(2*n-1);
16284           return;
16285         }
16286         // undocumented kludge: allow command-line option to be typed in!
16287         // (potentially fatal, and does not implement the effect of the option.)
16288         // should only be used for options that are values on which future decisions will be made,
16289         // and definitely not on options that would be used during initialization.
16290         if(strstr(move, "!!! -") == move) {
16291             ParseArgsFromString(move+4);
16292             return;
16293         }
16294
16295       if (gameMode != EditGame && currentMove != forwardMostMove &&
16296         gameMode != Training) {
16297         DisplayMoveError(_("Displayed move is not current"));
16298       } else {
16299         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16300           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16301         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16302         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16303           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16304           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16305         } else {
16306           DisplayMoveError(_("Could not parse move"));
16307         }
16308       }
16309 }
16310
16311 void
16312 DisplayMove (int moveNumber)
16313 {
16314     char message[MSG_SIZ];
16315     char res[MSG_SIZ];
16316     char cpThinkOutput[MSG_SIZ];
16317
16318     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16319
16320     if (moveNumber == forwardMostMove - 1 ||
16321         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16322
16323         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16324
16325         if (strchr(cpThinkOutput, '\n')) {
16326             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16327         }
16328     } else {
16329         *cpThinkOutput = NULLCHAR;
16330     }
16331
16332     /* [AS] Hide thinking from human user */
16333     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16334         *cpThinkOutput = NULLCHAR;
16335         if( thinkOutput[0] != NULLCHAR ) {
16336             int i;
16337
16338             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16339                 cpThinkOutput[i] = '.';
16340             }
16341             cpThinkOutput[i] = NULLCHAR;
16342             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16343         }
16344     }
16345
16346     if (moveNumber == forwardMostMove - 1 &&
16347         gameInfo.resultDetails != NULL) {
16348         if (gameInfo.resultDetails[0] == NULLCHAR) {
16349           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16350         } else {
16351           snprintf(res, MSG_SIZ, " {%s} %s",
16352                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16353         }
16354     } else {
16355         res[0] = NULLCHAR;
16356     }
16357
16358     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16359         DisplayMessage(res, cpThinkOutput);
16360     } else {
16361       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16362                 WhiteOnMove(moveNumber) ? " " : ".. ",
16363                 parseList[moveNumber], res);
16364         DisplayMessage(message, cpThinkOutput);
16365     }
16366 }
16367
16368 void
16369 DisplayComment (int moveNumber, char *text)
16370 {
16371     char title[MSG_SIZ];
16372
16373     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16374       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16375     } else {
16376       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16377               WhiteOnMove(moveNumber) ? " " : ".. ",
16378               parseList[moveNumber]);
16379     }
16380     if (text != NULL && (appData.autoDisplayComment || commentUp))
16381         CommentPopUp(title, text);
16382 }
16383
16384 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16385  * might be busy thinking or pondering.  It can be omitted if your
16386  * gnuchess is configured to stop thinking immediately on any user
16387  * input.  However, that gnuchess feature depends on the FIONREAD
16388  * ioctl, which does not work properly on some flavors of Unix.
16389  */
16390 void
16391 Attention (ChessProgramState *cps)
16392 {
16393 #if ATTENTION
16394     if (!cps->useSigint) return;
16395     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16396     switch (gameMode) {
16397       case MachinePlaysWhite:
16398       case MachinePlaysBlack:
16399       case TwoMachinesPlay:
16400       case IcsPlayingWhite:
16401       case IcsPlayingBlack:
16402       case AnalyzeMode:
16403       case AnalyzeFile:
16404         /* Skip if we know it isn't thinking */
16405         if (!cps->maybeThinking) return;
16406         if (appData.debugMode)
16407           fprintf(debugFP, "Interrupting %s\n", cps->which);
16408         InterruptChildProcess(cps->pr);
16409         cps->maybeThinking = FALSE;
16410         break;
16411       default:
16412         break;
16413     }
16414 #endif /*ATTENTION*/
16415 }
16416
16417 int
16418 CheckFlags ()
16419 {
16420     if (whiteTimeRemaining <= 0) {
16421         if (!whiteFlag) {
16422             whiteFlag = TRUE;
16423             if (appData.icsActive) {
16424                 if (appData.autoCallFlag &&
16425                     gameMode == IcsPlayingBlack && !blackFlag) {
16426                   SendToICS(ics_prefix);
16427                   SendToICS("flag\n");
16428                 }
16429             } else {
16430                 if (blackFlag) {
16431                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16432                 } else {
16433                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16434                     if (appData.autoCallFlag) {
16435                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16436                         return TRUE;
16437                     }
16438                 }
16439             }
16440         }
16441     }
16442     if (blackTimeRemaining <= 0) {
16443         if (!blackFlag) {
16444             blackFlag = TRUE;
16445             if (appData.icsActive) {
16446                 if (appData.autoCallFlag &&
16447                     gameMode == IcsPlayingWhite && !whiteFlag) {
16448                   SendToICS(ics_prefix);
16449                   SendToICS("flag\n");
16450                 }
16451             } else {
16452                 if (whiteFlag) {
16453                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16454                 } else {
16455                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16456                     if (appData.autoCallFlag) {
16457                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16458                         return TRUE;
16459                     }
16460                 }
16461             }
16462         }
16463     }
16464     return FALSE;
16465 }
16466
16467 void
16468 CheckTimeControl ()
16469 {
16470     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16471         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16472
16473     /*
16474      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16475      */
16476     if ( !WhiteOnMove(forwardMostMove) ) {
16477         /* White made time control */
16478         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16479         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16480         /* [HGM] time odds: correct new time quota for time odds! */
16481                                             / WhitePlayer()->timeOdds;
16482         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16483     } else {
16484         lastBlack -= blackTimeRemaining;
16485         /* Black made time control */
16486         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16487                                             / WhitePlayer()->other->timeOdds;
16488         lastWhite = whiteTimeRemaining;
16489     }
16490 }
16491
16492 void
16493 DisplayBothClocks ()
16494 {
16495     int wom = gameMode == EditPosition ?
16496       !blackPlaysFirst : WhiteOnMove(currentMove);
16497     DisplayWhiteClock(whiteTimeRemaining, wom);
16498     DisplayBlackClock(blackTimeRemaining, !wom);
16499 }
16500
16501
16502 /* Timekeeping seems to be a portability nightmare.  I think everyone
16503    has ftime(), but I'm really not sure, so I'm including some ifdefs
16504    to use other calls if you don't.  Clocks will be less accurate if
16505    you have neither ftime nor gettimeofday.
16506 */
16507
16508 /* VS 2008 requires the #include outside of the function */
16509 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16510 #include <sys/timeb.h>
16511 #endif
16512
16513 /* Get the current time as a TimeMark */
16514 void
16515 GetTimeMark (TimeMark *tm)
16516 {
16517 #if HAVE_GETTIMEOFDAY
16518
16519     struct timeval timeVal;
16520     struct timezone timeZone;
16521
16522     gettimeofday(&timeVal, &timeZone);
16523     tm->sec = (long) timeVal.tv_sec;
16524     tm->ms = (int) (timeVal.tv_usec / 1000L);
16525
16526 #else /*!HAVE_GETTIMEOFDAY*/
16527 #if HAVE_FTIME
16528
16529 // include <sys/timeb.h> / moved to just above start of function
16530     struct timeb timeB;
16531
16532     ftime(&timeB);
16533     tm->sec = (long) timeB.time;
16534     tm->ms = (int) timeB.millitm;
16535
16536 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16537     tm->sec = (long) time(NULL);
16538     tm->ms = 0;
16539 #endif
16540 #endif
16541 }
16542
16543 /* Return the difference in milliseconds between two
16544    time marks.  We assume the difference will fit in a long!
16545 */
16546 long
16547 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16548 {
16549     return 1000L*(tm2->sec - tm1->sec) +
16550            (long) (tm2->ms - tm1->ms);
16551 }
16552
16553
16554 /*
16555  * Code to manage the game clocks.
16556  *
16557  * In tournament play, black starts the clock and then white makes a move.
16558  * We give the human user a slight advantage if he is playing white---the
16559  * clocks don't run until he makes his first move, so it takes zero time.
16560  * Also, we don't account for network lag, so we could get out of sync
16561  * with GNU Chess's clock -- but then, referees are always right.
16562  */
16563
16564 static TimeMark tickStartTM;
16565 static long intendedTickLength;
16566
16567 long
16568 NextTickLength (long timeRemaining)
16569 {
16570     long nominalTickLength, nextTickLength;
16571
16572     if (timeRemaining > 0L && timeRemaining <= 10000L)
16573       nominalTickLength = 100L;
16574     else
16575       nominalTickLength = 1000L;
16576     nextTickLength = timeRemaining % nominalTickLength;
16577     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16578
16579     return nextTickLength;
16580 }
16581
16582 /* Adjust clock one minute up or down */
16583 void
16584 AdjustClock (Boolean which, int dir)
16585 {
16586     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16587     if(which) blackTimeRemaining += 60000*dir;
16588     else      whiteTimeRemaining += 60000*dir;
16589     DisplayBothClocks();
16590     adjustedClock = TRUE;
16591 }
16592
16593 /* Stop clocks and reset to a fresh time control */
16594 void
16595 ResetClocks ()
16596 {
16597     (void) StopClockTimer();
16598     if (appData.icsActive) {
16599         whiteTimeRemaining = blackTimeRemaining = 0;
16600     } else if (searchTime) {
16601         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16602         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16603     } else { /* [HGM] correct new time quote for time odds */
16604         whiteTC = blackTC = fullTimeControlString;
16605         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16606         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16607     }
16608     if (whiteFlag || blackFlag) {
16609         DisplayTitle("");
16610         whiteFlag = blackFlag = FALSE;
16611     }
16612     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16613     DisplayBothClocks();
16614     adjustedClock = FALSE;
16615 }
16616
16617 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16618
16619 /* Decrement running clock by amount of time that has passed */
16620 void
16621 DecrementClocks ()
16622 {
16623     long timeRemaining;
16624     long lastTickLength, fudge;
16625     TimeMark now;
16626
16627     if (!appData.clockMode) return;
16628     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16629
16630     GetTimeMark(&now);
16631
16632     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16633
16634     /* Fudge if we woke up a little too soon */
16635     fudge = intendedTickLength - lastTickLength;
16636     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16637
16638     if (WhiteOnMove(forwardMostMove)) {
16639         if(whiteNPS >= 0) lastTickLength = 0;
16640         timeRemaining = whiteTimeRemaining -= lastTickLength;
16641         if(timeRemaining < 0 && !appData.icsActive) {
16642             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16643             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16644                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16645                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16646             }
16647         }
16648         DisplayWhiteClock(whiteTimeRemaining - fudge,
16649                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16650     } else {
16651         if(blackNPS >= 0) lastTickLength = 0;
16652         timeRemaining = blackTimeRemaining -= lastTickLength;
16653         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16654             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16655             if(suddenDeath) {
16656                 blackStartMove = forwardMostMove;
16657                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16658             }
16659         }
16660         DisplayBlackClock(blackTimeRemaining - fudge,
16661                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16662     }
16663     if (CheckFlags()) return;
16664
16665     if(twoBoards) { // count down secondary board's clocks as well
16666         activePartnerTime -= lastTickLength;
16667         partnerUp = 1;
16668         if(activePartner == 'W')
16669             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16670         else
16671             DisplayBlackClock(activePartnerTime, TRUE);
16672         partnerUp = 0;
16673     }
16674
16675     tickStartTM = now;
16676     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16677     StartClockTimer(intendedTickLength);
16678
16679     /* if the time remaining has fallen below the alarm threshold, sound the
16680      * alarm. if the alarm has sounded and (due to a takeback or time control
16681      * with increment) the time remaining has increased to a level above the
16682      * threshold, reset the alarm so it can sound again.
16683      */
16684
16685     if (appData.icsActive && appData.icsAlarm) {
16686
16687         /* make sure we are dealing with the user's clock */
16688         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16689                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16690            )) return;
16691
16692         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16693             alarmSounded = FALSE;
16694         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16695             PlayAlarmSound();
16696             alarmSounded = TRUE;
16697         }
16698     }
16699 }
16700
16701
16702 /* A player has just moved, so stop the previously running
16703    clock and (if in clock mode) start the other one.
16704    We redisplay both clocks in case we're in ICS mode, because
16705    ICS gives us an update to both clocks after every move.
16706    Note that this routine is called *after* forwardMostMove
16707    is updated, so the last fractional tick must be subtracted
16708    from the color that is *not* on move now.
16709 */
16710 void
16711 SwitchClocks (int newMoveNr)
16712 {
16713     long lastTickLength;
16714     TimeMark now;
16715     int flagged = FALSE;
16716
16717     GetTimeMark(&now);
16718
16719     if (StopClockTimer() && appData.clockMode) {
16720         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16721         if (!WhiteOnMove(forwardMostMove)) {
16722             if(blackNPS >= 0) lastTickLength = 0;
16723             blackTimeRemaining -= lastTickLength;
16724            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16725 //         if(pvInfoList[forwardMostMove].time == -1)
16726                  pvInfoList[forwardMostMove].time =               // use GUI time
16727                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16728         } else {
16729            if(whiteNPS >= 0) lastTickLength = 0;
16730            whiteTimeRemaining -= lastTickLength;
16731            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16732 //         if(pvInfoList[forwardMostMove].time == -1)
16733                  pvInfoList[forwardMostMove].time =
16734                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16735         }
16736         flagged = CheckFlags();
16737     }
16738     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16739     CheckTimeControl();
16740
16741     if (flagged || !appData.clockMode) return;
16742
16743     switch (gameMode) {
16744       case MachinePlaysBlack:
16745       case MachinePlaysWhite:
16746       case BeginningOfGame:
16747         if (pausing) return;
16748         break;
16749
16750       case EditGame:
16751       case PlayFromGameFile:
16752       case IcsExamining:
16753         return;
16754
16755       default:
16756         break;
16757     }
16758
16759     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16760         if(WhiteOnMove(forwardMostMove))
16761              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16762         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16763     }
16764
16765     tickStartTM = now;
16766     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16767       whiteTimeRemaining : blackTimeRemaining);
16768     StartClockTimer(intendedTickLength);
16769 }
16770
16771
16772 /* Stop both clocks */
16773 void
16774 StopClocks ()
16775 {
16776     long lastTickLength;
16777     TimeMark now;
16778
16779     if (!StopClockTimer()) return;
16780     if (!appData.clockMode) return;
16781
16782     GetTimeMark(&now);
16783
16784     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16785     if (WhiteOnMove(forwardMostMove)) {
16786         if(whiteNPS >= 0) lastTickLength = 0;
16787         whiteTimeRemaining -= lastTickLength;
16788         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16789     } else {
16790         if(blackNPS >= 0) lastTickLength = 0;
16791         blackTimeRemaining -= lastTickLength;
16792         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16793     }
16794     CheckFlags();
16795 }
16796
16797 /* Start clock of player on move.  Time may have been reset, so
16798    if clock is already running, stop and restart it. */
16799 void
16800 StartClocks ()
16801 {
16802     (void) StopClockTimer(); /* in case it was running already */
16803     DisplayBothClocks();
16804     if (CheckFlags()) return;
16805
16806     if (!appData.clockMode) return;
16807     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16808
16809     GetTimeMark(&tickStartTM);
16810     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16811       whiteTimeRemaining : blackTimeRemaining);
16812
16813    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16814     whiteNPS = blackNPS = -1;
16815     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16816        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16817         whiteNPS = first.nps;
16818     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16819        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16820         blackNPS = first.nps;
16821     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16822         whiteNPS = second.nps;
16823     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16824         blackNPS = second.nps;
16825     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16826
16827     StartClockTimer(intendedTickLength);
16828 }
16829
16830 char *
16831 TimeString (long ms)
16832 {
16833     long second, minute, hour, day;
16834     char *sign = "";
16835     static char buf[32];
16836
16837     if (ms > 0 && ms <= 9900) {
16838       /* convert milliseconds to tenths, rounding up */
16839       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16840
16841       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16842       return buf;
16843     }
16844
16845     /* convert milliseconds to seconds, rounding up */
16846     /* use floating point to avoid strangeness of integer division
16847        with negative dividends on many machines */
16848     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16849
16850     if (second < 0) {
16851         sign = "-";
16852         second = -second;
16853     }
16854
16855     day = second / (60 * 60 * 24);
16856     second = second % (60 * 60 * 24);
16857     hour = second / (60 * 60);
16858     second = second % (60 * 60);
16859     minute = second / 60;
16860     second = second % 60;
16861
16862     if (day > 0)
16863       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16864               sign, day, hour, minute, second);
16865     else if (hour > 0)
16866       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16867     else
16868       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16869
16870     return buf;
16871 }
16872
16873
16874 /*
16875  * This is necessary because some C libraries aren't ANSI C compliant yet.
16876  */
16877 char *
16878 StrStr (char *string, char *match)
16879 {
16880     int i, length;
16881
16882     length = strlen(match);
16883
16884     for (i = strlen(string) - length; i >= 0; i--, string++)
16885       if (!strncmp(match, string, length))
16886         return string;
16887
16888     return NULL;
16889 }
16890
16891 char *
16892 StrCaseStr (char *string, char *match)
16893 {
16894     int i, j, length;
16895
16896     length = strlen(match);
16897
16898     for (i = strlen(string) - length; i >= 0; i--, string++) {
16899         for (j = 0; j < length; j++) {
16900             if (ToLower(match[j]) != ToLower(string[j]))
16901               break;
16902         }
16903         if (j == length) return string;
16904     }
16905
16906     return NULL;
16907 }
16908
16909 #ifndef _amigados
16910 int
16911 StrCaseCmp (char *s1, char *s2)
16912 {
16913     char c1, c2;
16914
16915     for (;;) {
16916         c1 = ToLower(*s1++);
16917         c2 = ToLower(*s2++);
16918         if (c1 > c2) return 1;
16919         if (c1 < c2) return -1;
16920         if (c1 == NULLCHAR) return 0;
16921     }
16922 }
16923
16924
16925 int
16926 ToLower (int c)
16927 {
16928     return isupper(c) ? tolower(c) : c;
16929 }
16930
16931
16932 int
16933 ToUpper (int c)
16934 {
16935     return islower(c) ? toupper(c) : c;
16936 }
16937 #endif /* !_amigados    */
16938
16939 char *
16940 StrSave (char *s)
16941 {
16942   char *ret;
16943
16944   if ((ret = (char *) malloc(strlen(s) + 1)))
16945     {
16946       safeStrCpy(ret, s, strlen(s)+1);
16947     }
16948   return ret;
16949 }
16950
16951 char *
16952 StrSavePtr (char *s, char **savePtr)
16953 {
16954     if (*savePtr) {
16955         free(*savePtr);
16956     }
16957     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16958       safeStrCpy(*savePtr, s, strlen(s)+1);
16959     }
16960     return(*savePtr);
16961 }
16962
16963 char *
16964 PGNDate ()
16965 {
16966     time_t clock;
16967     struct tm *tm;
16968     char buf[MSG_SIZ];
16969
16970     clock = time((time_t *)NULL);
16971     tm = localtime(&clock);
16972     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16973             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16974     return StrSave(buf);
16975 }
16976
16977
16978 char *
16979 PositionToFEN (int move, char *overrideCastling)
16980 {
16981     int i, j, fromX, fromY, toX, toY;
16982     int whiteToPlay;
16983     char buf[MSG_SIZ];
16984     char *p, *q;
16985     int emptycount;
16986     ChessSquare piece;
16987
16988     whiteToPlay = (gameMode == EditPosition) ?
16989       !blackPlaysFirst : (move % 2 == 0);
16990     p = buf;
16991
16992     /* Piece placement data */
16993     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16994         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16995         emptycount = 0;
16996         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16997             if (boards[move][i][j] == EmptySquare) {
16998                 emptycount++;
16999             } else { ChessSquare piece = boards[move][i][j];
17000                 if (emptycount > 0) {
17001                     if(emptycount<10) /* [HGM] can be >= 10 */
17002                         *p++ = '0' + emptycount;
17003                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17004                     emptycount = 0;
17005                 }
17006                 if(PieceToChar(piece) == '+') {
17007                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17008                     *p++ = '+';
17009                     piece = (ChessSquare)(DEMOTED piece);
17010                 }
17011                 *p++ = PieceToChar(piece);
17012                 if(p[-1] == '~') {
17013                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17014                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17015                     *p++ = '~';
17016                 }
17017             }
17018         }
17019         if (emptycount > 0) {
17020             if(emptycount<10) /* [HGM] can be >= 10 */
17021                 *p++ = '0' + emptycount;
17022             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17023             emptycount = 0;
17024         }
17025         *p++ = '/';
17026     }
17027     *(p - 1) = ' ';
17028
17029     /* [HGM] print Crazyhouse or Shogi holdings */
17030     if( gameInfo.holdingsWidth ) {
17031         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17032         q = p;
17033         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17034             piece = boards[move][i][BOARD_WIDTH-1];
17035             if( piece != EmptySquare )
17036               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17037                   *p++ = PieceToChar(piece);
17038         }
17039         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17040             piece = boards[move][BOARD_HEIGHT-i-1][0];
17041             if( piece != EmptySquare )
17042               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17043                   *p++ = PieceToChar(piece);
17044         }
17045
17046         if( q == p ) *p++ = '-';
17047         *p++ = ']';
17048         *p++ = ' ';
17049     }
17050
17051     /* Active color */
17052     *p++ = whiteToPlay ? 'w' : 'b';
17053     *p++ = ' ';
17054
17055   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17056     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17057   } else {
17058   if(nrCastlingRights) {
17059      q = p;
17060      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17061        /* [HGM] write directly from rights */
17062            if(boards[move][CASTLING][2] != NoRights &&
17063               boards[move][CASTLING][0] != NoRights   )
17064                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17065            if(boards[move][CASTLING][2] != NoRights &&
17066               boards[move][CASTLING][1] != NoRights   )
17067                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17068            if(boards[move][CASTLING][5] != NoRights &&
17069               boards[move][CASTLING][3] != NoRights   )
17070                 *p++ = boards[move][CASTLING][3] + AAA;
17071            if(boards[move][CASTLING][5] != NoRights &&
17072               boards[move][CASTLING][4] != NoRights   )
17073                 *p++ = boards[move][CASTLING][4] + AAA;
17074      } else {
17075
17076         /* [HGM] write true castling rights */
17077         if( nrCastlingRights == 6 ) {
17078             int q, k=0;
17079             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17080                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17081             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17082                  boards[move][CASTLING][2] != NoRights  );
17083             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17084                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17085                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17086                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17087                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17088             }
17089             if(q) *p++ = 'Q';
17090             k = 0;
17091             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17092                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17093             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17094                  boards[move][CASTLING][5] != NoRights  );
17095             if(gameInfo.variant == VariantSChess) {
17096                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17097                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17098                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17099                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17100             }
17101             if(q) *p++ = 'q';
17102         }
17103      }
17104      if (q == p) *p++ = '-'; /* No castling rights */
17105      *p++ = ' ';
17106   }
17107
17108   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17109      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17110     /* En passant target square */
17111     if (move > backwardMostMove) {
17112         fromX = moveList[move - 1][0] - AAA;
17113         fromY = moveList[move - 1][1] - ONE;
17114         toX = moveList[move - 1][2] - AAA;
17115         toY = moveList[move - 1][3] - ONE;
17116         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17117             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17118             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17119             fromX == toX) {
17120             /* 2-square pawn move just happened */
17121             *p++ = toX + AAA;
17122             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17123         } else {
17124             *p++ = '-';
17125         }
17126     } else if(move == backwardMostMove) {
17127         // [HGM] perhaps we should always do it like this, and forget the above?
17128         if((signed char)boards[move][EP_STATUS] >= 0) {
17129             *p++ = boards[move][EP_STATUS] + AAA;
17130             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17131         } else {
17132             *p++ = '-';
17133         }
17134     } else {
17135         *p++ = '-';
17136     }
17137     *p++ = ' ';
17138   }
17139   }
17140
17141     /* [HGM] find reversible plies */
17142     {   int i = 0, j=move;
17143
17144         if (appData.debugMode) { int k;
17145             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17146             for(k=backwardMostMove; k<=forwardMostMove; k++)
17147                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17148
17149         }
17150
17151         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17152         if( j == backwardMostMove ) i += initialRulePlies;
17153         sprintf(p, "%d ", i);
17154         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17155     }
17156     /* Fullmove number */
17157     sprintf(p, "%d", (move / 2) + 1);
17158
17159     return StrSave(buf);
17160 }
17161
17162 Boolean
17163 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17164 {
17165     int i, j;
17166     char *p, c;
17167     int emptycount, virgin[BOARD_FILES];
17168     ChessSquare piece;
17169
17170     p = fen;
17171
17172     /* [HGM] by default clear Crazyhouse holdings, if present */
17173     if(gameInfo.holdingsWidth) {
17174        for(i=0; i<BOARD_HEIGHT; i++) {
17175            board[i][0]             = EmptySquare; /* black holdings */
17176            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17177            board[i][1]             = (ChessSquare) 0; /* black counts */
17178            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17179        }
17180     }
17181
17182     /* Piece placement data */
17183     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17184         j = 0;
17185         for (;;) {
17186             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17187                 if (*p == '/') p++;
17188                 emptycount = gameInfo.boardWidth - j;
17189                 while (emptycount--)
17190                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17191                 break;
17192 #if(BOARD_FILES >= 10)
17193             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17194                 p++; emptycount=10;
17195                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17196                 while (emptycount--)
17197                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17198 #endif
17199             } else if (isdigit(*p)) {
17200                 emptycount = *p++ - '0';
17201                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17202                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17203                 while (emptycount--)
17204                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17205             } else if (*p == '+' || isalpha(*p)) {
17206                 if (j >= gameInfo.boardWidth) return FALSE;
17207                 if(*p=='+') {
17208                     piece = CharToPiece(*++p);
17209                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17210                     piece = (ChessSquare) (PROMOTED piece ); p++;
17211                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17212                 } else piece = CharToPiece(*p++);
17213
17214                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17215                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17216                     piece = (ChessSquare) (PROMOTED piece);
17217                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17218                     p++;
17219                 }
17220                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17221             } else {
17222                 return FALSE;
17223             }
17224         }
17225     }
17226     while (*p == '/' || *p == ' ') p++;
17227
17228     /* [HGM] look for Crazyhouse holdings here */
17229     while(*p==' ') p++;
17230     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17231         if(*p == '[') p++;
17232         if(*p == '-' ) p++; /* empty holdings */ else {
17233             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17234             /* if we would allow FEN reading to set board size, we would   */
17235             /* have to add holdings and shift the board read so far here   */
17236             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17237                 p++;
17238                 if((int) piece >= (int) BlackPawn ) {
17239                     i = (int)piece - (int)BlackPawn;
17240                     i = PieceToNumber((ChessSquare)i);
17241                     if( i >= gameInfo.holdingsSize ) return FALSE;
17242                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17243                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17244                 } else {
17245                     i = (int)piece - (int)WhitePawn;
17246                     i = PieceToNumber((ChessSquare)i);
17247                     if( i >= gameInfo.holdingsSize ) return FALSE;
17248                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17249                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17250                 }
17251             }
17252         }
17253         if(*p == ']') p++;
17254     }
17255
17256     while(*p == ' ') p++;
17257
17258     /* Active color */
17259     c = *p++;
17260     if(appData.colorNickNames) {
17261       if( c == appData.colorNickNames[0] ) c = 'w'; else
17262       if( c == appData.colorNickNames[1] ) c = 'b';
17263     }
17264     switch (c) {
17265       case 'w':
17266         *blackPlaysFirst = FALSE;
17267         break;
17268       case 'b':
17269         *blackPlaysFirst = TRUE;
17270         break;
17271       default:
17272         return FALSE;
17273     }
17274
17275     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17276     /* return the extra info in global variiables             */
17277
17278     /* set defaults in case FEN is incomplete */
17279     board[EP_STATUS] = EP_UNKNOWN;
17280     for(i=0; i<nrCastlingRights; i++ ) {
17281         board[CASTLING][i] =
17282             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17283     }   /* assume possible unless obviously impossible */
17284     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17285     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17286     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17287                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17288     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17289     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17290     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17291                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17292     FENrulePlies = 0;
17293
17294     while(*p==' ') p++;
17295     if(nrCastlingRights) {
17296       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17297       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17298           /* castling indicator present, so default becomes no castlings */
17299           for(i=0; i<nrCastlingRights; i++ ) {
17300                  board[CASTLING][i] = NoRights;
17301           }
17302       }
17303       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17304              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17305              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17306              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17307         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17308
17309         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17310             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17311             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17312         }
17313         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17314             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17315         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17316                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17317         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17318                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17319         switch(c) {
17320           case'K':
17321               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17322               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17323               board[CASTLING][2] = whiteKingFile;
17324               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17325               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17326               break;
17327           case'Q':
17328               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17329               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17330               board[CASTLING][2] = whiteKingFile;
17331               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17332               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17333               break;
17334           case'k':
17335               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17336               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17337               board[CASTLING][5] = blackKingFile;
17338               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17339               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17340               break;
17341           case'q':
17342               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17343               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17344               board[CASTLING][5] = blackKingFile;
17345               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17346               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17347           case '-':
17348               break;
17349           default: /* FRC castlings */
17350               if(c >= 'a') { /* black rights */
17351                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17352                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17353                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17354                   if(i == BOARD_RGHT) break;
17355                   board[CASTLING][5] = i;
17356                   c -= AAA;
17357                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17358                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17359                   if(c > i)
17360                       board[CASTLING][3] = c;
17361                   else
17362                       board[CASTLING][4] = c;
17363               } else { /* white rights */
17364                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17365                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17366                     if(board[0][i] == WhiteKing) break;
17367                   if(i == BOARD_RGHT) break;
17368                   board[CASTLING][2] = i;
17369                   c -= AAA - 'a' + 'A';
17370                   if(board[0][c] >= WhiteKing) break;
17371                   if(c > i)
17372                       board[CASTLING][0] = c;
17373                   else
17374                       board[CASTLING][1] = c;
17375               }
17376         }
17377       }
17378       for(i=0; i<nrCastlingRights; i++)
17379         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17380       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17381     if (appData.debugMode) {
17382         fprintf(debugFP, "FEN castling rights:");
17383         for(i=0; i<nrCastlingRights; i++)
17384         fprintf(debugFP, " %d", board[CASTLING][i]);
17385         fprintf(debugFP, "\n");
17386     }
17387
17388       while(*p==' ') p++;
17389     }
17390
17391     /* read e.p. field in games that know e.p. capture */
17392     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17393        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17394       if(*p=='-') {
17395         p++; board[EP_STATUS] = EP_NONE;
17396       } else {
17397          char c = *p++ - AAA;
17398
17399          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17400          if(*p >= '0' && *p <='9') p++;
17401          board[EP_STATUS] = c;
17402       }
17403     }
17404
17405
17406     if(sscanf(p, "%d", &i) == 1) {
17407         FENrulePlies = i; /* 50-move ply counter */
17408         /* (The move number is still ignored)    */
17409     }
17410
17411     return TRUE;
17412 }
17413
17414 void
17415 EditPositionPasteFEN (char *fen)
17416 {
17417   if (fen != NULL) {
17418     Board initial_position;
17419
17420     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17421       DisplayError(_("Bad FEN position in clipboard"), 0);
17422       return ;
17423     } else {
17424       int savedBlackPlaysFirst = blackPlaysFirst;
17425       EditPositionEvent();
17426       blackPlaysFirst = savedBlackPlaysFirst;
17427       CopyBoard(boards[0], initial_position);
17428       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17429       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17430       DisplayBothClocks();
17431       DrawPosition(FALSE, boards[currentMove]);
17432     }
17433   }
17434 }
17435
17436 static char cseq[12] = "\\   ";
17437
17438 Boolean
17439 set_cont_sequence (char *new_seq)
17440 {
17441     int len;
17442     Boolean ret;
17443
17444     // handle bad attempts to set the sequence
17445         if (!new_seq)
17446                 return 0; // acceptable error - no debug
17447
17448     len = strlen(new_seq);
17449     ret = (len > 0) && (len < sizeof(cseq));
17450     if (ret)
17451       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17452     else if (appData.debugMode)
17453       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17454     return ret;
17455 }
17456
17457 /*
17458     reformat a source message so words don't cross the width boundary.  internal
17459     newlines are not removed.  returns the wrapped size (no null character unless
17460     included in source message).  If dest is NULL, only calculate the size required
17461     for the dest buffer.  lp argument indicats line position upon entry, and it's
17462     passed back upon exit.
17463 */
17464 int
17465 wrap (char *dest, char *src, int count, int width, int *lp)
17466 {
17467     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17468
17469     cseq_len = strlen(cseq);
17470     old_line = line = *lp;
17471     ansi = len = clen = 0;
17472
17473     for (i=0; i < count; i++)
17474     {
17475         if (src[i] == '\033')
17476             ansi = 1;
17477
17478         // if we hit the width, back up
17479         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17480         {
17481             // store i & len in case the word is too long
17482             old_i = i, old_len = len;
17483
17484             // find the end of the last word
17485             while (i && src[i] != ' ' && src[i] != '\n')
17486             {
17487                 i--;
17488                 len--;
17489             }
17490
17491             // word too long?  restore i & len before splitting it
17492             if ((old_i-i+clen) >= width)
17493             {
17494                 i = old_i;
17495                 len = old_len;
17496             }
17497
17498             // extra space?
17499             if (i && src[i-1] == ' ')
17500                 len--;
17501
17502             if (src[i] != ' ' && src[i] != '\n')
17503             {
17504                 i--;
17505                 if (len)
17506                     len--;
17507             }
17508
17509             // now append the newline and continuation sequence
17510             if (dest)
17511                 dest[len] = '\n';
17512             len++;
17513             if (dest)
17514                 strncpy(dest+len, cseq, cseq_len);
17515             len += cseq_len;
17516             line = cseq_len;
17517             clen = cseq_len;
17518             continue;
17519         }
17520
17521         if (dest)
17522             dest[len] = src[i];
17523         len++;
17524         if (!ansi)
17525             line++;
17526         if (src[i] == '\n')
17527             line = 0;
17528         if (src[i] == 'm')
17529             ansi = 0;
17530     }
17531     if (dest && appData.debugMode)
17532     {
17533         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17534             count, width, line, len, *lp);
17535         show_bytes(debugFP, src, count);
17536         fprintf(debugFP, "\ndest: ");
17537         show_bytes(debugFP, dest, len);
17538         fprintf(debugFP, "\n");
17539     }
17540     *lp = dest ? line : old_line;
17541
17542     return len;
17543 }
17544
17545 // [HGM] vari: routines for shelving variations
17546 Boolean modeRestore = FALSE;
17547
17548 void
17549 PushInner (int firstMove, int lastMove)
17550 {
17551         int i, j, nrMoves = lastMove - firstMove;
17552
17553         // push current tail of game on stack
17554         savedResult[storedGames] = gameInfo.result;
17555         savedDetails[storedGames] = gameInfo.resultDetails;
17556         gameInfo.resultDetails = NULL;
17557         savedFirst[storedGames] = firstMove;
17558         savedLast [storedGames] = lastMove;
17559         savedFramePtr[storedGames] = framePtr;
17560         framePtr -= nrMoves; // reserve space for the boards
17561         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17562             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17563             for(j=0; j<MOVE_LEN; j++)
17564                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17565             for(j=0; j<2*MOVE_LEN; j++)
17566                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17567             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17568             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17569             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17570             pvInfoList[firstMove+i-1].depth = 0;
17571             commentList[framePtr+i] = commentList[firstMove+i];
17572             commentList[firstMove+i] = NULL;
17573         }
17574
17575         storedGames++;
17576         forwardMostMove = firstMove; // truncate game so we can start variation
17577 }
17578
17579 void
17580 PushTail (int firstMove, int lastMove)
17581 {
17582         if(appData.icsActive) { // only in local mode
17583                 forwardMostMove = currentMove; // mimic old ICS behavior
17584                 return;
17585         }
17586         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17587
17588         PushInner(firstMove, lastMove);
17589         if(storedGames == 1) GreyRevert(FALSE);
17590         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17591 }
17592
17593 void
17594 PopInner (Boolean annotate)
17595 {
17596         int i, j, nrMoves;
17597         char buf[8000], moveBuf[20];
17598
17599         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17600         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17601         nrMoves = savedLast[storedGames] - currentMove;
17602         if(annotate) {
17603                 int cnt = 10;
17604                 if(!WhiteOnMove(currentMove))
17605                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17606                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17607                 for(i=currentMove; i<forwardMostMove; i++) {
17608                         if(WhiteOnMove(i))
17609                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17610                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17611                         strcat(buf, moveBuf);
17612                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17613                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17614                 }
17615                 strcat(buf, ")");
17616         }
17617         for(i=1; i<=nrMoves; i++) { // copy last variation back
17618             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17619             for(j=0; j<MOVE_LEN; j++)
17620                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17621             for(j=0; j<2*MOVE_LEN; j++)
17622                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17623             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17624             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17625             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17626             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17627             commentList[currentMove+i] = commentList[framePtr+i];
17628             commentList[framePtr+i] = NULL;
17629         }
17630         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17631         framePtr = savedFramePtr[storedGames];
17632         gameInfo.result = savedResult[storedGames];
17633         if(gameInfo.resultDetails != NULL) {
17634             free(gameInfo.resultDetails);
17635       }
17636         gameInfo.resultDetails = savedDetails[storedGames];
17637         forwardMostMove = currentMove + nrMoves;
17638 }
17639
17640 Boolean
17641 PopTail (Boolean annotate)
17642 {
17643         if(appData.icsActive) return FALSE; // only in local mode
17644         if(!storedGames) return FALSE; // sanity
17645         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17646
17647         PopInner(annotate);
17648         if(currentMove < forwardMostMove) ForwardEvent(); else
17649         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17650
17651         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17652         return TRUE;
17653 }
17654
17655 void
17656 CleanupTail ()
17657 {       // remove all shelved variations
17658         int i;
17659         for(i=0; i<storedGames; i++) {
17660             if(savedDetails[i])
17661                 free(savedDetails[i]);
17662             savedDetails[i] = NULL;
17663         }
17664         for(i=framePtr; i<MAX_MOVES; i++) {
17665                 if(commentList[i]) free(commentList[i]);
17666                 commentList[i] = NULL;
17667         }
17668         framePtr = MAX_MOVES-1;
17669         storedGames = 0;
17670 }
17671
17672 void
17673 LoadVariation (int index, char *text)
17674 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17675         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17676         int level = 0, move;
17677
17678         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17679         // first find outermost bracketing variation
17680         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17681             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17682                 if(*p == '{') wait = '}'; else
17683                 if(*p == '[') wait = ']'; else
17684                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17685                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17686             }
17687             if(*p == wait) wait = NULLCHAR; // closing ]} found
17688             p++;
17689         }
17690         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17691         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17692         end[1] = NULLCHAR; // clip off comment beyond variation
17693         ToNrEvent(currentMove-1);
17694         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17695         // kludge: use ParsePV() to append variation to game
17696         move = currentMove;
17697         ParsePV(start, TRUE, TRUE);
17698         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17699         ClearPremoveHighlights();
17700         CommentPopDown();
17701         ToNrEvent(currentMove+1);
17702 }
17703
17704 void
17705 LoadTheme ()
17706 {
17707     char *p, *q, buf[MSG_SIZ];
17708     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17709         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17710         ParseArgsFromString(buf);
17711         ActivateTheme(TRUE); // also redo colors
17712         return;
17713     }
17714     p = nickName;
17715     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17716     {
17717         int len;
17718         q = appData.themeNames;
17719         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17720       if(appData.useBitmaps) {
17721         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17722                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17723                 appData.liteBackTextureMode,
17724                 appData.darkBackTextureMode );
17725       } else {
17726         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17727                 Col2Text(2),   // lightSquareColor
17728                 Col2Text(3) ); // darkSquareColor
17729       }
17730       if(appData.useBorder) {
17731         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17732                 appData.border);
17733       } else {
17734         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17735       }
17736       if(appData.useFont) {
17737         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17738                 appData.renderPiecesWithFont,
17739                 appData.fontToPieceTable,
17740                 Col2Text(9),    // appData.fontBackColorWhite
17741                 Col2Text(10) ); // appData.fontForeColorBlack
17742       } else {
17743         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17744                 appData.pieceDirectory);
17745         if(!appData.pieceDirectory[0])
17746           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17747                 Col2Text(0),   // whitePieceColor
17748                 Col2Text(1) ); // blackPieceColor
17749       }
17750       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17751                 Col2Text(4),   // highlightSquareColor
17752                 Col2Text(5) ); // premoveHighlightColor
17753         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17754         if(insert != q) insert[-1] = NULLCHAR;
17755         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17756         if(q)   free(q);
17757     }
17758     ActivateTheme(FALSE);
17759 }