new version number for developer release
[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, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
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       if(!twoBoards) 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     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     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4688     fprintf(debugFP, "moveNum = %d\n", moveNum);
4689     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4690     setbuf(debugFP, NULL);
4691   }
4692         if (moveNum <= backwardMostMove) {
4693             /* We don't know what the board looked like before
4694                this move.  Punt. */
4695           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4696             strcat(parseList[moveNum - 1], " ");
4697             strcat(parseList[moveNum - 1], elapsed_time);
4698             moveList[moveNum - 1][0] = NULLCHAR;
4699         } else if (strcmp(move_str, "none") == 0) {
4700             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4701             /* Again, we don't know what the board looked like;
4702                this is really the start of the game. */
4703             parseList[moveNum - 1][0] = NULLCHAR;
4704             moveList[moveNum - 1][0] = NULLCHAR;
4705             backwardMostMove = moveNum;
4706             startedFromSetupPosition = TRUE;
4707             fromX = fromY = toX = toY = -1;
4708         } else {
4709           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4710           //                 So we parse the long-algebraic move string in stead of the SAN move
4711           int valid; char buf[MSG_SIZ], *prom;
4712
4713           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4714                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4715           // str looks something like "Q/a1-a2"; kill the slash
4716           if(str[1] == '/')
4717             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4718           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4719           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4720                 strcat(buf, prom); // long move lacks promo specification!
4721           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4722                 if(appData.debugMode)
4723                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4724                 safeStrCpy(move_str, buf, MSG_SIZ);
4725           }
4726           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4727                                 &fromX, &fromY, &toX, &toY, &promoChar)
4728                || ParseOneMove(buf, moveNum - 1, &moveType,
4729                                 &fromX, &fromY, &toX, &toY, &promoChar);
4730           // end of long SAN patch
4731           if (valid) {
4732             (void) CoordsToAlgebraic(boards[moveNum - 1],
4733                                      PosFlags(moveNum - 1),
4734                                      fromY, fromX, toY, toX, promoChar,
4735                                      parseList[moveNum-1]);
4736             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4737               case MT_NONE:
4738               case MT_STALEMATE:
4739               default:
4740                 break;
4741               case MT_CHECK:
4742                 if(gameInfo.variant != VariantShogi)
4743                     strcat(parseList[moveNum - 1], "+");
4744                 break;
4745               case MT_CHECKMATE:
4746               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4747                 strcat(parseList[moveNum - 1], "#");
4748                 break;
4749             }
4750             strcat(parseList[moveNum - 1], " ");
4751             strcat(parseList[moveNum - 1], elapsed_time);
4752             /* currentMoveString is set as a side-effect of ParseOneMove */
4753             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4754             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4755             strcat(moveList[moveNum - 1], "\n");
4756
4757             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4758                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4759               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4760                 ChessSquare old, new = boards[moveNum][k][j];
4761                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4762                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4763                   if(old == new) continue;
4764                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4765                   else if(new == WhiteWazir || new == BlackWazir) {
4766                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4767                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4768                       else boards[moveNum][k][j] = old; // preserve type of Gold
4769                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4770                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4771               }
4772           } else {
4773             /* Move from ICS was illegal!?  Punt. */
4774             if (appData.debugMode) {
4775               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4776               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4777             }
4778             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4779             strcat(parseList[moveNum - 1], " ");
4780             strcat(parseList[moveNum - 1], elapsed_time);
4781             moveList[moveNum - 1][0] = NULLCHAR;
4782             fromX = fromY = toX = toY = -1;
4783           }
4784         }
4785   if (appData.debugMode) {
4786     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4787     setbuf(debugFP, NULL);
4788   }
4789
4790 #if ZIPPY
4791         /* Send move to chess program (BEFORE animating it). */
4792         if (appData.zippyPlay && !newGame && newMove &&
4793            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4794
4795             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4796                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4797                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4798                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4799                             move_str);
4800                     DisplayError(str, 0);
4801                 } else {
4802                     if (first.sendTime) {
4803                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4804                     }
4805                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4806                     if (firstMove && !bookHit) {
4807                         firstMove = FALSE;
4808                         if (first.useColors) {
4809                           SendToProgram(gameMode == IcsPlayingWhite ?
4810                                         "white\ngo\n" :
4811                                         "black\ngo\n", &first);
4812                         } else {
4813                           SendToProgram("go\n", &first);
4814                         }
4815                         first.maybeThinking = TRUE;
4816                     }
4817                 }
4818             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4819               if (moveList[moveNum - 1][0] == NULLCHAR) {
4820                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4821                 DisplayError(str, 0);
4822               } else {
4823                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4824                 SendMoveToProgram(moveNum - 1, &first);
4825               }
4826             }
4827         }
4828 #endif
4829     }
4830
4831     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4832         /* If move comes from a remote source, animate it.  If it
4833            isn't remote, it will have already been animated. */
4834         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4835             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4836         }
4837         if (!pausing && appData.highlightLastMove) {
4838             SetHighlights(fromX, fromY, toX, toY);
4839         }
4840     }
4841
4842     /* Start the clocks */
4843     whiteFlag = blackFlag = FALSE;
4844     appData.clockMode = !(basetime == 0 && increment == 0);
4845     if (ticking == 0) {
4846       ics_clock_paused = TRUE;
4847       StopClocks();
4848     } else if (ticking == 1) {
4849       ics_clock_paused = FALSE;
4850     }
4851     if (gameMode == IcsIdle ||
4852         relation == RELATION_OBSERVING_STATIC ||
4853         relation == RELATION_EXAMINING ||
4854         ics_clock_paused)
4855       DisplayBothClocks();
4856     else
4857       StartClocks();
4858
4859     /* Display opponents and material strengths */
4860     if (gameInfo.variant != VariantBughouse &&
4861         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4862         if (tinyLayout || smallLayout) {
4863             if(gameInfo.variant == VariantNormal)
4864               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4865                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4866                     basetime, increment);
4867             else
4868               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4869                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4870                     basetime, increment, (int) gameInfo.variant);
4871         } else {
4872             if(gameInfo.variant == VariantNormal)
4873               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4874                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4875                     basetime, increment);
4876             else
4877               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4878                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4879                     basetime, increment, VariantName(gameInfo.variant));
4880         }
4881         DisplayTitle(str);
4882   if (appData.debugMode) {
4883     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4884   }
4885     }
4886
4887
4888     /* Display the board */
4889     if (!pausing && !appData.noGUI) {
4890
4891       if (appData.premove)
4892           if (!gotPremove ||
4893              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4894              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4895               ClearPremoveHighlights();
4896
4897       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4898         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4899       DrawPosition(j, boards[currentMove]);
4900
4901       DisplayMove(moveNum - 1);
4902       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4903             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4904               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4905         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4906       }
4907     }
4908
4909     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4910 #if ZIPPY
4911     if(bookHit) { // [HGM] book: simulate book reply
4912         static char bookMove[MSG_SIZ]; // a bit generous?
4913
4914         programStats.nodes = programStats.depth = programStats.time =
4915         programStats.score = programStats.got_only_move = 0;
4916         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4917
4918         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4919         strcat(bookMove, bookHit);
4920         HandleMachineMove(bookMove, &first);
4921     }
4922 #endif
4923 }
4924
4925 void
4926 GetMoveListEvent ()
4927 {
4928     char buf[MSG_SIZ];
4929     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4930         ics_getting_history = H_REQUESTED;
4931         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4932         SendToICS(buf);
4933     }
4934 }
4935
4936 void
4937 SendToBoth (char *msg)
4938 {   // to make it easy to keep two engines in step in dual analysis
4939     SendToProgram(msg, &first);
4940     if(second.analyzing) SendToProgram(msg, &second);
4941 }
4942
4943 void
4944 AnalysisPeriodicEvent (int force)
4945 {
4946     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4947          && !force) || !appData.periodicUpdates)
4948       return;
4949
4950     /* Send . command to Crafty to collect stats */
4951     SendToBoth(".\n");
4952
4953     /* Don't send another until we get a response (this makes
4954        us stop sending to old Crafty's which don't understand
4955        the "." command (sending illegal cmds resets node count & time,
4956        which looks bad)) */
4957     programStats.ok_to_send = 0;
4958 }
4959
4960 void
4961 ics_update_width (int new_width)
4962 {
4963         ics_printf("set width %d\n", new_width);
4964 }
4965
4966 void
4967 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4968 {
4969     char buf[MSG_SIZ];
4970
4971     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4972         // null move in variant where engine does not understand it (for analysis purposes)
4973         SendBoard(cps, moveNum + 1); // send position after move in stead.
4974         return;
4975     }
4976     if (cps->useUsermove) {
4977       SendToProgram("usermove ", cps);
4978     }
4979     if (cps->useSAN) {
4980       char *space;
4981       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4982         int len = space - parseList[moveNum];
4983         memcpy(buf, parseList[moveNum], len);
4984         buf[len++] = '\n';
4985         buf[len] = NULLCHAR;
4986       } else {
4987         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4988       }
4989       SendToProgram(buf, cps);
4990     } else {
4991       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4992         AlphaRank(moveList[moveNum], 4);
4993         SendToProgram(moveList[moveNum], cps);
4994         AlphaRank(moveList[moveNum], 4); // and back
4995       } else
4996       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4997        * the engine. It would be nice to have a better way to identify castle
4998        * moves here. */
4999       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5000                                                                          && cps->useOOCastle) {
5001         int fromX = moveList[moveNum][0] - AAA;
5002         int fromY = moveList[moveNum][1] - ONE;
5003         int toX = moveList[moveNum][2] - AAA;
5004         int toY = moveList[moveNum][3] - ONE;
5005         if((boards[moveNum][fromY][fromX] == WhiteKing
5006             && boards[moveNum][toY][toX] == WhiteRook)
5007            || (boards[moveNum][fromY][fromX] == BlackKing
5008                && boards[moveNum][toY][toX] == BlackRook)) {
5009           if(toX > fromX) SendToProgram("O-O\n", cps);
5010           else SendToProgram("O-O-O\n", cps);
5011         }
5012         else SendToProgram(moveList[moveNum], cps);
5013       } else
5014       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5015         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5016           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5017           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5018                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5019         } else
5020           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5021                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5022         SendToProgram(buf, cps);
5023       }
5024       else SendToProgram(moveList[moveNum], cps);
5025       /* End of additions by Tord */
5026     }
5027
5028     /* [HGM] setting up the opening has brought engine in force mode! */
5029     /*       Send 'go' if we are in a mode where machine should play. */
5030     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5031         (gameMode == TwoMachinesPlay   ||
5032 #if ZIPPY
5033          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5034 #endif
5035          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5036         SendToProgram("go\n", cps);
5037   if (appData.debugMode) {
5038     fprintf(debugFP, "(extra)\n");
5039   }
5040     }
5041     setboardSpoiledMachineBlack = 0;
5042 }
5043
5044 void
5045 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5046 {
5047     char user_move[MSG_SIZ];
5048     char suffix[4];
5049
5050     if(gameInfo.variant == VariantSChess && promoChar) {
5051         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5052         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5053     } else suffix[0] = NULLCHAR;
5054
5055     switch (moveType) {
5056       default:
5057         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5058                 (int)moveType, fromX, fromY, toX, toY);
5059         DisplayError(user_move + strlen("say "), 0);
5060         break;
5061       case WhiteKingSideCastle:
5062       case BlackKingSideCastle:
5063       case WhiteQueenSideCastleWild:
5064       case BlackQueenSideCastleWild:
5065       /* PUSH Fabien */
5066       case WhiteHSideCastleFR:
5067       case BlackHSideCastleFR:
5068       /* POP Fabien */
5069         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5070         break;
5071       case WhiteQueenSideCastle:
5072       case BlackQueenSideCastle:
5073       case WhiteKingSideCastleWild:
5074       case BlackKingSideCastleWild:
5075       /* PUSH Fabien */
5076       case WhiteASideCastleFR:
5077       case BlackASideCastleFR:
5078       /* POP Fabien */
5079         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5080         break;
5081       case WhiteNonPromotion:
5082       case BlackNonPromotion:
5083         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5084         break;
5085       case WhitePromotion:
5086       case BlackPromotion:
5087         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5088           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5089                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5090                 PieceToChar(WhiteFerz));
5091         else if(gameInfo.variant == VariantGreat)
5092           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5093                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5094                 PieceToChar(WhiteMan));
5095         else
5096           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5097                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5098                 promoChar);
5099         break;
5100       case WhiteDrop:
5101       case BlackDrop:
5102       drop:
5103         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5104                  ToUpper(PieceToChar((ChessSquare) fromX)),
5105                  AAA + toX, ONE + toY);
5106         break;
5107       case IllegalMove:  /* could be a variant we don't quite understand */
5108         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5109       case NormalMove:
5110       case WhiteCapturesEnPassant:
5111       case BlackCapturesEnPassant:
5112         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5113                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5114         break;
5115     }
5116     SendToICS(user_move);
5117     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5118         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5119 }
5120
5121 void
5122 UploadGameEvent ()
5123 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5124     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5125     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5126     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5127       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5128       return;
5129     }
5130     if(gameMode != IcsExamining) { // is this ever not the case?
5131         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5132
5133         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5134           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5135         } else { // on FICS we must first go to general examine mode
5136           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5137         }
5138         if(gameInfo.variant != VariantNormal) {
5139             // try figure out wild number, as xboard names are not always valid on ICS
5140             for(i=1; i<=36; i++) {
5141               snprintf(buf, MSG_SIZ, "wild/%d", i);
5142                 if(StringToVariant(buf) == gameInfo.variant) break;
5143             }
5144             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5145             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5146             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5147         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5148         SendToICS(ics_prefix);
5149         SendToICS(buf);
5150         if(startedFromSetupPosition || backwardMostMove != 0) {
5151           fen = PositionToFEN(backwardMostMove, NULL);
5152           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5153             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5154             SendToICS(buf);
5155           } else { // FICS: everything has to set by separate bsetup commands
5156             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5157             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5158             SendToICS(buf);
5159             if(!WhiteOnMove(backwardMostMove)) {
5160                 SendToICS("bsetup tomove black\n");
5161             }
5162             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5163             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5164             SendToICS(buf);
5165             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5166             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5167             SendToICS(buf);
5168             i = boards[backwardMostMove][EP_STATUS];
5169             if(i >= 0) { // set e.p.
5170               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5171                 SendToICS(buf);
5172             }
5173             bsetup++;
5174           }
5175         }
5176       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5177             SendToICS("bsetup done\n"); // switch to normal examining.
5178     }
5179     for(i = backwardMostMove; i<last; i++) {
5180         char buf[20];
5181         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5182         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5183             int len = strlen(moveList[i]);
5184             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5185             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5186         }
5187         SendToICS(buf);
5188     }
5189     SendToICS(ics_prefix);
5190     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5191 }
5192
5193 void
5194 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5195 {
5196     if (rf == DROP_RANK) {
5197       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5198       sprintf(move, "%c@%c%c\n",
5199                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5200     } else {
5201         if (promoChar == 'x' || promoChar == NULLCHAR) {
5202           sprintf(move, "%c%c%c%c\n",
5203                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5204         } else {
5205             sprintf(move, "%c%c%c%c%c\n",
5206                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5207         }
5208     }
5209 }
5210
5211 void
5212 ProcessICSInitScript (FILE *f)
5213 {
5214     char buf[MSG_SIZ];
5215
5216     while (fgets(buf, MSG_SIZ, f)) {
5217         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5218     }
5219
5220     fclose(f);
5221 }
5222
5223
5224 static int lastX, lastY, selectFlag, dragging;
5225
5226 void
5227 Sweep (int step)
5228 {
5229     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5230     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5231     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5232     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5233     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5234     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5235     do {
5236         promoSweep -= step;
5237         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5238         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5239         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5240         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5241         if(!step) step = -1;
5242     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5243             appData.testLegality && (promoSweep == king ||
5244             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5245     if(toX >= 0) {
5246         int victim = boards[currentMove][toY][toX];
5247         boards[currentMove][toY][toX] = promoSweep;
5248         DrawPosition(FALSE, boards[currentMove]);
5249         boards[currentMove][toY][toX] = victim;
5250     } else
5251     ChangeDragPiece(promoSweep);
5252 }
5253
5254 int
5255 PromoScroll (int x, int y)
5256 {
5257   int step = 0;
5258
5259   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5260   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5261   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5262   if(!step) return FALSE;
5263   lastX = x; lastY = y;
5264   if((promoSweep < BlackPawn) == flipView) step = -step;
5265   if(step > 0) selectFlag = 1;
5266   if(!selectFlag) Sweep(step);
5267   return FALSE;
5268 }
5269
5270 void
5271 NextPiece (int step)
5272 {
5273     ChessSquare piece = boards[currentMove][toY][toX];
5274     do {
5275         pieceSweep -= step;
5276         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5277         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5278         if(!step) step = -1;
5279     } while(PieceToChar(pieceSweep) == '.');
5280     boards[currentMove][toY][toX] = pieceSweep;
5281     DrawPosition(FALSE, boards[currentMove]);
5282     boards[currentMove][toY][toX] = piece;
5283 }
5284 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5285 void
5286 AlphaRank (char *move, int n)
5287 {
5288 //    char *p = move, c; int x, y;
5289
5290     if (appData.debugMode) {
5291         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5292     }
5293
5294     if(move[1]=='*' &&
5295        move[2]>='0' && move[2]<='9' &&
5296        move[3]>='a' && move[3]<='x'    ) {
5297         move[1] = '@';
5298         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5299         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5300     } else
5301     if(move[0]>='0' && move[0]<='9' &&
5302        move[1]>='a' && move[1]<='x' &&
5303        move[2]>='0' && move[2]<='9' &&
5304        move[3]>='a' && move[3]<='x'    ) {
5305         /* input move, Shogi -> normal */
5306         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5307         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5308         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5309         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5310     } else
5311     if(move[1]=='@' &&
5312        move[3]>='0' && move[3]<='9' &&
5313        move[2]>='a' && move[2]<='x'    ) {
5314         move[1] = '*';
5315         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5316         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5317     } else
5318     if(
5319        move[0]>='a' && move[0]<='x' &&
5320        move[3]>='0' && move[3]<='9' &&
5321        move[2]>='a' && move[2]<='x'    ) {
5322          /* output move, normal -> Shogi */
5323         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5324         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5325         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5326         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5327         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5328     }
5329     if (appData.debugMode) {
5330         fprintf(debugFP, "   out = '%s'\n", move);
5331     }
5332 }
5333
5334 char yy_textstr[8000];
5335
5336 /* Parser for moves from gnuchess, ICS, or user typein box */
5337 Boolean
5338 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5339 {
5340     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5341
5342     switch (*moveType) {
5343       case WhitePromotion:
5344       case BlackPromotion:
5345       case WhiteNonPromotion:
5346       case BlackNonPromotion:
5347       case NormalMove:
5348       case WhiteCapturesEnPassant:
5349       case BlackCapturesEnPassant:
5350       case WhiteKingSideCastle:
5351       case WhiteQueenSideCastle:
5352       case BlackKingSideCastle:
5353       case BlackQueenSideCastle:
5354       case WhiteKingSideCastleWild:
5355       case WhiteQueenSideCastleWild:
5356       case BlackKingSideCastleWild:
5357       case BlackQueenSideCastleWild:
5358       /* Code added by Tord: */
5359       case WhiteHSideCastleFR:
5360       case WhiteASideCastleFR:
5361       case BlackHSideCastleFR:
5362       case BlackASideCastleFR:
5363       /* End of code added by Tord */
5364       case IllegalMove:         /* bug or odd chess variant */
5365         *fromX = currentMoveString[0] - AAA;
5366         *fromY = currentMoveString[1] - ONE;
5367         *toX = currentMoveString[2] - AAA;
5368         *toY = currentMoveString[3] - ONE;
5369         *promoChar = currentMoveString[4];
5370         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5371             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5372     if (appData.debugMode) {
5373         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5374     }
5375             *fromX = *fromY = *toX = *toY = 0;
5376             return FALSE;
5377         }
5378         if (appData.testLegality) {
5379           return (*moveType != IllegalMove);
5380         } else {
5381           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5382                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5383         }
5384
5385       case WhiteDrop:
5386       case BlackDrop:
5387         *fromX = *moveType == WhiteDrop ?
5388           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5389           (int) CharToPiece(ToLower(currentMoveString[0]));
5390         *fromY = DROP_RANK;
5391         *toX = currentMoveString[2] - AAA;
5392         *toY = currentMoveString[3] - ONE;
5393         *promoChar = NULLCHAR;
5394         return TRUE;
5395
5396       case AmbiguousMove:
5397       case ImpossibleMove:
5398       case EndOfFile:
5399       case ElapsedTime:
5400       case Comment:
5401       case PGNTag:
5402       case NAG:
5403       case WhiteWins:
5404       case BlackWins:
5405       case GameIsDrawn:
5406       default:
5407     if (appData.debugMode) {
5408         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5409     }
5410         /* bug? */
5411         *fromX = *fromY = *toX = *toY = 0;
5412         *promoChar = NULLCHAR;
5413         return FALSE;
5414     }
5415 }
5416
5417 Boolean pushed = FALSE;
5418 char *lastParseAttempt;
5419
5420 void
5421 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5422 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5423   int fromX, fromY, toX, toY; char promoChar;
5424   ChessMove moveType;
5425   Boolean valid;
5426   int nr = 0;
5427
5428   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5429   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5430     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5431     pushed = TRUE;
5432   }
5433   endPV = forwardMostMove;
5434   do {
5435     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5436     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5437     lastParseAttempt = pv;
5438     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5439     if(!valid && nr == 0 &&
5440        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5441         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5442         // Hande case where played move is different from leading PV move
5443         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5444         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5445         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5446         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5447           endPV += 2; // if position different, keep this
5448           moveList[endPV-1][0] = fromX + AAA;
5449           moveList[endPV-1][1] = fromY + ONE;
5450           moveList[endPV-1][2] = toX + AAA;
5451           moveList[endPV-1][3] = toY + ONE;
5452           parseList[endPV-1][0] = NULLCHAR;
5453           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5454         }
5455       }
5456     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5457     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5458     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5459     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5460         valid++; // allow comments in PV
5461         continue;
5462     }
5463     nr++;
5464     if(endPV+1 > framePtr) break; // no space, truncate
5465     if(!valid) break;
5466     endPV++;
5467     CopyBoard(boards[endPV], boards[endPV-1]);
5468     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5469     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5470     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5471     CoordsToAlgebraic(boards[endPV - 1],
5472                              PosFlags(endPV - 1),
5473                              fromY, fromX, toY, toX, promoChar,
5474                              parseList[endPV - 1]);
5475   } while(valid);
5476   if(atEnd == 2) return; // used hidden, for PV conversion
5477   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5478   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5479   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5480                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5481   DrawPosition(TRUE, boards[currentMove]);
5482 }
5483
5484 int
5485 MultiPV (ChessProgramState *cps)
5486 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5487         int i;
5488         for(i=0; i<cps->nrOptions; i++)
5489             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5490                 return i;
5491         return -1;
5492 }
5493
5494 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5495
5496 Boolean
5497 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5498 {
5499         int startPV, multi, lineStart, origIndex = index;
5500         char *p, buf2[MSG_SIZ];
5501         ChessProgramState *cps = (pane ? &second : &first);
5502
5503         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5504         lastX = x; lastY = y;
5505         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5506         lineStart = startPV = index;
5507         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5508         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5509         index = startPV;
5510         do{ while(buf[index] && buf[index] != '\n') index++;
5511         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5512         buf[index] = 0;
5513         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5514                 int n = cps->option[multi].value;
5515                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5516                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5517                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5518                 cps->option[multi].value = n;
5519                 *start = *end = 0;
5520                 return FALSE;
5521         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5522                 ExcludeClick(origIndex - lineStart);
5523                 return FALSE;
5524         }
5525         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5526         *start = startPV; *end = index-1;
5527         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5528         return TRUE;
5529 }
5530
5531 char *
5532 PvToSAN (char *pv)
5533 {
5534         static char buf[10*MSG_SIZ];
5535         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5536         *buf = NULLCHAR;
5537         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5538         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5539         for(i = forwardMostMove; i<endPV; i++){
5540             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5541             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5542             k += strlen(buf+k);
5543         }
5544         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5545         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5546         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5547         endPV = savedEnd;
5548         return buf;
5549 }
5550
5551 Boolean
5552 LoadPV (int x, int y)
5553 { // called on right mouse click to load PV
5554   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5555   lastX = x; lastY = y;
5556   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5557   extendGame = FALSE;
5558   return TRUE;
5559 }
5560
5561 void
5562 UnLoadPV ()
5563 {
5564   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5565   if(endPV < 0) return;
5566   if(appData.autoCopyPV) CopyFENToClipboard();
5567   endPV = -1;
5568   if(extendGame && currentMove > forwardMostMove) {
5569         Boolean saveAnimate = appData.animate;
5570         if(pushed) {
5571             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5572                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5573             } else storedGames--; // abandon shelved tail of original game
5574         }
5575         pushed = FALSE;
5576         forwardMostMove = currentMove;
5577         currentMove = oldFMM;
5578         appData.animate = FALSE;
5579         ToNrEvent(forwardMostMove);
5580         appData.animate = saveAnimate;
5581   }
5582   currentMove = forwardMostMove;
5583   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5584   ClearPremoveHighlights();
5585   DrawPosition(TRUE, boards[currentMove]);
5586 }
5587
5588 void
5589 MovePV (int x, int y, int h)
5590 { // step through PV based on mouse coordinates (called on mouse move)
5591   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5592
5593   // we must somehow check if right button is still down (might be released off board!)
5594   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5595   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5596   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5597   if(!step) return;
5598   lastX = x; lastY = y;
5599
5600   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5601   if(endPV < 0) return;
5602   if(y < margin) step = 1; else
5603   if(y > h - margin) step = -1;
5604   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5605   currentMove += step;
5606   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5607   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5608                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5609   DrawPosition(FALSE, boards[currentMove]);
5610 }
5611
5612
5613 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5614 // All positions will have equal probability, but the current method will not provide a unique
5615 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5616 #define DARK 1
5617 #define LITE 2
5618 #define ANY 3
5619
5620 int squaresLeft[4];
5621 int piecesLeft[(int)BlackPawn];
5622 int seed, nrOfShuffles;
5623
5624 void
5625 GetPositionNumber ()
5626 {       // sets global variable seed
5627         int i;
5628
5629         seed = appData.defaultFrcPosition;
5630         if(seed < 0) { // randomize based on time for negative FRC position numbers
5631                 for(i=0; i<50; i++) seed += random();
5632                 seed = random() ^ random() >> 8 ^ random() << 8;
5633                 if(seed<0) seed = -seed;
5634         }
5635 }
5636
5637 int
5638 put (Board board, int pieceType, int rank, int n, int shade)
5639 // put the piece on the (n-1)-th empty squares of the given shade
5640 {
5641         int i;
5642
5643         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5644                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5645                         board[rank][i] = (ChessSquare) pieceType;
5646                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5647                         squaresLeft[ANY]--;
5648                         piecesLeft[pieceType]--;
5649                         return i;
5650                 }
5651         }
5652         return -1;
5653 }
5654
5655
5656 void
5657 AddOnePiece (Board board, int pieceType, int rank, int shade)
5658 // calculate where the next piece goes, (any empty square), and put it there
5659 {
5660         int i;
5661
5662         i = seed % squaresLeft[shade];
5663         nrOfShuffles *= squaresLeft[shade];
5664         seed /= squaresLeft[shade];
5665         put(board, pieceType, rank, i, shade);
5666 }
5667
5668 void
5669 AddTwoPieces (Board board, int pieceType, int rank)
5670 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5671 {
5672         int i, n=squaresLeft[ANY], j=n-1, k;
5673
5674         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5675         i = seed % k;  // pick one
5676         nrOfShuffles *= k;
5677         seed /= k;
5678         while(i >= j) i -= j--;
5679         j = n - 1 - j; i += j;
5680         put(board, pieceType, rank, j, ANY);
5681         put(board, pieceType, rank, i, ANY);
5682 }
5683
5684 void
5685 SetUpShuffle (Board board, int number)
5686 {
5687         int i, p, first=1;
5688
5689         GetPositionNumber(); nrOfShuffles = 1;
5690
5691         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5692         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5693         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5694
5695         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5696
5697         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5698             p = (int) board[0][i];
5699             if(p < (int) BlackPawn) piecesLeft[p] ++;
5700             board[0][i] = EmptySquare;
5701         }
5702
5703         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5704             // shuffles restricted to allow normal castling put KRR first
5705             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5706                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5707             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5708                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5709             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5710                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5711             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5712                 put(board, WhiteRook, 0, 0, ANY);
5713             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5714         }
5715
5716         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5717             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5718             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5719                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5720                 while(piecesLeft[p] >= 2) {
5721                     AddOnePiece(board, p, 0, LITE);
5722                     AddOnePiece(board, p, 0, DARK);
5723                 }
5724                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5725             }
5726
5727         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5728             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5729             // but we leave King and Rooks for last, to possibly obey FRC restriction
5730             if(p == (int)WhiteRook) continue;
5731             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5732             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5733         }
5734
5735         // now everything is placed, except perhaps King (Unicorn) and Rooks
5736
5737         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5738             // Last King gets castling rights
5739             while(piecesLeft[(int)WhiteUnicorn]) {
5740                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5741                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5742             }
5743
5744             while(piecesLeft[(int)WhiteKing]) {
5745                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5746                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5747             }
5748
5749
5750         } else {
5751             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5752             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5753         }
5754
5755         // Only Rooks can be left; simply place them all
5756         while(piecesLeft[(int)WhiteRook]) {
5757                 i = put(board, WhiteRook, 0, 0, ANY);
5758                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5759                         if(first) {
5760                                 first=0;
5761                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5762                         }
5763                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5764                 }
5765         }
5766         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5767             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5768         }
5769
5770         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5771 }
5772
5773 int
5774 SetCharTable (char *table, const char * map)
5775 /* [HGM] moved here from winboard.c because of its general usefulness */
5776 /*       Basically a safe strcpy that uses the last character as King */
5777 {
5778     int result = FALSE; int NrPieces;
5779
5780     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5781                     && NrPieces >= 12 && !(NrPieces&1)) {
5782         int i; /* [HGM] Accept even length from 12 to 34 */
5783
5784         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5785         for( i=0; i<NrPieces/2-1; i++ ) {
5786             table[i] = map[i];
5787             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5788         }
5789         table[(int) WhiteKing]  = map[NrPieces/2-1];
5790         table[(int) BlackKing]  = map[NrPieces-1];
5791
5792         result = TRUE;
5793     }
5794
5795     return result;
5796 }
5797
5798 void
5799 Prelude (Board board)
5800 {       // [HGM] superchess: random selection of exo-pieces
5801         int i, j, k; ChessSquare p;
5802         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5803
5804         GetPositionNumber(); // use FRC position number
5805
5806         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5807             SetCharTable(pieceToChar, appData.pieceToCharTable);
5808             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5809                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5810         }
5811
5812         j = seed%4;                 seed /= 4;
5813         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5814         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5815         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5816         j = seed%3 + (seed%3 >= j); seed /= 3;
5817         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5818         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5819         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5820         j = seed%3;                 seed /= 3;
5821         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5822         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5823         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5824         j = seed%2 + (seed%2 >= j); seed /= 2;
5825         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5826         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5827         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5828         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5829         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5830         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5831         put(board, exoPieces[0],    0, 0, ANY);
5832         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5833 }
5834
5835 void
5836 InitPosition (int redraw)
5837 {
5838     ChessSquare (* pieces)[BOARD_FILES];
5839     int i, j, pawnRow, overrule,
5840     oldx = gameInfo.boardWidth,
5841     oldy = gameInfo.boardHeight,
5842     oldh = gameInfo.holdingsWidth;
5843     static int oldv;
5844
5845     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5846
5847     /* [AS] Initialize pv info list [HGM] and game status */
5848     {
5849         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5850             pvInfoList[i].depth = 0;
5851             boards[i][EP_STATUS] = EP_NONE;
5852             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5853         }
5854
5855         initialRulePlies = 0; /* 50-move counter start */
5856
5857         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5858         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5859     }
5860
5861
5862     /* [HGM] logic here is completely changed. In stead of full positions */
5863     /* the initialized data only consist of the two backranks. The switch */
5864     /* selects which one we will use, which is than copied to the Board   */
5865     /* initialPosition, which for the rest is initialized by Pawns and    */
5866     /* empty squares. This initial position is then copied to boards[0],  */
5867     /* possibly after shuffling, so that it remains available.            */
5868
5869     gameInfo.holdingsWidth = 0; /* default board sizes */
5870     gameInfo.boardWidth    = 8;
5871     gameInfo.boardHeight   = 8;
5872     gameInfo.holdingsSize  = 0;
5873     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5874     for(i=0; i<BOARD_FILES-2; i++)
5875       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5876     initialPosition[EP_STATUS] = EP_NONE;
5877     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5878     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5879          SetCharTable(pieceNickName, appData.pieceNickNames);
5880     else SetCharTable(pieceNickName, "............");
5881     pieces = FIDEArray;
5882
5883     switch (gameInfo.variant) {
5884     case VariantFischeRandom:
5885       shuffleOpenings = TRUE;
5886     default:
5887       break;
5888     case VariantShatranj:
5889       pieces = ShatranjArray;
5890       nrCastlingRights = 0;
5891       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5892       break;
5893     case VariantMakruk:
5894       pieces = makrukArray;
5895       nrCastlingRights = 0;
5896       startedFromSetupPosition = TRUE;
5897       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5898       break;
5899     case VariantTwoKings:
5900       pieces = twoKingsArray;
5901       break;
5902     case VariantGrand:
5903       pieces = GrandArray;
5904       nrCastlingRights = 0;
5905       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5906       gameInfo.boardWidth = 10;
5907       gameInfo.boardHeight = 10;
5908       gameInfo.holdingsSize = 7;
5909       break;
5910     case VariantCapaRandom:
5911       shuffleOpenings = TRUE;
5912     case VariantCapablanca:
5913       pieces = CapablancaArray;
5914       gameInfo.boardWidth = 10;
5915       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5916       break;
5917     case VariantGothic:
5918       pieces = GothicArray;
5919       gameInfo.boardWidth = 10;
5920       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5921       break;
5922     case VariantSChess:
5923       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5924       gameInfo.holdingsSize = 7;
5925       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5926       break;
5927     case VariantJanus:
5928       pieces = JanusArray;
5929       gameInfo.boardWidth = 10;
5930       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5931       nrCastlingRights = 6;
5932         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5933         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5934         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5935         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5936         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5937         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5938       break;
5939     case VariantFalcon:
5940       pieces = FalconArray;
5941       gameInfo.boardWidth = 10;
5942       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5943       break;
5944     case VariantXiangqi:
5945       pieces = XiangqiArray;
5946       gameInfo.boardWidth  = 9;
5947       gameInfo.boardHeight = 10;
5948       nrCastlingRights = 0;
5949       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5950       break;
5951     case VariantShogi:
5952       pieces = ShogiArray;
5953       gameInfo.boardWidth  = 9;
5954       gameInfo.boardHeight = 9;
5955       gameInfo.holdingsSize = 7;
5956       nrCastlingRights = 0;
5957       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5958       break;
5959     case VariantCourier:
5960       pieces = CourierArray;
5961       gameInfo.boardWidth  = 12;
5962       nrCastlingRights = 0;
5963       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5964       break;
5965     case VariantKnightmate:
5966       pieces = KnightmateArray;
5967       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5968       break;
5969     case VariantSpartan:
5970       pieces = SpartanArray;
5971       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5972       break;
5973     case VariantFairy:
5974       pieces = fairyArray;
5975       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5976       break;
5977     case VariantGreat:
5978       pieces = GreatArray;
5979       gameInfo.boardWidth = 10;
5980       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5981       gameInfo.holdingsSize = 8;
5982       break;
5983     case VariantSuper:
5984       pieces = FIDEArray;
5985       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5986       gameInfo.holdingsSize = 8;
5987       startedFromSetupPosition = TRUE;
5988       break;
5989     case VariantCrazyhouse:
5990     case VariantBughouse:
5991       pieces = FIDEArray;
5992       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5993       gameInfo.holdingsSize = 5;
5994       break;
5995     case VariantWildCastle:
5996       pieces = FIDEArray;
5997       /* !!?shuffle with kings guaranteed to be on d or e file */
5998       shuffleOpenings = 1;
5999       break;
6000     case VariantNoCastle:
6001       pieces = FIDEArray;
6002       nrCastlingRights = 0;
6003       /* !!?unconstrained back-rank shuffle */
6004       shuffleOpenings = 1;
6005       break;
6006     }
6007
6008     overrule = 0;
6009     if(appData.NrFiles >= 0) {
6010         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6011         gameInfo.boardWidth = appData.NrFiles;
6012     }
6013     if(appData.NrRanks >= 0) {
6014         gameInfo.boardHeight = appData.NrRanks;
6015     }
6016     if(appData.holdingsSize >= 0) {
6017         i = appData.holdingsSize;
6018         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6019         gameInfo.holdingsSize = i;
6020     }
6021     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6022     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6023         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6024
6025     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6026     if(pawnRow < 1) pawnRow = 1;
6027     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6028
6029     /* User pieceToChar list overrules defaults */
6030     if(appData.pieceToCharTable != NULL)
6031         SetCharTable(pieceToChar, appData.pieceToCharTable);
6032
6033     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6034
6035         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6036             s = (ChessSquare) 0; /* account holding counts in guard band */
6037         for( i=0; i<BOARD_HEIGHT; i++ )
6038             initialPosition[i][j] = s;
6039
6040         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6041         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6042         initialPosition[pawnRow][j] = WhitePawn;
6043         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6044         if(gameInfo.variant == VariantXiangqi) {
6045             if(j&1) {
6046                 initialPosition[pawnRow][j] =
6047                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6048                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6049                    initialPosition[2][j] = WhiteCannon;
6050                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6051                 }
6052             }
6053         }
6054         if(gameInfo.variant == VariantGrand) {
6055             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6056                initialPosition[0][j] = WhiteRook;
6057                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6058             }
6059         }
6060         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6061     }
6062     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6063
6064             j=BOARD_LEFT+1;
6065             initialPosition[1][j] = WhiteBishop;
6066             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6067             j=BOARD_RGHT-2;
6068             initialPosition[1][j] = WhiteRook;
6069             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6070     }
6071
6072     if( nrCastlingRights == -1) {
6073         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6074         /*       This sets default castling rights from none to normal corners   */
6075         /* Variants with other castling rights must set them themselves above    */
6076         nrCastlingRights = 6;
6077
6078         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6079         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6080         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6081         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6082         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6083         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6084      }
6085
6086      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6087      if(gameInfo.variant == VariantGreat) { // promotion commoners
6088         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6089         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6090         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6091         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6092      }
6093      if( gameInfo.variant == VariantSChess ) {
6094       initialPosition[1][0] = BlackMarshall;
6095       initialPosition[2][0] = BlackAngel;
6096       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6097       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6098       initialPosition[1][1] = initialPosition[2][1] =
6099       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6100      }
6101   if (appData.debugMode) {
6102     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6103   }
6104     if(shuffleOpenings) {
6105         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6106         startedFromSetupPosition = TRUE;
6107     }
6108     if(startedFromPositionFile) {
6109       /* [HGM] loadPos: use PositionFile for every new game */
6110       CopyBoard(initialPosition, filePosition);
6111       for(i=0; i<nrCastlingRights; i++)
6112           initialRights[i] = filePosition[CASTLING][i];
6113       startedFromSetupPosition = TRUE;
6114     }
6115
6116     CopyBoard(boards[0], initialPosition);
6117
6118     if(oldx != gameInfo.boardWidth ||
6119        oldy != gameInfo.boardHeight ||
6120        oldv != gameInfo.variant ||
6121        oldh != gameInfo.holdingsWidth
6122                                          )
6123             InitDrawingSizes(-2 ,0);
6124
6125     oldv = gameInfo.variant;
6126     if (redraw)
6127       DrawPosition(TRUE, boards[currentMove]);
6128 }
6129
6130 void
6131 SendBoard (ChessProgramState *cps, int moveNum)
6132 {
6133     char message[MSG_SIZ];
6134
6135     if (cps->useSetboard) {
6136       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6137       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6138       SendToProgram(message, cps);
6139       free(fen);
6140
6141     } else {
6142       ChessSquare *bp;
6143       int i, j, left=0, right=BOARD_WIDTH;
6144       /* Kludge to set black to move, avoiding the troublesome and now
6145        * deprecated "black" command.
6146        */
6147       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6148         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6149
6150       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6151
6152       SendToProgram("edit\n", cps);
6153       SendToProgram("#\n", cps);
6154       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6155         bp = &boards[moveNum][i][left];
6156         for (j = left; j < right; j++, bp++) {
6157           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6158           if ((int) *bp < (int) BlackPawn) {
6159             if(j == BOARD_RGHT+1)
6160                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6161             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6162             if(message[0] == '+' || message[0] == '~') {
6163               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6164                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6165                         AAA + j, ONE + i);
6166             }
6167             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6168                 message[1] = BOARD_RGHT   - 1 - j + '1';
6169                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6170             }
6171             SendToProgram(message, cps);
6172           }
6173         }
6174       }
6175
6176       SendToProgram("c\n", cps);
6177       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6178         bp = &boards[moveNum][i][left];
6179         for (j = left; j < right; j++, bp++) {
6180           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6181           if (((int) *bp != (int) EmptySquare)
6182               && ((int) *bp >= (int) BlackPawn)) {
6183             if(j == BOARD_LEFT-2)
6184                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6185             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6186                     AAA + j, ONE + i);
6187             if(message[0] == '+' || message[0] == '~') {
6188               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6189                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6190                         AAA + j, ONE + i);
6191             }
6192             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6193                 message[1] = BOARD_RGHT   - 1 - j + '1';
6194                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6195             }
6196             SendToProgram(message, cps);
6197           }
6198         }
6199       }
6200
6201       SendToProgram(".\n", cps);
6202     }
6203     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6204 }
6205
6206 char exclusionHeader[MSG_SIZ];
6207 int exCnt, excludePtr;
6208 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6209 static Exclusion excluTab[200];
6210 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6211
6212 static void
6213 WriteMap (int s)
6214 {
6215     int j;
6216     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6217     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6218 }
6219
6220 static void
6221 ClearMap ()
6222 {
6223     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6224     excludePtr = 24; exCnt = 0;
6225     WriteMap(0);
6226 }
6227
6228 static void
6229 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6230 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6231     char buf[2*MOVE_LEN], *p;
6232     Exclusion *e = excluTab;
6233     int i;
6234     for(i=0; i<exCnt; i++)
6235         if(e[i].ff == fromX && e[i].fr == fromY &&
6236            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6237     if(i == exCnt) { // was not in exclude list; add it
6238         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6239         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6240             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6241             return; // abort
6242         }
6243         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6244         excludePtr++; e[i].mark = excludePtr++;
6245         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6246         exCnt++;
6247     }
6248     exclusionHeader[e[i].mark] = state;
6249 }
6250
6251 static int
6252 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6253 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6254     char buf[MSG_SIZ];
6255     int j, k;
6256     ChessMove moveType;
6257     if((signed char)promoChar == -1) { // kludge to indicate best move
6258         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6259             return 1; // if unparsable, abort
6260     }
6261     // update exclusion map (resolving toggle by consulting existing state)
6262     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6263     j = k%8; k >>= 3;
6264     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6265     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6266          excludeMap[k] |=   1<<j;
6267     else excludeMap[k] &= ~(1<<j);
6268     // update header
6269     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6270     // inform engine
6271     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6272     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6273     SendToBoth(buf);
6274     return (state == '+');
6275 }
6276
6277 static void
6278 ExcludeClick (int index)
6279 {
6280     int i, j;
6281     Exclusion *e = excluTab;
6282     if(index < 25) { // none, best or tail clicked
6283         if(index < 13) { // none: include all
6284             WriteMap(0); // clear map
6285             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6286             SendToBoth("include all\n"); // and inform engine
6287         } else if(index > 18) { // tail
6288             if(exclusionHeader[19] == '-') { // tail was excluded
6289                 SendToBoth("include all\n");
6290                 WriteMap(0); // clear map completely
6291                 // now re-exclude selected moves
6292                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6293                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6294             } else { // tail was included or in mixed state
6295                 SendToBoth("exclude all\n");
6296                 WriteMap(0xFF); // fill map completely
6297                 // now re-include selected moves
6298                 j = 0; // count them
6299                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6300                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6301                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6302             }
6303         } else { // best
6304             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6305         }
6306     } else {
6307         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6308             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6309             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6310             break;
6311         }
6312     }
6313 }
6314
6315 ChessSquare
6316 DefaultPromoChoice (int white)
6317 {
6318     ChessSquare result;
6319     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6320         result = WhiteFerz; // no choice
6321     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6322         result= WhiteKing; // in Suicide Q is the last thing we want
6323     else if(gameInfo.variant == VariantSpartan)
6324         result = white ? WhiteQueen : WhiteAngel;
6325     else result = WhiteQueen;
6326     if(!white) result = WHITE_TO_BLACK result;
6327     return result;
6328 }
6329
6330 static int autoQueen; // [HGM] oneclick
6331
6332 int
6333 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6334 {
6335     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6336     /* [HGM] add Shogi promotions */
6337     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6338     ChessSquare piece;
6339     ChessMove moveType;
6340     Boolean premove;
6341
6342     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6343     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6344
6345     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6346       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6347         return FALSE;
6348
6349     piece = boards[currentMove][fromY][fromX];
6350     if(gameInfo.variant == VariantShogi) {
6351         promotionZoneSize = BOARD_HEIGHT/3;
6352         highestPromotingPiece = (int)WhiteFerz;
6353     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6354         promotionZoneSize = 3;
6355     }
6356
6357     // Treat Lance as Pawn when it is not representing Amazon
6358     if(gameInfo.variant != VariantSuper) {
6359         if(piece == WhiteLance) piece = WhitePawn; else
6360         if(piece == BlackLance) piece = BlackPawn;
6361     }
6362
6363     // next weed out all moves that do not touch the promotion zone at all
6364     if((int)piece >= BlackPawn) {
6365         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6366              return FALSE;
6367         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6368     } else {
6369         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6370            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6371     }
6372
6373     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6374
6375     // weed out mandatory Shogi promotions
6376     if(gameInfo.variant == VariantShogi) {
6377         if(piece >= BlackPawn) {
6378             if(toY == 0 && piece == BlackPawn ||
6379                toY == 0 && piece == BlackQueen ||
6380                toY <= 1 && piece == BlackKnight) {
6381                 *promoChoice = '+';
6382                 return FALSE;
6383             }
6384         } else {
6385             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6386                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6387                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6388                 *promoChoice = '+';
6389                 return FALSE;
6390             }
6391         }
6392     }
6393
6394     // weed out obviously illegal Pawn moves
6395     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6396         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6397         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6398         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6399         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6400         // note we are not allowed to test for valid (non-)capture, due to premove
6401     }
6402
6403     // we either have a choice what to promote to, or (in Shogi) whether to promote
6404     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6405         *promoChoice = PieceToChar(BlackFerz);  // no choice
6406         return FALSE;
6407     }
6408     // no sense asking what we must promote to if it is going to explode...
6409     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6410         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6411         return FALSE;
6412     }
6413     // give caller the default choice even if we will not make it
6414     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6415     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6416     if(        sweepSelect && gameInfo.variant != VariantGreat
6417                            && gameInfo.variant != VariantGrand
6418                            && gameInfo.variant != VariantSuper) return FALSE;
6419     if(autoQueen) return FALSE; // predetermined
6420
6421     // suppress promotion popup on illegal moves that are not premoves
6422     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6423               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6424     if(appData.testLegality && !premove) {
6425         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6426                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6427         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6428             return FALSE;
6429     }
6430
6431     return TRUE;
6432 }
6433
6434 int
6435 InPalace (int row, int column)
6436 {   /* [HGM] for Xiangqi */
6437     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6438          column < (BOARD_WIDTH + 4)/2 &&
6439          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6440     return FALSE;
6441 }
6442
6443 int
6444 PieceForSquare (int x, int y)
6445 {
6446   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6447      return -1;
6448   else
6449      return boards[currentMove][y][x];
6450 }
6451
6452 int
6453 OKToStartUserMove (int x, int y)
6454 {
6455     ChessSquare from_piece;
6456     int white_piece;
6457
6458     if (matchMode) return FALSE;
6459     if (gameMode == EditPosition) return TRUE;
6460
6461     if (x >= 0 && y >= 0)
6462       from_piece = boards[currentMove][y][x];
6463     else
6464       from_piece = EmptySquare;
6465
6466     if (from_piece == EmptySquare) return FALSE;
6467
6468     white_piece = (int)from_piece >= (int)WhitePawn &&
6469       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6470
6471     switch (gameMode) {
6472       case AnalyzeFile:
6473       case TwoMachinesPlay:
6474       case EndOfGame:
6475         return FALSE;
6476
6477       case IcsObserving:
6478       case IcsIdle:
6479         return FALSE;
6480
6481       case MachinePlaysWhite:
6482       case IcsPlayingBlack:
6483         if (appData.zippyPlay) return FALSE;
6484         if (white_piece) {
6485             DisplayMoveError(_("You are playing Black"));
6486             return FALSE;
6487         }
6488         break;
6489
6490       case MachinePlaysBlack:
6491       case IcsPlayingWhite:
6492         if (appData.zippyPlay) return FALSE;
6493         if (!white_piece) {
6494             DisplayMoveError(_("You are playing White"));
6495             return FALSE;
6496         }
6497         break;
6498
6499       case PlayFromGameFile:
6500             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6501       case EditGame:
6502         if (!white_piece && WhiteOnMove(currentMove)) {
6503             DisplayMoveError(_("It is White's turn"));
6504             return FALSE;
6505         }
6506         if (white_piece && !WhiteOnMove(currentMove)) {
6507             DisplayMoveError(_("It is Black's turn"));
6508             return FALSE;
6509         }
6510         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6511             /* Editing correspondence game history */
6512             /* Could disallow this or prompt for confirmation */
6513             cmailOldMove = -1;
6514         }
6515         break;
6516
6517       case BeginningOfGame:
6518         if (appData.icsActive) return FALSE;
6519         if (!appData.noChessProgram) {
6520             if (!white_piece) {
6521                 DisplayMoveError(_("You are playing White"));
6522                 return FALSE;
6523             }
6524         }
6525         break;
6526
6527       case Training:
6528         if (!white_piece && WhiteOnMove(currentMove)) {
6529             DisplayMoveError(_("It is White's turn"));
6530             return FALSE;
6531         }
6532         if (white_piece && !WhiteOnMove(currentMove)) {
6533             DisplayMoveError(_("It is Black's turn"));
6534             return FALSE;
6535         }
6536         break;
6537
6538       default:
6539       case IcsExamining:
6540         break;
6541     }
6542     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6543         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6544         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6545         && gameMode != AnalyzeFile && gameMode != Training) {
6546         DisplayMoveError(_("Displayed position is not current"));
6547         return FALSE;
6548     }
6549     return TRUE;
6550 }
6551
6552 Boolean
6553 OnlyMove (int *x, int *y, Boolean captures)
6554 {
6555     DisambiguateClosure cl;
6556     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6557     switch(gameMode) {
6558       case MachinePlaysBlack:
6559       case IcsPlayingWhite:
6560       case BeginningOfGame:
6561         if(!WhiteOnMove(currentMove)) return FALSE;
6562         break;
6563       case MachinePlaysWhite:
6564       case IcsPlayingBlack:
6565         if(WhiteOnMove(currentMove)) return FALSE;
6566         break;
6567       case EditGame:
6568         break;
6569       default:
6570         return FALSE;
6571     }
6572     cl.pieceIn = EmptySquare;
6573     cl.rfIn = *y;
6574     cl.ffIn = *x;
6575     cl.rtIn = -1;
6576     cl.ftIn = -1;
6577     cl.promoCharIn = NULLCHAR;
6578     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6579     if( cl.kind == NormalMove ||
6580         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6581         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6582         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6583       fromX = cl.ff;
6584       fromY = cl.rf;
6585       *x = cl.ft;
6586       *y = cl.rt;
6587       return TRUE;
6588     }
6589     if(cl.kind != ImpossibleMove) return FALSE;
6590     cl.pieceIn = EmptySquare;
6591     cl.rfIn = -1;
6592     cl.ffIn = -1;
6593     cl.rtIn = *y;
6594     cl.ftIn = *x;
6595     cl.promoCharIn = NULLCHAR;
6596     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6597     if( cl.kind == NormalMove ||
6598         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6599         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6600         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6601       fromX = cl.ff;
6602       fromY = cl.rf;
6603       *x = cl.ft;
6604       *y = cl.rt;
6605       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6606       return TRUE;
6607     }
6608     return FALSE;
6609 }
6610
6611 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6612 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6613 int lastLoadGameUseList = FALSE;
6614 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6615 ChessMove lastLoadGameStart = EndOfFile;
6616 int doubleClick;
6617
6618 void
6619 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6620 {
6621     ChessMove moveType;
6622     ChessSquare pup;
6623     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6624
6625     /* Check if the user is playing in turn.  This is complicated because we
6626        let the user "pick up" a piece before it is his turn.  So the piece he
6627        tried to pick up may have been captured by the time he puts it down!
6628        Therefore we use the color the user is supposed to be playing in this
6629        test, not the color of the piece that is currently on the starting
6630        square---except in EditGame mode, where the user is playing both
6631        sides; fortunately there the capture race can't happen.  (It can
6632        now happen in IcsExamining mode, but that's just too bad.  The user
6633        will get a somewhat confusing message in that case.)
6634        */
6635
6636     switch (gameMode) {
6637       case AnalyzeFile:
6638       case TwoMachinesPlay:
6639       case EndOfGame:
6640       case IcsObserving:
6641       case IcsIdle:
6642         /* We switched into a game mode where moves are not accepted,
6643            perhaps while the mouse button was down. */
6644         return;
6645
6646       case MachinePlaysWhite:
6647         /* User is moving for Black */
6648         if (WhiteOnMove(currentMove)) {
6649             DisplayMoveError(_("It is White's turn"));
6650             return;
6651         }
6652         break;
6653
6654       case MachinePlaysBlack:
6655         /* User is moving for White */
6656         if (!WhiteOnMove(currentMove)) {
6657             DisplayMoveError(_("It is Black's turn"));
6658             return;
6659         }
6660         break;
6661
6662       case PlayFromGameFile:
6663             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6664       case EditGame:
6665       case IcsExamining:
6666       case BeginningOfGame:
6667       case AnalyzeMode:
6668       case Training:
6669         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6670         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6671             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6672             /* User is moving for Black */
6673             if (WhiteOnMove(currentMove)) {
6674                 DisplayMoveError(_("It is White's turn"));
6675                 return;
6676             }
6677         } else {
6678             /* User is moving for White */
6679             if (!WhiteOnMove(currentMove)) {
6680                 DisplayMoveError(_("It is Black's turn"));
6681                 return;
6682             }
6683         }
6684         break;
6685
6686       case IcsPlayingBlack:
6687         /* User is moving for Black */
6688         if (WhiteOnMove(currentMove)) {
6689             if (!appData.premove) {
6690                 DisplayMoveError(_("It is White's turn"));
6691             } else if (toX >= 0 && toY >= 0) {
6692                 premoveToX = toX;
6693                 premoveToY = toY;
6694                 premoveFromX = fromX;
6695                 premoveFromY = fromY;
6696                 premovePromoChar = promoChar;
6697                 gotPremove = 1;
6698                 if (appData.debugMode)
6699                     fprintf(debugFP, "Got premove: fromX %d,"
6700                             "fromY %d, toX %d, toY %d\n",
6701                             fromX, fromY, toX, toY);
6702             }
6703             return;
6704         }
6705         break;
6706
6707       case IcsPlayingWhite:
6708         /* User is moving for White */
6709         if (!WhiteOnMove(currentMove)) {
6710             if (!appData.premove) {
6711                 DisplayMoveError(_("It is Black's turn"));
6712             } else if (toX >= 0 && toY >= 0) {
6713                 premoveToX = toX;
6714                 premoveToY = toY;
6715                 premoveFromX = fromX;
6716                 premoveFromY = fromY;
6717                 premovePromoChar = promoChar;
6718                 gotPremove = 1;
6719                 if (appData.debugMode)
6720                     fprintf(debugFP, "Got premove: fromX %d,"
6721                             "fromY %d, toX %d, toY %d\n",
6722                             fromX, fromY, toX, toY);
6723             }
6724             return;
6725         }
6726         break;
6727
6728       default:
6729         break;
6730
6731       case EditPosition:
6732         /* EditPosition, empty square, or different color piece;
6733            click-click move is possible */
6734         if (toX == -2 || toY == -2) {
6735             boards[0][fromY][fromX] = EmptySquare;
6736             DrawPosition(FALSE, boards[currentMove]);
6737             return;
6738         } else if (toX >= 0 && toY >= 0) {
6739             boards[0][toY][toX] = boards[0][fromY][fromX];
6740             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6741                 if(boards[0][fromY][0] != EmptySquare) {
6742                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6743                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6744                 }
6745             } else
6746             if(fromX == BOARD_RGHT+1) {
6747                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6748                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6749                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6750                 }
6751             } else
6752             boards[0][fromY][fromX] = gatingPiece;
6753             DrawPosition(FALSE, boards[currentMove]);
6754             return;
6755         }
6756         return;
6757     }
6758
6759     if(toX < 0 || toY < 0) return;
6760     pup = boards[currentMove][toY][toX];
6761
6762     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6763     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6764          if( pup != EmptySquare ) return;
6765          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6766            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6767                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6768            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6769            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6770            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6771            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6772          fromY = DROP_RANK;
6773     }
6774
6775     /* [HGM] always test for legality, to get promotion info */
6776     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6777                                          fromY, fromX, toY, toX, promoChar);
6778
6779     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6780
6781     /* [HGM] but possibly ignore an IllegalMove result */
6782     if (appData.testLegality) {
6783         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6784             DisplayMoveError(_("Illegal move"));
6785             return;
6786         }
6787     }
6788
6789     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6790         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6791              ClearPremoveHighlights(); // was included
6792         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6793         return;
6794     }
6795
6796     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6797 }
6798
6799 /* Common tail of UserMoveEvent and DropMenuEvent */
6800 int
6801 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6802 {
6803     char *bookHit = 0;
6804
6805     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6806         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6807         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6808         if(WhiteOnMove(currentMove)) {
6809             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6810         } else {
6811             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6812         }
6813     }
6814
6815     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6816        move type in caller when we know the move is a legal promotion */
6817     if(moveType == NormalMove && promoChar)
6818         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6819
6820     /* [HGM] <popupFix> The following if has been moved here from
6821        UserMoveEvent(). Because it seemed to belong here (why not allow
6822        piece drops in training games?), and because it can only be
6823        performed after it is known to what we promote. */
6824     if (gameMode == Training) {
6825       /* compare the move played on the board to the next move in the
6826        * game. If they match, display the move and the opponent's response.
6827        * If they don't match, display an error message.
6828        */
6829       int saveAnimate;
6830       Board testBoard;
6831       CopyBoard(testBoard, boards[currentMove]);
6832       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6833
6834       if (CompareBoards(testBoard, boards[currentMove+1])) {
6835         ForwardInner(currentMove+1);
6836
6837         /* Autoplay the opponent's response.
6838          * if appData.animate was TRUE when Training mode was entered,
6839          * the response will be animated.
6840          */
6841         saveAnimate = appData.animate;
6842         appData.animate = animateTraining;
6843         ForwardInner(currentMove+1);
6844         appData.animate = saveAnimate;
6845
6846         /* check for the end of the game */
6847         if (currentMove >= forwardMostMove) {
6848           gameMode = PlayFromGameFile;
6849           ModeHighlight();
6850           SetTrainingModeOff();
6851           DisplayInformation(_("End of game"));
6852         }
6853       } else {
6854         DisplayError(_("Incorrect move"), 0);
6855       }
6856       return 1;
6857     }
6858
6859   /* Ok, now we know that the move is good, so we can kill
6860      the previous line in Analysis Mode */
6861   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6862                                 && currentMove < forwardMostMove) {
6863     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6864     else forwardMostMove = currentMove;
6865   }
6866
6867   ClearMap();
6868
6869   /* If we need the chess program but it's dead, restart it */
6870   ResurrectChessProgram();
6871
6872   /* A user move restarts a paused game*/
6873   if (pausing)
6874     PauseEvent();
6875
6876   thinkOutput[0] = NULLCHAR;
6877
6878   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6879
6880   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6881     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6882     return 1;
6883   }
6884
6885   if (gameMode == BeginningOfGame) {
6886     if (appData.noChessProgram) {
6887       gameMode = EditGame;
6888       SetGameInfo();
6889     } else {
6890       char buf[MSG_SIZ];
6891       gameMode = MachinePlaysBlack;
6892       StartClocks();
6893       SetGameInfo();
6894       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6895       DisplayTitle(buf);
6896       if (first.sendName) {
6897         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6898         SendToProgram(buf, &first);
6899       }
6900       StartClocks();
6901     }
6902     ModeHighlight();
6903   }
6904
6905   /* Relay move to ICS or chess engine */
6906   if (appData.icsActive) {
6907     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6908         gameMode == IcsExamining) {
6909       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6910         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6911         SendToICS("draw ");
6912         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6913       }
6914       // also send plain move, in case ICS does not understand atomic claims
6915       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6916       ics_user_moved = 1;
6917     }
6918   } else {
6919     if (first.sendTime && (gameMode == BeginningOfGame ||
6920                            gameMode == MachinePlaysWhite ||
6921                            gameMode == MachinePlaysBlack)) {
6922       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6923     }
6924     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6925          // [HGM] book: if program might be playing, let it use book
6926         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6927         first.maybeThinking = TRUE;
6928     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6929         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6930         SendBoard(&first, currentMove+1);
6931         if(second.analyzing) {
6932             if(!second.useSetboard) SendToProgram("undo\n", &second);
6933             SendBoard(&second, currentMove+1);
6934         }
6935     } else {
6936         SendMoveToProgram(forwardMostMove-1, &first);
6937         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6938     }
6939     if (currentMove == cmailOldMove + 1) {
6940       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6941     }
6942   }
6943
6944   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6945
6946   switch (gameMode) {
6947   case EditGame:
6948     if(appData.testLegality)
6949     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6950     case MT_NONE:
6951     case MT_CHECK:
6952       break;
6953     case MT_CHECKMATE:
6954     case MT_STAINMATE:
6955       if (WhiteOnMove(currentMove)) {
6956         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6957       } else {
6958         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6959       }
6960       break;
6961     case MT_STALEMATE:
6962       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6963       break;
6964     }
6965     break;
6966
6967   case MachinePlaysBlack:
6968   case MachinePlaysWhite:
6969     /* disable certain menu options while machine is thinking */
6970     SetMachineThinkingEnables();
6971     break;
6972
6973   default:
6974     break;
6975   }
6976
6977   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6978   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6979
6980   if(bookHit) { // [HGM] book: simulate book reply
6981         static char bookMove[MSG_SIZ]; // a bit generous?
6982
6983         programStats.nodes = programStats.depth = programStats.time =
6984         programStats.score = programStats.got_only_move = 0;
6985         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6986
6987         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6988         strcat(bookMove, bookHit);
6989         HandleMachineMove(bookMove, &first);
6990   }
6991   return 1;
6992 }
6993
6994 void
6995 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6996 {
6997     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6998     Markers *m = (Markers *) closure;
6999     if(rf == fromY && ff == fromX)
7000         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7001                          || kind == WhiteCapturesEnPassant
7002                          || kind == BlackCapturesEnPassant);
7003     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7004 }
7005
7006 void
7007 MarkTargetSquares (int clear)
7008 {
7009   int x, y;
7010   if(clear) // no reason to ever suppress clearing
7011     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7012   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7013      !appData.testLegality || gameMode == EditPosition) return;
7014   if(!clear) {
7015     int capt = 0;
7016     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7017     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7018       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7019       if(capt)
7020       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7021     }
7022   }
7023   DrawPosition(FALSE, NULL);
7024 }
7025
7026 int
7027 Explode (Board board, int fromX, int fromY, int toX, int toY)
7028 {
7029     if(gameInfo.variant == VariantAtomic &&
7030        (board[toY][toX] != EmptySquare ||                     // capture?
7031         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7032                          board[fromY][fromX] == BlackPawn   )
7033       )) {
7034         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7035         return TRUE;
7036     }
7037     return FALSE;
7038 }
7039
7040 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7041
7042 int
7043 CanPromote (ChessSquare piece, int y)
7044 {
7045         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7046         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7047         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7048            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7049            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7050                                                   gameInfo.variant == VariantMakruk) return FALSE;
7051         return (piece == BlackPawn && y == 1 ||
7052                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7053                 piece == BlackLance && y == 1 ||
7054                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7055 }
7056
7057 void
7058 LeftClick (ClickType clickType, int xPix, int yPix)
7059 {
7060     int x, y;
7061     Boolean saveAnimate;
7062     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7063     char promoChoice = NULLCHAR;
7064     ChessSquare piece;
7065     static TimeMark lastClickTime, prevClickTime;
7066
7067     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7068
7069     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7070
7071     if (clickType == Press) ErrorPopDown();
7072
7073     x = EventToSquare(xPix, BOARD_WIDTH);
7074     y = EventToSquare(yPix, BOARD_HEIGHT);
7075     if (!flipView && y >= 0) {
7076         y = BOARD_HEIGHT - 1 - y;
7077     }
7078     if (flipView && x >= 0) {
7079         x = BOARD_WIDTH - 1 - x;
7080     }
7081
7082     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7083         defaultPromoChoice = promoSweep;
7084         promoSweep = EmptySquare;   // terminate sweep
7085         promoDefaultAltered = TRUE;
7086         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7087     }
7088
7089     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7090         if(clickType == Release) return; // ignore upclick of click-click destination
7091         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7092         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7093         if(gameInfo.holdingsWidth &&
7094                 (WhiteOnMove(currentMove)
7095                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7096                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7097             // click in right holdings, for determining promotion piece
7098             ChessSquare p = boards[currentMove][y][x];
7099             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7100             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7101             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7102                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7103                 fromX = fromY = -1;
7104                 return;
7105             }
7106         }
7107         DrawPosition(FALSE, boards[currentMove]);
7108         return;
7109     }
7110
7111     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7112     if(clickType == Press
7113             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7114               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7115               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7116         return;
7117
7118     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7119         // could be static click on premove from-square: abort premove
7120         gotPremove = 0;
7121         ClearPremoveHighlights();
7122     }
7123
7124     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7125         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7126
7127     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7128         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7129                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7130         defaultPromoChoice = DefaultPromoChoice(side);
7131     }
7132
7133     autoQueen = appData.alwaysPromoteToQueen;
7134
7135     if (fromX == -1) {
7136       int originalY = y;
7137       gatingPiece = EmptySquare;
7138       if (clickType != Press) {
7139         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7140             DragPieceEnd(xPix, yPix); dragging = 0;
7141             DrawPosition(FALSE, NULL);
7142         }
7143         return;
7144       }
7145       doubleClick = FALSE;
7146       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7147         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7148       }
7149       fromX = x; fromY = y; toX = toY = -1;
7150       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7151          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7152          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7153             /* First square */
7154             if (OKToStartUserMove(fromX, fromY)) {
7155                 second = 0;
7156                 MarkTargetSquares(0);
7157                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7158                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7159                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7160                     promoSweep = defaultPromoChoice;
7161                     selectFlag = 0; lastX = xPix; lastY = yPix;
7162                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7163                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7164                 }
7165                 if (appData.highlightDragging) {
7166                     SetHighlights(fromX, fromY, -1, -1);
7167                 } else {
7168                     ClearHighlights();
7169                 }
7170             } else fromX = fromY = -1;
7171             return;
7172         }
7173     }
7174
7175     /* fromX != -1 */
7176     if (clickType == Press && gameMode != EditPosition) {
7177         ChessSquare fromP;
7178         ChessSquare toP;
7179         int frc;
7180
7181         // ignore off-board to clicks
7182         if(y < 0 || x < 0) return;
7183
7184         /* Check if clicking again on the same color piece */
7185         fromP = boards[currentMove][fromY][fromX];
7186         toP = boards[currentMove][y][x];
7187         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7188         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7189              WhitePawn <= toP && toP <= WhiteKing &&
7190              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7191              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7192             (BlackPawn <= fromP && fromP <= BlackKing &&
7193              BlackPawn <= toP && toP <= BlackKing &&
7194              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7195              !(fromP == BlackKing && toP == BlackRook && frc))) {
7196             /* Clicked again on same color piece -- changed his mind */
7197             second = (x == fromX && y == fromY);
7198             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7199                 second = FALSE; // first double-click rather than scond click
7200                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7201             }
7202             promoDefaultAltered = FALSE;
7203             MarkTargetSquares(1);
7204            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7205             if (appData.highlightDragging) {
7206                 SetHighlights(x, y, -1, -1);
7207             } else {
7208                 ClearHighlights();
7209             }
7210             if (OKToStartUserMove(x, y)) {
7211                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7212                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7213                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7214                  gatingPiece = boards[currentMove][fromY][fromX];
7215                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7216                 fromX = x;
7217                 fromY = y; dragging = 1;
7218                 MarkTargetSquares(0);
7219                 DragPieceBegin(xPix, yPix, FALSE);
7220                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7221                     promoSweep = defaultPromoChoice;
7222                     selectFlag = 0; lastX = xPix; lastY = yPix;
7223                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7224                 }
7225             }
7226            }
7227            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7228            second = FALSE;
7229         }
7230         // ignore clicks on holdings
7231         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7232     }
7233
7234     if (clickType == Release && x == fromX && y == fromY) {
7235         DragPieceEnd(xPix, yPix); dragging = 0;
7236         if(clearFlag) {
7237             // a deferred attempt to click-click move an empty square on top of a piece
7238             boards[currentMove][y][x] = EmptySquare;
7239             ClearHighlights();
7240             DrawPosition(FALSE, boards[currentMove]);
7241             fromX = fromY = -1; clearFlag = 0;
7242             return;
7243         }
7244         if (appData.animateDragging) {
7245             /* Undo animation damage if any */
7246             DrawPosition(FALSE, NULL);
7247         }
7248         if (second || sweepSelecting) {
7249             /* Second up/down in same square; just abort move */
7250             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7251             second = sweepSelecting = 0;
7252             fromX = fromY = -1;
7253             gatingPiece = EmptySquare;
7254             ClearHighlights();
7255             gotPremove = 0;
7256             ClearPremoveHighlights();
7257         } else {
7258             /* First upclick in same square; start click-click mode */
7259             SetHighlights(x, y, -1, -1);
7260         }
7261         return;
7262     }
7263
7264     clearFlag = 0;
7265
7266     /* we now have a different from- and (possibly off-board) to-square */
7267     /* Completed move */
7268     if(!sweepSelecting) {
7269         toX = x;
7270         toY = y;
7271     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7272
7273     saveAnimate = appData.animate;
7274     if (clickType == Press) {
7275         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7276             // must be Edit Position mode with empty-square selected
7277             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7278             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7279             return;
7280         }
7281         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7282           if(appData.sweepSelect) {
7283             ChessSquare piece = boards[currentMove][fromY][fromX];
7284             promoSweep = defaultPromoChoice;
7285             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7286             selectFlag = 0; lastX = xPix; lastY = yPix;
7287             Sweep(0); // Pawn that is going to promote: preview promotion piece
7288             sweepSelecting = 1;
7289             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7290             MarkTargetSquares(1);
7291           }
7292           return; // promo popup appears on up-click
7293         }
7294         /* Finish clickclick move */
7295         if (appData.animate || appData.highlightLastMove) {
7296             SetHighlights(fromX, fromY, toX, toY);
7297         } else {
7298             ClearHighlights();
7299         }
7300     } else {
7301 #if 0
7302 // [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
7303         /* Finish drag move */
7304         if (appData.highlightLastMove) {
7305             SetHighlights(fromX, fromY, toX, toY);
7306         } else {
7307             ClearHighlights();
7308         }
7309 #endif
7310         DragPieceEnd(xPix, yPix); dragging = 0;
7311         /* Don't animate move and drag both */
7312         appData.animate = FALSE;
7313     }
7314
7315     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7316     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7317         ChessSquare piece = boards[currentMove][fromY][fromX];
7318         if(gameMode == EditPosition && piece != EmptySquare &&
7319            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7320             int n;
7321
7322             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7323                 n = PieceToNumber(piece - (int)BlackPawn);
7324                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7325                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7326                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7327             } else
7328             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7329                 n = PieceToNumber(piece);
7330                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7331                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7332                 boards[currentMove][n][BOARD_WIDTH-2]++;
7333             }
7334             boards[currentMove][fromY][fromX] = EmptySquare;
7335         }
7336         ClearHighlights();
7337         fromX = fromY = -1;
7338         MarkTargetSquares(1);
7339         DrawPosition(TRUE, boards[currentMove]);
7340         return;
7341     }
7342
7343     // off-board moves should not be highlighted
7344     if(x < 0 || y < 0) ClearHighlights();
7345
7346     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7347
7348     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7349         SetHighlights(fromX, fromY, toX, toY);
7350         MarkTargetSquares(1);
7351         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7352             // [HGM] super: promotion to captured piece selected from holdings
7353             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7354             promotionChoice = TRUE;
7355             // kludge follows to temporarily execute move on display, without promoting yet
7356             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7357             boards[currentMove][toY][toX] = p;
7358             DrawPosition(FALSE, boards[currentMove]);
7359             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7360             boards[currentMove][toY][toX] = q;
7361             DisplayMessage("Click in holdings to choose piece", "");
7362             return;
7363         }
7364         PromotionPopUp();
7365     } else {
7366         int oldMove = currentMove;
7367         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7368         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7369         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7370         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7371            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7372             DrawPosition(TRUE, boards[currentMove]);
7373         MarkTargetSquares(1);
7374         fromX = fromY = -1;
7375     }
7376     appData.animate = saveAnimate;
7377     if (appData.animate || appData.animateDragging) {
7378         /* Undo animation damage if needed */
7379         DrawPosition(FALSE, NULL);
7380     }
7381 }
7382
7383 int
7384 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7385 {   // front-end-free part taken out of PieceMenuPopup
7386     int whichMenu; int xSqr, ySqr;
7387
7388     if(seekGraphUp) { // [HGM] seekgraph
7389         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7390         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7391         return -2;
7392     }
7393
7394     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7395          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7396         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7397         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7398         if(action == Press)   {
7399             originalFlip = flipView;
7400             flipView = !flipView; // temporarily flip board to see game from partners perspective
7401             DrawPosition(TRUE, partnerBoard);
7402             DisplayMessage(partnerStatus, "");
7403             partnerUp = TRUE;
7404         } else if(action == Release) {
7405             flipView = originalFlip;
7406             DrawPosition(TRUE, boards[currentMove]);
7407             partnerUp = FALSE;
7408         }
7409         return -2;
7410     }
7411
7412     xSqr = EventToSquare(x, BOARD_WIDTH);
7413     ySqr = EventToSquare(y, BOARD_HEIGHT);
7414     if (action == Release) {
7415         if(pieceSweep != EmptySquare) {
7416             EditPositionMenuEvent(pieceSweep, toX, toY);
7417             pieceSweep = EmptySquare;
7418         } else UnLoadPV(); // [HGM] pv
7419     }
7420     if (action != Press) return -2; // return code to be ignored
7421     switch (gameMode) {
7422       case IcsExamining:
7423         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7424       case EditPosition:
7425         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7426         if (xSqr < 0 || ySqr < 0) return -1;
7427         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7428         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7429         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7430         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7431         NextPiece(0);
7432         return 2; // grab
7433       case IcsObserving:
7434         if(!appData.icsEngineAnalyze) return -1;
7435       case IcsPlayingWhite:
7436       case IcsPlayingBlack:
7437         if(!appData.zippyPlay) goto noZip;
7438       case AnalyzeMode:
7439       case AnalyzeFile:
7440       case MachinePlaysWhite:
7441       case MachinePlaysBlack:
7442       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7443         if (!appData.dropMenu) {
7444           LoadPV(x, y);
7445           return 2; // flag front-end to grab mouse events
7446         }
7447         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7448            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7449       case EditGame:
7450       noZip:
7451         if (xSqr < 0 || ySqr < 0) return -1;
7452         if (!appData.dropMenu || appData.testLegality &&
7453             gameInfo.variant != VariantBughouse &&
7454             gameInfo.variant != VariantCrazyhouse) return -1;
7455         whichMenu = 1; // drop menu
7456         break;
7457       default:
7458         return -1;
7459     }
7460
7461     if (((*fromX = xSqr) < 0) ||
7462         ((*fromY = ySqr) < 0)) {
7463         *fromX = *fromY = -1;
7464         return -1;
7465     }
7466     if (flipView)
7467       *fromX = BOARD_WIDTH - 1 - *fromX;
7468     else
7469       *fromY = BOARD_HEIGHT - 1 - *fromY;
7470
7471     return whichMenu;
7472 }
7473
7474 void
7475 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7476 {
7477 //    char * hint = lastHint;
7478     FrontEndProgramStats stats;
7479
7480     stats.which = cps == &first ? 0 : 1;
7481     stats.depth = cpstats->depth;
7482     stats.nodes = cpstats->nodes;
7483     stats.score = cpstats->score;
7484     stats.time = cpstats->time;
7485     stats.pv = cpstats->movelist;
7486     stats.hint = lastHint;
7487     stats.an_move_index = 0;
7488     stats.an_move_count = 0;
7489
7490     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7491         stats.hint = cpstats->move_name;
7492         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7493         stats.an_move_count = cpstats->nr_moves;
7494     }
7495
7496     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
7497
7498     SetProgramStats( &stats );
7499 }
7500
7501 void
7502 ClearEngineOutputPane (int which)
7503 {
7504     static FrontEndProgramStats dummyStats;
7505     dummyStats.which = which;
7506     dummyStats.pv = "#";
7507     SetProgramStats( &dummyStats );
7508 }
7509
7510 #define MAXPLAYERS 500
7511
7512 char *
7513 TourneyStandings (int display)
7514 {
7515     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7516     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7517     char result, *p, *names[MAXPLAYERS];
7518
7519     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7520         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7521     names[0] = p = strdup(appData.participants);
7522     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7523
7524     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7525
7526     while(result = appData.results[nr]) {
7527         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7528         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7529         wScore = bScore = 0;
7530         switch(result) {
7531           case '+': wScore = 2; break;
7532           case '-': bScore = 2; break;
7533           case '=': wScore = bScore = 1; break;
7534           case ' ':
7535           case '*': return strdup("busy"); // tourney not finished
7536         }
7537         score[w] += wScore;
7538         score[b] += bScore;
7539         games[w]++;
7540         games[b]++;
7541         nr++;
7542     }
7543     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7544     for(w=0; w<nPlayers; w++) {
7545         bScore = -1;
7546         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7547         ranking[w] = b; points[w] = bScore; score[b] = -2;
7548     }
7549     p = malloc(nPlayers*34+1);
7550     for(w=0; w<nPlayers && w<display; w++)
7551         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7552     free(names[0]);
7553     return p;
7554 }
7555
7556 void
7557 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7558 {       // count all piece types
7559         int p, f, r;
7560         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7561         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7562         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7563                 p = board[r][f];
7564                 pCnt[p]++;
7565                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7566                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7567                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7568                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7569                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7570                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7571         }
7572 }
7573
7574 int
7575 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7576 {
7577         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7578         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7579
7580         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7581         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7582         if(myPawns == 2 && nMine == 3) // KPP
7583             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7584         if(myPawns == 1 && nMine == 2) // KP
7585             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7586         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7587             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7588         if(myPawns) return FALSE;
7589         if(pCnt[WhiteRook+side])
7590             return pCnt[BlackRook-side] ||
7591                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7592                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7593                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7594         if(pCnt[WhiteCannon+side]) {
7595             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7596             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7597         }
7598         if(pCnt[WhiteKnight+side])
7599             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7600         return FALSE;
7601 }
7602
7603 int
7604 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7605 {
7606         VariantClass v = gameInfo.variant;
7607
7608         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7609         if(v == VariantShatranj) return TRUE; // always winnable through baring
7610         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7611         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7612
7613         if(v == VariantXiangqi) {
7614                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7615
7616                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7617                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7618                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7619                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7620                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7621                 if(stale) // we have at least one last-rank P plus perhaps C
7622                     return majors // KPKX
7623                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7624                 else // KCA*E*
7625                     return pCnt[WhiteFerz+side] // KCAK
7626                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7627                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7628                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7629
7630         } else if(v == VariantKnightmate) {
7631                 if(nMine == 1) return FALSE;
7632                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7633         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7634                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7635
7636                 if(nMine == 1) return FALSE; // bare King
7637                 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
7638                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7639                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7640                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7641                 if(pCnt[WhiteKnight+side])
7642                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7643                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7644                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7645                 if(nBishops)
7646                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7647                 if(pCnt[WhiteAlfil+side])
7648                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7649                 if(pCnt[WhiteWazir+side])
7650                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7651         }
7652
7653         return TRUE;
7654 }
7655
7656 int
7657 CompareWithRights (Board b1, Board b2)
7658 {
7659     int rights = 0;
7660     if(!CompareBoards(b1, b2)) return FALSE;
7661     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7662     /* compare castling rights */
7663     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7664            rights++; /* King lost rights, while rook still had them */
7665     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7666         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7667            rights++; /* but at least one rook lost them */
7668     }
7669     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7670            rights++;
7671     if( b1[CASTLING][5] != NoRights ) {
7672         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7673            rights++;
7674     }
7675     return rights == 0;
7676 }
7677
7678 int
7679 Adjudicate (ChessProgramState *cps)
7680 {       // [HGM] some adjudications useful with buggy engines
7681         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7682         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7683         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7684         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7685         int k, drop, count = 0; static int bare = 1;
7686         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7687         Boolean canAdjudicate = !appData.icsActive;
7688
7689         // most tests only when we understand the game, i.e. legality-checking on
7690             if( appData.testLegality )
7691             {   /* [HGM] Some more adjudications for obstinate engines */
7692                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7693                 static int moveCount = 6;
7694                 ChessMove result;
7695                 char *reason = NULL;
7696
7697                 /* Count what is on board. */
7698                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7699
7700                 /* Some material-based adjudications that have to be made before stalemate test */
7701                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7702                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7703                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7704                      if(canAdjudicate && appData.checkMates) {
7705                          if(engineOpponent)
7706                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7707                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7708                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7709                          return 1;
7710                      }
7711                 }
7712
7713                 /* Bare King in Shatranj (loses) or Losers (wins) */
7714                 if( nrW == 1 || nrB == 1) {
7715                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7716                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7717                      if(canAdjudicate && appData.checkMates) {
7718                          if(engineOpponent)
7719                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7720                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7721                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7722                          return 1;
7723                      }
7724                   } else
7725                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7726                   {    /* bare King */
7727                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7728                         if(canAdjudicate && appData.checkMates) {
7729                             /* but only adjudicate if adjudication enabled */
7730                             if(engineOpponent)
7731                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7732                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7733                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7734                             return 1;
7735                         }
7736                   }
7737                 } else bare = 1;
7738
7739
7740             // don't wait for engine to announce game end if we can judge ourselves
7741             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7742               case MT_CHECK:
7743                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7744                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7745                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7746                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7747                             checkCnt++;
7748                         if(checkCnt >= 2) {
7749                             reason = "Xboard adjudication: 3rd check";
7750                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7751                             break;
7752                         }
7753                     }
7754                 }
7755               case MT_NONE:
7756               default:
7757                 break;
7758               case MT_STALEMATE:
7759               case MT_STAINMATE:
7760                 reason = "Xboard adjudication: Stalemate";
7761                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7762                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7763                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7764                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7765                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7766                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7767                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7768                                                                         EP_CHECKMATE : EP_WINS);
7769                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7770                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7771                 }
7772                 break;
7773               case MT_CHECKMATE:
7774                 reason = "Xboard adjudication: Checkmate";
7775                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7776                 if(gameInfo.variant == VariantShogi) {
7777                     if(forwardMostMove > backwardMostMove
7778                        && moveList[forwardMostMove-1][1] == '@'
7779                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7780                         reason = "XBoard adjudication: pawn-drop mate";
7781                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7782                     }
7783                 }
7784                 break;
7785             }
7786
7787                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7788                     case EP_STALEMATE:
7789                         result = GameIsDrawn; break;
7790                     case EP_CHECKMATE:
7791                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7792                     case EP_WINS:
7793                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7794                     default:
7795                         result = EndOfFile;
7796                 }
7797                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7798                     if(engineOpponent)
7799                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7800                     GameEnds( result, reason, GE_XBOARD );
7801                     return 1;
7802                 }
7803
7804                 /* Next absolutely insufficient mating material. */
7805                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7806                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7807                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7808
7809                      /* always flag draws, for judging claims */
7810                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7811
7812                      if(canAdjudicate && appData.materialDraws) {
7813                          /* but only adjudicate them if adjudication enabled */
7814                          if(engineOpponent) {
7815                            SendToProgram("force\n", engineOpponent); // suppress reply
7816                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7817                          }
7818                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7819                          return 1;
7820                      }
7821                 }
7822
7823                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7824                 if(gameInfo.variant == VariantXiangqi ?
7825                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7826                  : nrW + nrB == 4 &&
7827                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7828                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7829                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7830                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7831                    ) ) {
7832                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7833                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7834                           if(engineOpponent) {
7835                             SendToProgram("force\n", engineOpponent); // suppress reply
7836                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7837                           }
7838                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7839                           return 1;
7840                      }
7841                 } else moveCount = 6;
7842             }
7843
7844         // Repetition draws and 50-move rule can be applied independently of legality testing
7845
7846                 /* Check for rep-draws */
7847                 count = 0;
7848                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7849                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7850                 for(k = forwardMostMove-2;
7851                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7852                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7853                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7854                     k-=2)
7855                 {   int rights=0;
7856                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7857                         /* compare castling rights */
7858                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7859                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7860                                 rights++; /* King lost rights, while rook still had them */
7861                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7862                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7863                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7864                                    rights++; /* but at least one rook lost them */
7865                         }
7866                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7867                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7868                                 rights++;
7869                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7870                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7871                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7872                                    rights++;
7873                         }
7874                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7875                             && appData.drawRepeats > 1) {
7876                              /* adjudicate after user-specified nr of repeats */
7877                              int result = GameIsDrawn;
7878                              char *details = "XBoard adjudication: repetition draw";
7879                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7880                                 // [HGM] xiangqi: check for forbidden perpetuals
7881                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7882                                 for(m=forwardMostMove; m>k; m-=2) {
7883                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7884                                         ourPerpetual = 0; // the current mover did not always check
7885                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7886                                         hisPerpetual = 0; // the opponent did not always check
7887                                 }
7888                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7889                                                                         ourPerpetual, hisPerpetual);
7890                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7891                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7892                                     details = "Xboard adjudication: perpetual checking";
7893                                 } else
7894                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7895                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7896                                 } else
7897                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7898                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7899                                         result = BlackWins;
7900                                         details = "Xboard adjudication: repetition";
7901                                     }
7902                                 } else // it must be XQ
7903                                 // Now check for perpetual chases
7904                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7905                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7906                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7907                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7908                                         static char resdet[MSG_SIZ];
7909                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7910                                         details = resdet;
7911                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7912                                     } else
7913                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7914                                         break; // Abort repetition-checking loop.
7915                                 }
7916                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7917                              }
7918                              if(engineOpponent) {
7919                                SendToProgram("force\n", engineOpponent); // suppress reply
7920                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7921                              }
7922                              GameEnds( result, details, GE_XBOARD );
7923                              return 1;
7924                         }
7925                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7926                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7927                     }
7928                 }
7929
7930                 /* Now we test for 50-move draws. Determine ply count */
7931                 count = forwardMostMove;
7932                 /* look for last irreversble move */
7933                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7934                     count--;
7935                 /* if we hit starting position, add initial plies */
7936                 if( count == backwardMostMove )
7937                     count -= initialRulePlies;
7938                 count = forwardMostMove - count;
7939                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7940                         // adjust reversible move counter for checks in Xiangqi
7941                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7942                         if(i < backwardMostMove) i = backwardMostMove;
7943                         while(i <= forwardMostMove) {
7944                                 lastCheck = inCheck; // check evasion does not count
7945                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7946                                 if(inCheck || lastCheck) count--; // check does not count
7947                                 i++;
7948                         }
7949                 }
7950                 if( count >= 100)
7951                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7952                          /* this is used to judge if draw claims are legal */
7953                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7954                          if(engineOpponent) {
7955                            SendToProgram("force\n", engineOpponent); // suppress reply
7956                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7957                          }
7958                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7959                          return 1;
7960                 }
7961
7962                 /* if draw offer is pending, treat it as a draw claim
7963                  * when draw condition present, to allow engines a way to
7964                  * claim draws before making their move to avoid a race
7965                  * condition occurring after their move
7966                  */
7967                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7968                          char *p = NULL;
7969                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7970                              p = "Draw claim: 50-move rule";
7971                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7972                              p = "Draw claim: 3-fold repetition";
7973                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7974                              p = "Draw claim: insufficient mating material";
7975                          if( p != NULL && canAdjudicate) {
7976                              if(engineOpponent) {
7977                                SendToProgram("force\n", engineOpponent); // suppress reply
7978                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7979                              }
7980                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7981                              return 1;
7982                          }
7983                 }
7984
7985                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7986                     if(engineOpponent) {
7987                       SendToProgram("force\n", engineOpponent); // suppress reply
7988                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7989                     }
7990                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7991                     return 1;
7992                 }
7993         return 0;
7994 }
7995
7996 char *
7997 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7998 {   // [HGM] book: this routine intercepts moves to simulate book replies
7999     char *bookHit = NULL;
8000
8001     //first determine if the incoming move brings opponent into his book
8002     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8003         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8004     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8005     if(bookHit != NULL && !cps->bookSuspend) {
8006         // make sure opponent is not going to reply after receiving move to book position
8007         SendToProgram("force\n", cps);
8008         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8009     }
8010     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8011     // now arrange restart after book miss
8012     if(bookHit) {
8013         // after a book hit we never send 'go', and the code after the call to this routine
8014         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8015         char buf[MSG_SIZ], *move = bookHit;
8016         if(cps->useSAN) {
8017             int fromX, fromY, toX, toY;
8018             char promoChar;
8019             ChessMove moveType;
8020             move = buf + 30;
8021             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8022                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8023                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8024                                     PosFlags(forwardMostMove),
8025                                     fromY, fromX, toY, toX, promoChar, move);
8026             } else {
8027                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8028                 bookHit = NULL;
8029             }
8030         }
8031         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8032         SendToProgram(buf, cps);
8033         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8034     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8035         SendToProgram("go\n", cps);
8036         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8037     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8038         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8039             SendToProgram("go\n", cps);
8040         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8041     }
8042     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8043 }
8044
8045 int
8046 LoadError (char *errmess, ChessProgramState *cps)
8047 {   // unloads engine and switches back to -ncp mode if it was first
8048     if(cps->initDone) return FALSE;
8049     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8050     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8051     cps->pr = NoProc;
8052     if(cps == &first) {
8053         appData.noChessProgram = TRUE;
8054         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8055         gameMode = BeginningOfGame; ModeHighlight();
8056         SetNCPMode();
8057     }
8058     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8059     DisplayMessage("", ""); // erase waiting message
8060     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8061     return TRUE;
8062 }
8063
8064 char *savedMessage;
8065 ChessProgramState *savedState;
8066 void
8067 DeferredBookMove (void)
8068 {
8069         if(savedState->lastPing != savedState->lastPong)
8070                     ScheduleDelayedEvent(DeferredBookMove, 10);
8071         else
8072         HandleMachineMove(savedMessage, savedState);
8073 }
8074
8075 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8076 static ChessProgramState *stalledEngine;
8077 static char stashedInputMove[MSG_SIZ];
8078
8079 void
8080 HandleMachineMove (char *message, ChessProgramState *cps)
8081 {
8082     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8083     char realname[MSG_SIZ];
8084     int fromX, fromY, toX, toY;
8085     ChessMove moveType;
8086     char promoChar;
8087     char *p, *pv=buf1;
8088     int machineWhite, oldError;
8089     char *bookHit;
8090
8091     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8092         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8093         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8094             DisplayError(_("Invalid pairing from pairing engine"), 0);
8095             return;
8096         }
8097         pairingReceived = 1;
8098         NextMatchGame();
8099         return; // Skim the pairing messages here.
8100     }
8101
8102     oldError = cps->userError; cps->userError = 0;
8103
8104 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8105     /*
8106      * Kludge to ignore BEL characters
8107      */
8108     while (*message == '\007') message++;
8109
8110     /*
8111      * [HGM] engine debug message: ignore lines starting with '#' character
8112      */
8113     if(cps->debug && *message == '#') return;
8114
8115     /*
8116      * Look for book output
8117      */
8118     if (cps == &first && bookRequested) {
8119         if (message[0] == '\t' || message[0] == ' ') {
8120             /* Part of the book output is here; append it */
8121             strcat(bookOutput, message);
8122             strcat(bookOutput, "  \n");
8123             return;
8124         } else if (bookOutput[0] != NULLCHAR) {
8125             /* All of book output has arrived; display it */
8126             char *p = bookOutput;
8127             while (*p != NULLCHAR) {
8128                 if (*p == '\t') *p = ' ';
8129                 p++;
8130             }
8131             DisplayInformation(bookOutput);
8132             bookRequested = FALSE;
8133             /* Fall through to parse the current output */
8134         }
8135     }
8136
8137     /*
8138      * Look for machine move.
8139      */
8140     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8141         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8142     {
8143         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8144             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8145             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8146             stalledEngine = cps;
8147             if(appData.ponderNextMove) { // bring opponent out of ponder
8148                 if(gameMode == TwoMachinesPlay) {
8149                     if(cps->other->pause)
8150                         PauseEngine(cps->other);
8151                     else
8152                         SendToProgram("easy\n", cps->other);
8153                 }
8154             }
8155             StopClocks();
8156             return;
8157         }
8158
8159         /* This method is only useful on engines that support ping */
8160         if (cps->lastPing != cps->lastPong) {
8161           if (gameMode == BeginningOfGame) {
8162             /* Extra move from before last new; ignore */
8163             if (appData.debugMode) {
8164                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8165             }
8166           } else {
8167             if (appData.debugMode) {
8168                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8169                         cps->which, gameMode);
8170             }
8171
8172             SendToProgram("undo\n", cps);
8173           }
8174           return;
8175         }
8176
8177         switch (gameMode) {
8178           case BeginningOfGame:
8179             /* Extra move from before last reset; ignore */
8180             if (appData.debugMode) {
8181                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8182             }
8183             return;
8184
8185           case EndOfGame:
8186           case IcsIdle:
8187           default:
8188             /* Extra move after we tried to stop.  The mode test is
8189                not a reliable way of detecting this problem, but it's
8190                the best we can do on engines that don't support ping.
8191             */
8192             if (appData.debugMode) {
8193                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8194                         cps->which, gameMode);
8195             }
8196             SendToProgram("undo\n", cps);
8197             return;
8198
8199           case MachinePlaysWhite:
8200           case IcsPlayingWhite:
8201             machineWhite = TRUE;
8202             break;
8203
8204           case MachinePlaysBlack:
8205           case IcsPlayingBlack:
8206             machineWhite = FALSE;
8207             break;
8208
8209           case TwoMachinesPlay:
8210             machineWhite = (cps->twoMachinesColor[0] == 'w');
8211             break;
8212         }
8213         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8214             if (appData.debugMode) {
8215                 fprintf(debugFP,
8216                         "Ignoring move out of turn by %s, gameMode %d"
8217                         ", forwardMost %d\n",
8218                         cps->which, gameMode, forwardMostMove);
8219             }
8220             return;
8221         }
8222
8223         if(cps->alphaRank) AlphaRank(machineMove, 4);
8224         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8225                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8226             /* Machine move could not be parsed; ignore it. */
8227           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8228                     machineMove, _(cps->which));
8229             DisplayMoveError(buf1);
8230             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8231                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8232             if (gameMode == TwoMachinesPlay) {
8233               GameEnds(machineWhite ? BlackWins : WhiteWins,
8234                        buf1, GE_XBOARD);
8235             }
8236             return;
8237         }
8238
8239         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8240         /* So we have to redo legality test with true e.p. status here,  */
8241         /* to make sure an illegal e.p. capture does not slip through,   */
8242         /* to cause a forfeit on a justified illegal-move complaint      */
8243         /* of the opponent.                                              */
8244         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8245            ChessMove moveType;
8246            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8247                              fromY, fromX, toY, toX, promoChar);
8248             if(moveType == IllegalMove) {
8249               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8250                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8251                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8252                            buf1, GE_XBOARD);
8253                 return;
8254            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8255            /* [HGM] Kludge to handle engines that send FRC-style castling
8256               when they shouldn't (like TSCP-Gothic) */
8257            switch(moveType) {
8258              case WhiteASideCastleFR:
8259              case BlackASideCastleFR:
8260                toX+=2;
8261                currentMoveString[2]++;
8262                break;
8263              case WhiteHSideCastleFR:
8264              case BlackHSideCastleFR:
8265                toX--;
8266                currentMoveString[2]--;
8267                break;
8268              default: ; // nothing to do, but suppresses warning of pedantic compilers
8269            }
8270         }
8271         hintRequested = FALSE;
8272         lastHint[0] = NULLCHAR;
8273         bookRequested = FALSE;
8274         /* Program may be pondering now */
8275         cps->maybeThinking = TRUE;
8276         if (cps->sendTime == 2) cps->sendTime = 1;
8277         if (cps->offeredDraw) cps->offeredDraw--;
8278
8279         /* [AS] Save move info*/
8280         pvInfoList[ forwardMostMove ].score = programStats.score;
8281         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8282         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8283
8284         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8285
8286         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8287         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8288             int count = 0;
8289
8290             while( count < adjudicateLossPlies ) {
8291                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8292
8293                 if( count & 1 ) {
8294                     score = -score; /* Flip score for winning side */
8295                 }
8296
8297                 if( score > adjudicateLossThreshold ) {
8298                     break;
8299                 }
8300
8301                 count++;
8302             }
8303
8304             if( count >= adjudicateLossPlies ) {
8305                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8306
8307                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8308                     "Xboard adjudication",
8309                     GE_XBOARD );
8310
8311                 return;
8312             }
8313         }
8314
8315         if(Adjudicate(cps)) {
8316             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8317             return; // [HGM] adjudicate: for all automatic game ends
8318         }
8319
8320 #if ZIPPY
8321         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8322             first.initDone) {
8323           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8324                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8325                 SendToICS("draw ");
8326                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8327           }
8328           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8329           ics_user_moved = 1;
8330           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8331                 char buf[3*MSG_SIZ];
8332
8333                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8334                         programStats.score / 100.,
8335                         programStats.depth,
8336                         programStats.time / 100.,
8337                         (unsigned int)programStats.nodes,
8338                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8339                         programStats.movelist);
8340                 SendToICS(buf);
8341 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8342           }
8343         }
8344 #endif
8345
8346         /* [AS] Clear stats for next move */
8347         ClearProgramStats();
8348         thinkOutput[0] = NULLCHAR;
8349         hiddenThinkOutputState = 0;
8350
8351         bookHit = NULL;
8352         if (gameMode == TwoMachinesPlay) {
8353             /* [HGM] relaying draw offers moved to after reception of move */
8354             /* and interpreting offer as claim if it brings draw condition */
8355             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8356                 SendToProgram("draw\n", cps->other);
8357             }
8358             if (cps->other->sendTime) {
8359                 SendTimeRemaining(cps->other,
8360                                   cps->other->twoMachinesColor[0] == 'w');
8361             }
8362             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8363             if (firstMove && !bookHit) {
8364                 firstMove = FALSE;
8365                 if (cps->other->useColors) {
8366                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8367                 }
8368                 SendToProgram("go\n", cps->other);
8369             }
8370             cps->other->maybeThinking = TRUE;
8371         }
8372
8373         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8374
8375         if (!pausing && appData.ringBellAfterMoves) {
8376             RingBell();
8377         }
8378
8379         /*
8380          * Reenable menu items that were disabled while
8381          * machine was thinking
8382          */
8383         if (gameMode != TwoMachinesPlay)
8384             SetUserThinkingEnables();
8385
8386         // [HGM] book: after book hit opponent has received move and is now in force mode
8387         // force the book reply into it, and then fake that it outputted this move by jumping
8388         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8389         if(bookHit) {
8390                 static char bookMove[MSG_SIZ]; // a bit generous?
8391
8392                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8393                 strcat(bookMove, bookHit);
8394                 message = bookMove;
8395                 cps = cps->other;
8396                 programStats.nodes = programStats.depth = programStats.time =
8397                 programStats.score = programStats.got_only_move = 0;
8398                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8399
8400                 if(cps->lastPing != cps->lastPong) {
8401                     savedMessage = message; // args for deferred call
8402                     savedState = cps;
8403                     ScheduleDelayedEvent(DeferredBookMove, 10);
8404                     return;
8405                 }
8406                 goto FakeBookMove;
8407         }
8408
8409         return;
8410     }
8411
8412     /* Set special modes for chess engines.  Later something general
8413      *  could be added here; for now there is just one kludge feature,
8414      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8415      *  when "xboard" is given as an interactive command.
8416      */
8417     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8418         cps->useSigint = FALSE;
8419         cps->useSigterm = FALSE;
8420     }
8421     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8422       ParseFeatures(message+8, cps);
8423       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8424     }
8425
8426     if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8427                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8428       int dummy, s=6; char buf[MSG_SIZ];
8429       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8430       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8431       if(startedFromSetupPosition) return;
8432       ParseFEN(boards[0], &dummy, message+s);
8433       DrawPosition(TRUE, boards[0]);
8434       startedFromSetupPosition = TRUE;
8435       return;
8436     }
8437     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8438      * want this, I was asked to put it in, and obliged.
8439      */
8440     if (!strncmp(message, "setboard ", 9)) {
8441         Board initial_position;
8442
8443         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8444
8445         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8446             DisplayError(_("Bad FEN received from engine"), 0);
8447             return ;
8448         } else {
8449            Reset(TRUE, FALSE);
8450            CopyBoard(boards[0], initial_position);
8451            initialRulePlies = FENrulePlies;
8452            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8453            else gameMode = MachinePlaysBlack;
8454            DrawPosition(FALSE, boards[currentMove]);
8455         }
8456         return;
8457     }
8458
8459     /*
8460      * Look for communication commands
8461      */
8462     if (!strncmp(message, "telluser ", 9)) {
8463         if(message[9] == '\\' && message[10] == '\\')
8464             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8465         PlayTellSound();
8466         DisplayNote(message + 9);
8467         return;
8468     }
8469     if (!strncmp(message, "tellusererror ", 14)) {
8470         cps->userError = 1;
8471         if(message[14] == '\\' && message[15] == '\\')
8472             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8473         PlayTellSound();
8474         DisplayError(message + 14, 0);
8475         return;
8476     }
8477     if (!strncmp(message, "tellopponent ", 13)) {
8478       if (appData.icsActive) {
8479         if (loggedOn) {
8480           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8481           SendToICS(buf1);
8482         }
8483       } else {
8484         DisplayNote(message + 13);
8485       }
8486       return;
8487     }
8488     if (!strncmp(message, "tellothers ", 11)) {
8489       if (appData.icsActive) {
8490         if (loggedOn) {
8491           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8492           SendToICS(buf1);
8493         }
8494       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8495       return;
8496     }
8497     if (!strncmp(message, "tellall ", 8)) {
8498       if (appData.icsActive) {
8499         if (loggedOn) {
8500           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8501           SendToICS(buf1);
8502         }
8503       } else {
8504         DisplayNote(message + 8);
8505       }
8506       return;
8507     }
8508     if (strncmp(message, "warning", 7) == 0) {
8509         /* Undocumented feature, use tellusererror in new code */
8510         DisplayError(message, 0);
8511         return;
8512     }
8513     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8514         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8515         strcat(realname, " query");
8516         AskQuestion(realname, buf2, buf1, cps->pr);
8517         return;
8518     }
8519     /* Commands from the engine directly to ICS.  We don't allow these to be
8520      *  sent until we are logged on. Crafty kibitzes have been known to
8521      *  interfere with the login process.
8522      */
8523     if (loggedOn) {
8524         if (!strncmp(message, "tellics ", 8)) {
8525             SendToICS(message + 8);
8526             SendToICS("\n");
8527             return;
8528         }
8529         if (!strncmp(message, "tellicsnoalias ", 15)) {
8530             SendToICS(ics_prefix);
8531             SendToICS(message + 15);
8532             SendToICS("\n");
8533             return;
8534         }
8535         /* The following are for backward compatibility only */
8536         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8537             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8538             SendToICS(ics_prefix);
8539             SendToICS(message);
8540             SendToICS("\n");
8541             return;
8542         }
8543     }
8544     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8545         return;
8546     }
8547     /*
8548      * If the move is illegal, cancel it and redraw the board.
8549      * Also deal with other error cases.  Matching is rather loose
8550      * here to accommodate engines written before the spec.
8551      */
8552     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8553         strncmp(message, "Error", 5) == 0) {
8554         if (StrStr(message, "name") ||
8555             StrStr(message, "rating") || StrStr(message, "?") ||
8556             StrStr(message, "result") || StrStr(message, "board") ||
8557             StrStr(message, "bk") || StrStr(message, "computer") ||
8558             StrStr(message, "variant") || StrStr(message, "hint") ||
8559             StrStr(message, "random") || StrStr(message, "depth") ||
8560             StrStr(message, "accepted")) {
8561             return;
8562         }
8563         if (StrStr(message, "protover")) {
8564           /* Program is responding to input, so it's apparently done
8565              initializing, and this error message indicates it is
8566              protocol version 1.  So we don't need to wait any longer
8567              for it to initialize and send feature commands. */
8568           FeatureDone(cps, 1);
8569           cps->protocolVersion = 1;
8570           return;
8571         }
8572         cps->maybeThinking = FALSE;
8573
8574         if (StrStr(message, "draw")) {
8575             /* Program doesn't have "draw" command */
8576             cps->sendDrawOffers = 0;
8577             return;
8578         }
8579         if (cps->sendTime != 1 &&
8580             (StrStr(message, "time") || StrStr(message, "otim"))) {
8581           /* Program apparently doesn't have "time" or "otim" command */
8582           cps->sendTime = 0;
8583           return;
8584         }
8585         if (StrStr(message, "analyze")) {
8586             cps->analysisSupport = FALSE;
8587             cps->analyzing = FALSE;
8588 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8589             EditGameEvent(); // [HGM] try to preserve loaded game
8590             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8591             DisplayError(buf2, 0);
8592             return;
8593         }
8594         if (StrStr(message, "(no matching move)st")) {
8595           /* Special kludge for GNU Chess 4 only */
8596           cps->stKludge = TRUE;
8597           SendTimeControl(cps, movesPerSession, timeControl,
8598                           timeIncrement, appData.searchDepth,
8599                           searchTime);
8600           return;
8601         }
8602         if (StrStr(message, "(no matching move)sd")) {
8603           /* Special kludge for GNU Chess 4 only */
8604           cps->sdKludge = TRUE;
8605           SendTimeControl(cps, movesPerSession, timeControl,
8606                           timeIncrement, appData.searchDepth,
8607                           searchTime);
8608           return;
8609         }
8610         if (!StrStr(message, "llegal")) {
8611             return;
8612         }
8613         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8614             gameMode == IcsIdle) return;
8615         if (forwardMostMove <= backwardMostMove) return;
8616         if (pausing) PauseEvent();
8617       if(appData.forceIllegal) {
8618             // [HGM] illegal: machine refused move; force position after move into it
8619           SendToProgram("force\n", cps);
8620           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8621                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8622                 // when black is to move, while there might be nothing on a2 or black
8623                 // might already have the move. So send the board as if white has the move.
8624                 // But first we must change the stm of the engine, as it refused the last move
8625                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8626                 if(WhiteOnMove(forwardMostMove)) {
8627                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8628                     SendBoard(cps, forwardMostMove); // kludgeless board
8629                 } else {
8630                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8631                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8632                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8633                 }
8634           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8635             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8636                  gameMode == TwoMachinesPlay)
8637               SendToProgram("go\n", cps);
8638             return;
8639       } else
8640         if (gameMode == PlayFromGameFile) {
8641             /* Stop reading this game file */
8642             gameMode = EditGame;
8643             ModeHighlight();
8644         }
8645         /* [HGM] illegal-move claim should forfeit game when Xboard */
8646         /* only passes fully legal moves                            */
8647         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8648             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8649                                 "False illegal-move claim", GE_XBOARD );
8650             return; // do not take back move we tested as valid
8651         }
8652         currentMove = forwardMostMove-1;
8653         DisplayMove(currentMove-1); /* before DisplayMoveError */
8654         SwitchClocks(forwardMostMove-1); // [HGM] race
8655         DisplayBothClocks();
8656         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8657                 parseList[currentMove], _(cps->which));
8658         DisplayMoveError(buf1);
8659         DrawPosition(FALSE, boards[currentMove]);
8660
8661         SetUserThinkingEnables();
8662         return;
8663     }
8664     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8665         /* Program has a broken "time" command that
8666            outputs a string not ending in newline.
8667            Don't use it. */
8668         cps->sendTime = 0;
8669     }
8670
8671     /*
8672      * If chess program startup fails, exit with an error message.
8673      * Attempts to recover here are futile. [HGM] Well, we try anyway
8674      */
8675     if ((StrStr(message, "unknown host") != NULL)
8676         || (StrStr(message, "No remote directory") != NULL)
8677         || (StrStr(message, "not found") != NULL)
8678         || (StrStr(message, "No such file") != NULL)
8679         || (StrStr(message, "can't alloc") != NULL)
8680         || (StrStr(message, "Permission denied") != NULL)) {
8681
8682         cps->maybeThinking = FALSE;
8683         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8684                 _(cps->which), cps->program, cps->host, message);
8685         RemoveInputSource(cps->isr);
8686         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8687             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8688             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8689         }
8690         return;
8691     }
8692
8693     /*
8694      * Look for hint output
8695      */
8696     if (sscanf(message, "Hint: %s", buf1) == 1) {
8697         if (cps == &first && hintRequested) {
8698             hintRequested = FALSE;
8699             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8700                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8701                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8702                                     PosFlags(forwardMostMove),
8703                                     fromY, fromX, toY, toX, promoChar, buf1);
8704                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8705                 DisplayInformation(buf2);
8706             } else {
8707                 /* Hint move could not be parsed!? */
8708               snprintf(buf2, sizeof(buf2),
8709                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8710                         buf1, _(cps->which));
8711                 DisplayError(buf2, 0);
8712             }
8713         } else {
8714           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8715         }
8716         return;
8717     }
8718
8719     /*
8720      * Ignore other messages if game is not in progress
8721      */
8722     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8723         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8724
8725     /*
8726      * look for win, lose, draw, or draw offer
8727      */
8728     if (strncmp(message, "1-0", 3) == 0) {
8729         char *p, *q, *r = "";
8730         p = strchr(message, '{');
8731         if (p) {
8732             q = strchr(p, '}');
8733             if (q) {
8734                 *q = NULLCHAR;
8735                 r = p + 1;
8736             }
8737         }
8738         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8739         return;
8740     } else if (strncmp(message, "0-1", 3) == 0) {
8741         char *p, *q, *r = "";
8742         p = strchr(message, '{');
8743         if (p) {
8744             q = strchr(p, '}');
8745             if (q) {
8746                 *q = NULLCHAR;
8747                 r = p + 1;
8748             }
8749         }
8750         /* Kludge for Arasan 4.1 bug */
8751         if (strcmp(r, "Black resigns") == 0) {
8752             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8753             return;
8754         }
8755         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8756         return;
8757     } else if (strncmp(message, "1/2", 3) == 0) {
8758         char *p, *q, *r = "";
8759         p = strchr(message, '{');
8760         if (p) {
8761             q = strchr(p, '}');
8762             if (q) {
8763                 *q = NULLCHAR;
8764                 r = p + 1;
8765             }
8766         }
8767
8768         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8769         return;
8770
8771     } else if (strncmp(message, "White resign", 12) == 0) {
8772         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8773         return;
8774     } else if (strncmp(message, "Black resign", 12) == 0) {
8775         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8776         return;
8777     } else if (strncmp(message, "White matches", 13) == 0 ||
8778                strncmp(message, "Black matches", 13) == 0   ) {
8779         /* [HGM] ignore GNUShogi noises */
8780         return;
8781     } else if (strncmp(message, "White", 5) == 0 &&
8782                message[5] != '(' &&
8783                StrStr(message, "Black") == NULL) {
8784         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8785         return;
8786     } else if (strncmp(message, "Black", 5) == 0 &&
8787                message[5] != '(') {
8788         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8789         return;
8790     } else if (strcmp(message, "resign") == 0 ||
8791                strcmp(message, "computer resigns") == 0) {
8792         switch (gameMode) {
8793           case MachinePlaysBlack:
8794           case IcsPlayingBlack:
8795             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8796             break;
8797           case MachinePlaysWhite:
8798           case IcsPlayingWhite:
8799             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8800             break;
8801           case TwoMachinesPlay:
8802             if (cps->twoMachinesColor[0] == 'w')
8803               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8804             else
8805               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8806             break;
8807           default:
8808             /* can't happen */
8809             break;
8810         }
8811         return;
8812     } else if (strncmp(message, "opponent mates", 14) == 0) {
8813         switch (gameMode) {
8814           case MachinePlaysBlack:
8815           case IcsPlayingBlack:
8816             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8817             break;
8818           case MachinePlaysWhite:
8819           case IcsPlayingWhite:
8820             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8821             break;
8822           case TwoMachinesPlay:
8823             if (cps->twoMachinesColor[0] == 'w')
8824               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8825             else
8826               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8827             break;
8828           default:
8829             /* can't happen */
8830             break;
8831         }
8832         return;
8833     } else if (strncmp(message, "computer mates", 14) == 0) {
8834         switch (gameMode) {
8835           case MachinePlaysBlack:
8836           case IcsPlayingBlack:
8837             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8838             break;
8839           case MachinePlaysWhite:
8840           case IcsPlayingWhite:
8841             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8842             break;
8843           case TwoMachinesPlay:
8844             if (cps->twoMachinesColor[0] == 'w')
8845               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8846             else
8847               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8848             break;
8849           default:
8850             /* can't happen */
8851             break;
8852         }
8853         return;
8854     } else if (strncmp(message, "checkmate", 9) == 0) {
8855         if (WhiteOnMove(forwardMostMove)) {
8856             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8857         } else {
8858             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8859         }
8860         return;
8861     } else if (strstr(message, "Draw") != NULL ||
8862                strstr(message, "game is a draw") != NULL) {
8863         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8864         return;
8865     } else if (strstr(message, "offer") != NULL &&
8866                strstr(message, "draw") != NULL) {
8867 #if ZIPPY
8868         if (appData.zippyPlay && first.initDone) {
8869             /* Relay offer to ICS */
8870             SendToICS(ics_prefix);
8871             SendToICS("draw\n");
8872         }
8873 #endif
8874         cps->offeredDraw = 2; /* valid until this engine moves twice */
8875         if (gameMode == TwoMachinesPlay) {
8876             if (cps->other->offeredDraw) {
8877                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8878             /* [HGM] in two-machine mode we delay relaying draw offer      */
8879             /* until after we also have move, to see if it is really claim */
8880             }
8881         } else if (gameMode == MachinePlaysWhite ||
8882                    gameMode == MachinePlaysBlack) {
8883           if (userOfferedDraw) {
8884             DisplayInformation(_("Machine accepts your draw offer"));
8885             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8886           } else {
8887             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8888           }
8889         }
8890     }
8891
8892
8893     /*
8894      * Look for thinking output
8895      */
8896     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8897           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8898                                 ) {
8899         int plylev, mvleft, mvtot, curscore, time;
8900         char mvname[MOVE_LEN];
8901         u64 nodes; // [DM]
8902         char plyext;
8903         int ignore = FALSE;
8904         int prefixHint = FALSE;
8905         mvname[0] = NULLCHAR;
8906
8907         switch (gameMode) {
8908           case MachinePlaysBlack:
8909           case IcsPlayingBlack:
8910             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8911             break;
8912           case MachinePlaysWhite:
8913           case IcsPlayingWhite:
8914             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8915             break;
8916           case AnalyzeMode:
8917           case AnalyzeFile:
8918             break;
8919           case IcsObserving: /* [DM] icsEngineAnalyze */
8920             if (!appData.icsEngineAnalyze) ignore = TRUE;
8921             break;
8922           case TwoMachinesPlay:
8923             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8924                 ignore = TRUE;
8925             }
8926             break;
8927           default:
8928             ignore = TRUE;
8929             break;
8930         }
8931
8932         if (!ignore) {
8933             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8934             buf1[0] = NULLCHAR;
8935             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8936                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8937
8938                 if (plyext != ' ' && plyext != '\t') {
8939                     time *= 100;
8940                 }
8941
8942                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8943                 if( cps->scoreIsAbsolute &&
8944                     ( gameMode == MachinePlaysBlack ||
8945                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8946                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8947                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8948                      !WhiteOnMove(currentMove)
8949                     ) )
8950                 {
8951                     curscore = -curscore;
8952                 }
8953
8954                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8955
8956                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8957                         char buf[MSG_SIZ];
8958                         FILE *f;
8959                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8960                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8961                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8962                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8963                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8964                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8965                                 fclose(f);
8966                         } else DisplayError(_("failed writing PV"), 0);
8967                 }
8968
8969                 tempStats.depth = plylev;
8970                 tempStats.nodes = nodes;
8971                 tempStats.time = time;
8972                 tempStats.score = curscore;
8973                 tempStats.got_only_move = 0;
8974
8975                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8976                         int ticklen;
8977
8978                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8979                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8980                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8981                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8982                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8983                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8984                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8985                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8986                 }
8987
8988                 /* Buffer overflow protection */
8989                 if (pv[0] != NULLCHAR) {
8990                     if (strlen(pv) >= sizeof(tempStats.movelist)
8991                         && appData.debugMode) {
8992                         fprintf(debugFP,
8993                                 "PV is too long; using the first %u bytes.\n",
8994                                 (unsigned) sizeof(tempStats.movelist) - 1);
8995                     }
8996
8997                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8998                 } else {
8999                     sprintf(tempStats.movelist, " no PV\n");
9000                 }
9001
9002                 if (tempStats.seen_stat) {
9003                     tempStats.ok_to_send = 1;
9004                 }
9005
9006                 if (strchr(tempStats.movelist, '(') != NULL) {
9007                     tempStats.line_is_book = 1;
9008                     tempStats.nr_moves = 0;
9009                     tempStats.moves_left = 0;
9010                 } else {
9011                     tempStats.line_is_book = 0;
9012                 }
9013
9014                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9015                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9016
9017                 SendProgramStatsToFrontend( cps, &tempStats );
9018
9019                 /*
9020                     [AS] Protect the thinkOutput buffer from overflow... this
9021                     is only useful if buf1 hasn't overflowed first!
9022                 */
9023                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9024                          plylev,
9025                          (gameMode == TwoMachinesPlay ?
9026                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9027                          ((double) curscore) / 100.0,
9028                          prefixHint ? lastHint : "",
9029                          prefixHint ? " " : "" );
9030
9031                 if( buf1[0] != NULLCHAR ) {
9032                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9033
9034                     if( strlen(pv) > max_len ) {
9035                         if( appData.debugMode) {
9036                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9037                         }
9038                         pv[max_len+1] = '\0';
9039                     }
9040
9041                     strcat( thinkOutput, pv);
9042                 }
9043
9044                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9045                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9046                     DisplayMove(currentMove - 1);
9047                 }
9048                 return;
9049
9050             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9051                 /* crafty (9.25+) says "(only move) <move>"
9052                  * if there is only 1 legal move
9053                  */
9054                 sscanf(p, "(only move) %s", buf1);
9055                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9056                 sprintf(programStats.movelist, "%s (only move)", buf1);
9057                 programStats.depth = 1;
9058                 programStats.nr_moves = 1;
9059                 programStats.moves_left = 1;
9060                 programStats.nodes = 1;
9061                 programStats.time = 1;
9062                 programStats.got_only_move = 1;
9063
9064                 /* Not really, but we also use this member to
9065                    mean "line isn't going to change" (Crafty
9066                    isn't searching, so stats won't change) */
9067                 programStats.line_is_book = 1;
9068
9069                 SendProgramStatsToFrontend( cps, &programStats );
9070
9071                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9072                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9073                     DisplayMove(currentMove - 1);
9074                 }
9075                 return;
9076             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9077                               &time, &nodes, &plylev, &mvleft,
9078                               &mvtot, mvname) >= 5) {
9079                 /* The stat01: line is from Crafty (9.29+) in response
9080                    to the "." command */
9081                 programStats.seen_stat = 1;
9082                 cps->maybeThinking = TRUE;
9083
9084                 if (programStats.got_only_move || !appData.periodicUpdates)
9085                   return;
9086
9087                 programStats.depth = plylev;
9088                 programStats.time = time;
9089                 programStats.nodes = nodes;
9090                 programStats.moves_left = mvleft;
9091                 programStats.nr_moves = mvtot;
9092                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9093                 programStats.ok_to_send = 1;
9094                 programStats.movelist[0] = '\0';
9095
9096                 SendProgramStatsToFrontend( cps, &programStats );
9097
9098                 return;
9099
9100             } else if (strncmp(message,"++",2) == 0) {
9101                 /* Crafty 9.29+ outputs this */
9102                 programStats.got_fail = 2;
9103                 return;
9104
9105             } else if (strncmp(message,"--",2) == 0) {
9106                 /* Crafty 9.29+ outputs this */
9107                 programStats.got_fail = 1;
9108                 return;
9109
9110             } else if (thinkOutput[0] != NULLCHAR &&
9111                        strncmp(message, "    ", 4) == 0) {
9112                 unsigned message_len;
9113
9114                 p = message;
9115                 while (*p && *p == ' ') p++;
9116
9117                 message_len = strlen( p );
9118
9119                 /* [AS] Avoid buffer overflow */
9120                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9121                     strcat(thinkOutput, " ");
9122                     strcat(thinkOutput, p);
9123                 }
9124
9125                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9126                     strcat(programStats.movelist, " ");
9127                     strcat(programStats.movelist, p);
9128                 }
9129
9130                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9131                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9132                     DisplayMove(currentMove - 1);
9133                 }
9134                 return;
9135             }
9136         }
9137         else {
9138             buf1[0] = NULLCHAR;
9139
9140             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9141                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9142             {
9143                 ChessProgramStats cpstats;
9144
9145                 if (plyext != ' ' && plyext != '\t') {
9146                     time *= 100;
9147                 }
9148
9149                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9150                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9151                     curscore = -curscore;
9152                 }
9153
9154                 cpstats.depth = plylev;
9155                 cpstats.nodes = nodes;
9156                 cpstats.time = time;
9157                 cpstats.score = curscore;
9158                 cpstats.got_only_move = 0;
9159                 cpstats.movelist[0] = '\0';
9160
9161                 if (buf1[0] != NULLCHAR) {
9162                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9163                 }
9164
9165                 cpstats.ok_to_send = 0;
9166                 cpstats.line_is_book = 0;
9167                 cpstats.nr_moves = 0;
9168                 cpstats.moves_left = 0;
9169
9170                 SendProgramStatsToFrontend( cps, &cpstats );
9171             }
9172         }
9173     }
9174 }
9175
9176
9177 /* Parse a game score from the character string "game", and
9178    record it as the history of the current game.  The game
9179    score is NOT assumed to start from the standard position.
9180    The display is not updated in any way.
9181    */
9182 void
9183 ParseGameHistory (char *game)
9184 {
9185     ChessMove moveType;
9186     int fromX, fromY, toX, toY, boardIndex;
9187     char promoChar;
9188     char *p, *q;
9189     char buf[MSG_SIZ];
9190
9191     if (appData.debugMode)
9192       fprintf(debugFP, "Parsing game history: %s\n", game);
9193
9194     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9195     gameInfo.site = StrSave(appData.icsHost);
9196     gameInfo.date = PGNDate();
9197     gameInfo.round = StrSave("-");
9198
9199     /* Parse out names of players */
9200     while (*game == ' ') game++;
9201     p = buf;
9202     while (*game != ' ') *p++ = *game++;
9203     *p = NULLCHAR;
9204     gameInfo.white = StrSave(buf);
9205     while (*game == ' ') game++;
9206     p = buf;
9207     while (*game != ' ' && *game != '\n') *p++ = *game++;
9208     *p = NULLCHAR;
9209     gameInfo.black = StrSave(buf);
9210
9211     /* Parse moves */
9212     boardIndex = blackPlaysFirst ? 1 : 0;
9213     yynewstr(game);
9214     for (;;) {
9215         yyboardindex = boardIndex;
9216         moveType = (ChessMove) Myylex();
9217         switch (moveType) {
9218           case IllegalMove:             /* maybe suicide chess, etc. */
9219   if (appData.debugMode) {
9220     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9221     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9222     setbuf(debugFP, NULL);
9223   }
9224           case WhitePromotion:
9225           case BlackPromotion:
9226           case WhiteNonPromotion:
9227           case BlackNonPromotion:
9228           case NormalMove:
9229           case WhiteCapturesEnPassant:
9230           case BlackCapturesEnPassant:
9231           case WhiteKingSideCastle:
9232           case WhiteQueenSideCastle:
9233           case BlackKingSideCastle:
9234           case BlackQueenSideCastle:
9235           case WhiteKingSideCastleWild:
9236           case WhiteQueenSideCastleWild:
9237           case BlackKingSideCastleWild:
9238           case BlackQueenSideCastleWild:
9239           /* PUSH Fabien */
9240           case WhiteHSideCastleFR:
9241           case WhiteASideCastleFR:
9242           case BlackHSideCastleFR:
9243           case BlackASideCastleFR:
9244           /* POP Fabien */
9245             fromX = currentMoveString[0] - AAA;
9246             fromY = currentMoveString[1] - ONE;
9247             toX = currentMoveString[2] - AAA;
9248             toY = currentMoveString[3] - ONE;
9249             promoChar = currentMoveString[4];
9250             break;
9251           case WhiteDrop:
9252           case BlackDrop:
9253             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9254             fromX = moveType == WhiteDrop ?
9255               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9256             (int) CharToPiece(ToLower(currentMoveString[0]));
9257             fromY = DROP_RANK;
9258             toX = currentMoveString[2] - AAA;
9259             toY = currentMoveString[3] - ONE;
9260             promoChar = NULLCHAR;
9261             break;
9262           case AmbiguousMove:
9263             /* bug? */
9264             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9265   if (appData.debugMode) {
9266     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9267     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9268     setbuf(debugFP, NULL);
9269   }
9270             DisplayError(buf, 0);
9271             return;
9272           case ImpossibleMove:
9273             /* bug? */
9274             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9275   if (appData.debugMode) {
9276     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9277     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9278     setbuf(debugFP, NULL);
9279   }
9280             DisplayError(buf, 0);
9281             return;
9282           case EndOfFile:
9283             if (boardIndex < backwardMostMove) {
9284                 /* Oops, gap.  How did that happen? */
9285                 DisplayError(_("Gap in move list"), 0);
9286                 return;
9287             }
9288             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9289             if (boardIndex > forwardMostMove) {
9290                 forwardMostMove = boardIndex;
9291             }
9292             return;
9293           case ElapsedTime:
9294             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9295                 strcat(parseList[boardIndex-1], " ");
9296                 strcat(parseList[boardIndex-1], yy_text);
9297             }
9298             continue;
9299           case Comment:
9300           case PGNTag:
9301           case NAG:
9302           default:
9303             /* ignore */
9304             continue;
9305           case WhiteWins:
9306           case BlackWins:
9307           case GameIsDrawn:
9308           case GameUnfinished:
9309             if (gameMode == IcsExamining) {
9310                 if (boardIndex < backwardMostMove) {
9311                     /* Oops, gap.  How did that happen? */
9312                     return;
9313                 }
9314                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9315                 return;
9316             }
9317             gameInfo.result = moveType;
9318             p = strchr(yy_text, '{');
9319             if (p == NULL) p = strchr(yy_text, '(');
9320             if (p == NULL) {
9321                 p = yy_text;
9322                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9323             } else {
9324                 q = strchr(p, *p == '{' ? '}' : ')');
9325                 if (q != NULL) *q = NULLCHAR;
9326                 p++;
9327             }
9328             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9329             gameInfo.resultDetails = StrSave(p);
9330             continue;
9331         }
9332         if (boardIndex >= forwardMostMove &&
9333             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9334             backwardMostMove = blackPlaysFirst ? 1 : 0;
9335             return;
9336         }
9337         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9338                                  fromY, fromX, toY, toX, promoChar,
9339                                  parseList[boardIndex]);
9340         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9341         /* currentMoveString is set as a side-effect of yylex */
9342         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9343         strcat(moveList[boardIndex], "\n");
9344         boardIndex++;
9345         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9346         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9347           case MT_NONE:
9348           case MT_STALEMATE:
9349           default:
9350             break;
9351           case MT_CHECK:
9352             if(gameInfo.variant != VariantShogi)
9353                 strcat(parseList[boardIndex - 1], "+");
9354             break;
9355           case MT_CHECKMATE:
9356           case MT_STAINMATE:
9357             strcat(parseList[boardIndex - 1], "#");
9358             break;
9359         }
9360     }
9361 }
9362
9363
9364 /* Apply a move to the given board  */
9365 void
9366 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9367 {
9368   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9369   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9370
9371     /* [HGM] compute & store e.p. status and castling rights for new position */
9372     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9373
9374       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9375       oldEP = (signed char)board[EP_STATUS];
9376       board[EP_STATUS] = EP_NONE;
9377
9378   if (fromY == DROP_RANK) {
9379         /* must be first */
9380         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9381             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9382             return;
9383         }
9384         piece = board[toY][toX] = (ChessSquare) fromX;
9385   } else {
9386       int i;
9387
9388       if( board[toY][toX] != EmptySquare )
9389            board[EP_STATUS] = EP_CAPTURE;
9390
9391       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9392            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9393                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9394       } else
9395       if( board[fromY][fromX] == WhitePawn ) {
9396            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9397                board[EP_STATUS] = EP_PAWN_MOVE;
9398            if( toY-fromY==2) {
9399                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9400                         gameInfo.variant != VariantBerolina || toX < fromX)
9401                       board[EP_STATUS] = toX | berolina;
9402                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9403                         gameInfo.variant != VariantBerolina || toX > fromX)
9404                       board[EP_STATUS] = toX;
9405            }
9406       } else
9407       if( board[fromY][fromX] == BlackPawn ) {
9408            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9409                board[EP_STATUS] = EP_PAWN_MOVE;
9410            if( toY-fromY== -2) {
9411                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9412                         gameInfo.variant != VariantBerolina || toX < fromX)
9413                       board[EP_STATUS] = toX | berolina;
9414                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9415                         gameInfo.variant != VariantBerolina || toX > fromX)
9416                       board[EP_STATUS] = toX;
9417            }
9418        }
9419
9420        for(i=0; i<nrCastlingRights; i++) {
9421            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9422               board[CASTLING][i] == toX   && castlingRank[i] == toY
9423              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9424        }
9425
9426        if(gameInfo.variant == VariantSChess) { // update virginity
9427            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9428            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9429            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9430            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9431        }
9432
9433      if (fromX == toX && fromY == toY) return;
9434
9435      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9436      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9437      if(gameInfo.variant == VariantKnightmate)
9438          king += (int) WhiteUnicorn - (int) WhiteKing;
9439
9440     /* Code added by Tord: */
9441     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9442     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9443         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9444       board[fromY][fromX] = EmptySquare;
9445       board[toY][toX] = EmptySquare;
9446       if((toX > fromX) != (piece == WhiteRook)) {
9447         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9448       } else {
9449         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9450       }
9451     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9452                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9453       board[fromY][fromX] = EmptySquare;
9454       board[toY][toX] = EmptySquare;
9455       if((toX > fromX) != (piece == BlackRook)) {
9456         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9457       } else {
9458         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9459       }
9460     /* End of code added by Tord */
9461
9462     } else if (board[fromY][fromX] == king
9463         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9464         && toY == fromY && toX > fromX+1) {
9465         board[fromY][fromX] = EmptySquare;
9466         board[toY][toX] = king;
9467         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9468         board[fromY][BOARD_RGHT-1] = EmptySquare;
9469     } else if (board[fromY][fromX] == king
9470         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9471                && toY == fromY && toX < fromX-1) {
9472         board[fromY][fromX] = EmptySquare;
9473         board[toY][toX] = king;
9474         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9475         board[fromY][BOARD_LEFT] = EmptySquare;
9476     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9477                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9478                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9479                ) {
9480         /* white pawn promotion */
9481         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9482         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9483             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9484         board[fromY][fromX] = EmptySquare;
9485     } else if ((fromY >= BOARD_HEIGHT>>1)
9486                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9487                && (toX != fromX)
9488                && gameInfo.variant != VariantXiangqi
9489                && gameInfo.variant != VariantBerolina
9490                && (board[fromY][fromX] == WhitePawn)
9491                && (board[toY][toX] == EmptySquare)) {
9492         board[fromY][fromX] = EmptySquare;
9493         board[toY][toX] = WhitePawn;
9494         captured = board[toY - 1][toX];
9495         board[toY - 1][toX] = EmptySquare;
9496     } else if ((fromY == BOARD_HEIGHT-4)
9497                && (toX == fromX)
9498                && gameInfo.variant == VariantBerolina
9499                && (board[fromY][fromX] == WhitePawn)
9500                && (board[toY][toX] == EmptySquare)) {
9501         board[fromY][fromX] = EmptySquare;
9502         board[toY][toX] = WhitePawn;
9503         if(oldEP & EP_BEROLIN_A) {
9504                 captured = board[fromY][fromX-1];
9505                 board[fromY][fromX-1] = EmptySquare;
9506         }else{  captured = board[fromY][fromX+1];
9507                 board[fromY][fromX+1] = EmptySquare;
9508         }
9509     } else if (board[fromY][fromX] == king
9510         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9511                && toY == fromY && toX > fromX+1) {
9512         board[fromY][fromX] = EmptySquare;
9513         board[toY][toX] = king;
9514         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9515         board[fromY][BOARD_RGHT-1] = EmptySquare;
9516     } else if (board[fromY][fromX] == king
9517         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9518                && toY == fromY && toX < fromX-1) {
9519         board[fromY][fromX] = EmptySquare;
9520         board[toY][toX] = king;
9521         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9522         board[fromY][BOARD_LEFT] = EmptySquare;
9523     } else if (fromY == 7 && fromX == 3
9524                && board[fromY][fromX] == BlackKing
9525                && toY == 7 && toX == 5) {
9526         board[fromY][fromX] = EmptySquare;
9527         board[toY][toX] = BlackKing;
9528         board[fromY][7] = EmptySquare;
9529         board[toY][4] = BlackRook;
9530     } else if (fromY == 7 && fromX == 3
9531                && board[fromY][fromX] == BlackKing
9532                && toY == 7 && toX == 1) {
9533         board[fromY][fromX] = EmptySquare;
9534         board[toY][toX] = BlackKing;
9535         board[fromY][0] = EmptySquare;
9536         board[toY][2] = BlackRook;
9537     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9538                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9539                && toY < promoRank && promoChar
9540                ) {
9541         /* black pawn promotion */
9542         board[toY][toX] = CharToPiece(ToLower(promoChar));
9543         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9544             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9545         board[fromY][fromX] = EmptySquare;
9546     } else if ((fromY < BOARD_HEIGHT>>1)
9547                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9548                && (toX != fromX)
9549                && gameInfo.variant != VariantXiangqi
9550                && gameInfo.variant != VariantBerolina
9551                && (board[fromY][fromX] == BlackPawn)
9552                && (board[toY][toX] == EmptySquare)) {
9553         board[fromY][fromX] = EmptySquare;
9554         board[toY][toX] = BlackPawn;
9555         captured = board[toY + 1][toX];
9556         board[toY + 1][toX] = EmptySquare;
9557     } else if ((fromY == 3)
9558                && (toX == fromX)
9559                && gameInfo.variant == VariantBerolina
9560                && (board[fromY][fromX] == BlackPawn)
9561                && (board[toY][toX] == EmptySquare)) {
9562         board[fromY][fromX] = EmptySquare;
9563         board[toY][toX] = BlackPawn;
9564         if(oldEP & EP_BEROLIN_A) {
9565                 captured = board[fromY][fromX-1];
9566                 board[fromY][fromX-1] = EmptySquare;
9567         }else{  captured = board[fromY][fromX+1];
9568                 board[fromY][fromX+1] = EmptySquare;
9569         }
9570     } else {
9571         board[toY][toX] = board[fromY][fromX];
9572         board[fromY][fromX] = EmptySquare;
9573     }
9574   }
9575
9576     if (gameInfo.holdingsWidth != 0) {
9577
9578       /* !!A lot more code needs to be written to support holdings  */
9579       /* [HGM] OK, so I have written it. Holdings are stored in the */
9580       /* penultimate board files, so they are automaticlly stored   */
9581       /* in the game history.                                       */
9582       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9583                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9584         /* Delete from holdings, by decreasing count */
9585         /* and erasing image if necessary            */
9586         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9587         if(p < (int) BlackPawn) { /* white drop */
9588              p -= (int)WhitePawn;
9589                  p = PieceToNumber((ChessSquare)p);
9590              if(p >= gameInfo.holdingsSize) p = 0;
9591              if(--board[p][BOARD_WIDTH-2] <= 0)
9592                   board[p][BOARD_WIDTH-1] = EmptySquare;
9593              if((int)board[p][BOARD_WIDTH-2] < 0)
9594                         board[p][BOARD_WIDTH-2] = 0;
9595         } else {                  /* black drop */
9596              p -= (int)BlackPawn;
9597                  p = PieceToNumber((ChessSquare)p);
9598              if(p >= gameInfo.holdingsSize) p = 0;
9599              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9600                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9601              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9602                         board[BOARD_HEIGHT-1-p][1] = 0;
9603         }
9604       }
9605       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9606           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9607         /* [HGM] holdings: Add to holdings, if holdings exist */
9608         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9609                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9610                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9611         }
9612         p = (int) captured;
9613         if (p >= (int) BlackPawn) {
9614           p -= (int)BlackPawn;
9615           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9616                   /* in Shogi restore piece to its original  first */
9617                   captured = (ChessSquare) (DEMOTED captured);
9618                   p = DEMOTED p;
9619           }
9620           p = PieceToNumber((ChessSquare)p);
9621           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9622           board[p][BOARD_WIDTH-2]++;
9623           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9624         } else {
9625           p -= (int)WhitePawn;
9626           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9627                   captured = (ChessSquare) (DEMOTED captured);
9628                   p = DEMOTED p;
9629           }
9630           p = PieceToNumber((ChessSquare)p);
9631           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9632           board[BOARD_HEIGHT-1-p][1]++;
9633           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9634         }
9635       }
9636     } else if (gameInfo.variant == VariantAtomic) {
9637       if (captured != EmptySquare) {
9638         int y, x;
9639         for (y = toY-1; y <= toY+1; y++) {
9640           for (x = toX-1; x <= toX+1; x++) {
9641             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9642                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9643               board[y][x] = EmptySquare;
9644             }
9645           }
9646         }
9647         board[toY][toX] = EmptySquare;
9648       }
9649     }
9650     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9651         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9652     } else
9653     if(promoChar == '+') {
9654         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9655         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9656     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9657         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9658         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9659            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9660         board[toY][toX] = newPiece;
9661     }
9662     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9663                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9664         // [HGM] superchess: take promotion piece out of holdings
9665         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9666         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9667             if(!--board[k][BOARD_WIDTH-2])
9668                 board[k][BOARD_WIDTH-1] = EmptySquare;
9669         } else {
9670             if(!--board[BOARD_HEIGHT-1-k][1])
9671                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9672         }
9673     }
9674
9675 }
9676
9677 /* Updates forwardMostMove */
9678 void
9679 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9680 {
9681 //    forwardMostMove++; // [HGM] bare: moved downstream
9682
9683     (void) CoordsToAlgebraic(boards[forwardMostMove],
9684                              PosFlags(forwardMostMove),
9685                              fromY, fromX, toY, toX, promoChar,
9686                              parseList[forwardMostMove]);
9687
9688     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9689         int timeLeft; static int lastLoadFlag=0; int king, piece;
9690         piece = boards[forwardMostMove][fromY][fromX];
9691         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9692         if(gameInfo.variant == VariantKnightmate)
9693             king += (int) WhiteUnicorn - (int) WhiteKing;
9694         if(forwardMostMove == 0) {
9695             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9696                 fprintf(serverMoves, "%s;", UserName());
9697             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9698                 fprintf(serverMoves, "%s;", second.tidy);
9699             fprintf(serverMoves, "%s;", first.tidy);
9700             if(gameMode == MachinePlaysWhite)
9701                 fprintf(serverMoves, "%s;", UserName());
9702             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9703                 fprintf(serverMoves, "%s;", second.tidy);
9704         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9705         lastLoadFlag = loadFlag;
9706         // print base move
9707         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9708         // print castling suffix
9709         if( toY == fromY && piece == king ) {
9710             if(toX-fromX > 1)
9711                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9712             if(fromX-toX >1)
9713                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9714         }
9715         // e.p. suffix
9716         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9717              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9718              boards[forwardMostMove][toY][toX] == EmptySquare
9719              && fromX != toX && fromY != toY)
9720                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9721         // promotion suffix
9722         if(promoChar != NULLCHAR) {
9723             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9724                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9725                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9726             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9727         }
9728         if(!loadFlag) {
9729                 char buf[MOVE_LEN*2], *p; int len;
9730             fprintf(serverMoves, "/%d/%d",
9731                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9732             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9733             else                      timeLeft = blackTimeRemaining/1000;
9734             fprintf(serverMoves, "/%d", timeLeft);
9735                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9736                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9737                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9738                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9739             fprintf(serverMoves, "/%s", buf);
9740         }
9741         fflush(serverMoves);
9742     }
9743
9744     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9745         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9746       return;
9747     }
9748     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9749     if (commentList[forwardMostMove+1] != NULL) {
9750         free(commentList[forwardMostMove+1]);
9751         commentList[forwardMostMove+1] = NULL;
9752     }
9753     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9754     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9755     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9756     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9757     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9758     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9759     adjustedClock = FALSE;
9760     gameInfo.result = GameUnfinished;
9761     if (gameInfo.resultDetails != NULL) {
9762         free(gameInfo.resultDetails);
9763         gameInfo.resultDetails = NULL;
9764     }
9765     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9766                               moveList[forwardMostMove - 1]);
9767     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9768       case MT_NONE:
9769       case MT_STALEMATE:
9770       default:
9771         break;
9772       case MT_CHECK:
9773         if(gameInfo.variant != VariantShogi)
9774             strcat(parseList[forwardMostMove - 1], "+");
9775         break;
9776       case MT_CHECKMATE:
9777       case MT_STAINMATE:
9778         strcat(parseList[forwardMostMove - 1], "#");
9779         break;
9780     }
9781
9782 }
9783
9784 /* Updates currentMove if not pausing */
9785 void
9786 ShowMove (int fromX, int fromY, int toX, int toY)
9787 {
9788     int instant = (gameMode == PlayFromGameFile) ?
9789         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9790     if(appData.noGUI) return;
9791     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9792         if (!instant) {
9793             if (forwardMostMove == currentMove + 1) {
9794                 AnimateMove(boards[forwardMostMove - 1],
9795                             fromX, fromY, toX, toY);
9796             }
9797         }
9798         currentMove = forwardMostMove;
9799     }
9800
9801     if (instant) return;
9802
9803     DisplayMove(currentMove - 1);
9804     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9805             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9806                 SetHighlights(fromX, fromY, toX, toY);
9807             }
9808     }
9809     DrawPosition(FALSE, boards[currentMove]);
9810     DisplayBothClocks();
9811     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9812 }
9813
9814 void
9815 SendEgtPath (ChessProgramState *cps)
9816 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9817         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9818
9819         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9820
9821         while(*p) {
9822             char c, *q = name+1, *r, *s;
9823
9824             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9825             while(*p && *p != ',') *q++ = *p++;
9826             *q++ = ':'; *q = 0;
9827             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9828                 strcmp(name, ",nalimov:") == 0 ) {
9829                 // take nalimov path from the menu-changeable option first, if it is defined
9830               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9831                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9832             } else
9833             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9834                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9835                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9836                 s = r = StrStr(s, ":") + 1; // beginning of path info
9837                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9838                 c = *r; *r = 0;             // temporarily null-terminate path info
9839                     *--q = 0;               // strip of trailig ':' from name
9840                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9841                 *r = c;
9842                 SendToProgram(buf,cps);     // send egtbpath command for this format
9843             }
9844             if(*p == ',') p++; // read away comma to position for next format name
9845         }
9846 }
9847
9848 void
9849 InitChessProgram (ChessProgramState *cps, int setup)
9850 /* setup needed to setup FRC opening position */
9851 {
9852     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9853     if (appData.noChessProgram) return;
9854     hintRequested = FALSE;
9855     bookRequested = FALSE;
9856
9857     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9858     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9859     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9860     if(cps->memSize) { /* [HGM] memory */
9861       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9862         SendToProgram(buf, cps);
9863     }
9864     SendEgtPath(cps); /* [HGM] EGT */
9865     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9866       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9867         SendToProgram(buf, cps);
9868     }
9869
9870     SendToProgram(cps->initString, cps);
9871     if (gameInfo.variant != VariantNormal &&
9872         gameInfo.variant != VariantLoadable
9873         /* [HGM] also send variant if board size non-standard */
9874         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9875                                             ) {
9876       char *v = VariantName(gameInfo.variant);
9877       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9878         /* [HGM] in protocol 1 we have to assume all variants valid */
9879         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9880         DisplayFatalError(buf, 0, 1);
9881         return;
9882       }
9883
9884       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9885       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9886       if( gameInfo.variant == VariantXiangqi )
9887            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9888       if( gameInfo.variant == VariantShogi )
9889            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9890       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9891            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9892       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9893           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9894            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9895       if( gameInfo.variant == VariantCourier )
9896            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9897       if( gameInfo.variant == VariantSuper )
9898            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9899       if( gameInfo.variant == VariantGreat )
9900            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9901       if( gameInfo.variant == VariantSChess )
9902            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9903       if( gameInfo.variant == VariantGrand )
9904            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9905
9906       if(overruled) {
9907         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9908                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9909            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9910            if(StrStr(cps->variants, b) == NULL) {
9911                // specific sized variant not known, check if general sizing allowed
9912                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9913                    if(StrStr(cps->variants, "boardsize") == NULL) {
9914                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9915                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9916                        DisplayFatalError(buf, 0, 1);
9917                        return;
9918                    }
9919                    /* [HGM] here we really should compare with the maximum supported board size */
9920                }
9921            }
9922       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9923       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9924       SendToProgram(buf, cps);
9925     }
9926     currentlyInitializedVariant = gameInfo.variant;
9927
9928     /* [HGM] send opening position in FRC to first engine */
9929     if(setup) {
9930           SendToProgram("force\n", cps);
9931           SendBoard(cps, 0);
9932           /* engine is now in force mode! Set flag to wake it up after first move. */
9933           setboardSpoiledMachineBlack = 1;
9934     }
9935
9936     if (cps->sendICS) {
9937       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9938       SendToProgram(buf, cps);
9939     }
9940     cps->maybeThinking = FALSE;
9941     cps->offeredDraw = 0;
9942     if (!appData.icsActive) {
9943         SendTimeControl(cps, movesPerSession, timeControl,
9944                         timeIncrement, appData.searchDepth,
9945                         searchTime);
9946     }
9947     if (appData.showThinking
9948         // [HGM] thinking: four options require thinking output to be sent
9949         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9950                                 ) {
9951         SendToProgram("post\n", cps);
9952     }
9953     SendToProgram("hard\n", cps);
9954     if (!appData.ponderNextMove) {
9955         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9956            it without being sure what state we are in first.  "hard"
9957            is not a toggle, so that one is OK.
9958          */
9959         SendToProgram("easy\n", cps);
9960     }
9961     if (cps->usePing) {
9962       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9963       SendToProgram(buf, cps);
9964     }
9965     cps->initDone = TRUE;
9966     ClearEngineOutputPane(cps == &second);
9967 }
9968
9969
9970 void
9971 ResendOptions (ChessProgramState *cps)
9972 { // send the stored value of the options
9973   int i;
9974   char buf[MSG_SIZ];
9975   Option *opt = cps->option;
9976   for(i=0; i<cps->nrOptions; i++, opt++) {
9977       switch(opt->type) {
9978         case Spin:
9979         case Slider:
9980         case CheckBox:
9981             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9982           break;
9983         case ComboBox:
9984           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9985           break;
9986         default:
9987             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9988           break;
9989         case Button:
9990         case SaveButton:
9991           continue;
9992       }
9993       SendToProgram(buf, cps);
9994   }
9995 }
9996
9997 void
9998 StartChessProgram (ChessProgramState *cps)
9999 {
10000     char buf[MSG_SIZ];
10001     int err;
10002
10003     if (appData.noChessProgram) return;
10004     cps->initDone = FALSE;
10005
10006     if (strcmp(cps->host, "localhost") == 0) {
10007         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10008     } else if (*appData.remoteShell == NULLCHAR) {
10009         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10010     } else {
10011         if (*appData.remoteUser == NULLCHAR) {
10012           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10013                     cps->program);
10014         } else {
10015           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10016                     cps->host, appData.remoteUser, cps->program);
10017         }
10018         err = StartChildProcess(buf, "", &cps->pr);
10019     }
10020
10021     if (err != 0) {
10022       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10023         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10024         if(cps != &first) return;
10025         appData.noChessProgram = TRUE;
10026         ThawUI();
10027         SetNCPMode();
10028 //      DisplayFatalError(buf, err, 1);
10029 //      cps->pr = NoProc;
10030 //      cps->isr = NULL;
10031         return;
10032     }
10033
10034     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10035     if (cps->protocolVersion > 1) {
10036       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10037       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10038         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10039         cps->comboCnt = 0;  //                and values of combo boxes
10040       }
10041       SendToProgram(buf, cps);
10042       if(cps->reload) ResendOptions(cps);
10043     } else {
10044       SendToProgram("xboard\n", cps);
10045     }
10046 }
10047
10048 void
10049 TwoMachinesEventIfReady P((void))
10050 {
10051   static int curMess = 0;
10052   if (first.lastPing != first.lastPong) {
10053     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10054     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10055     return;
10056   }
10057   if (second.lastPing != second.lastPong) {
10058     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10059     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10060     return;
10061   }
10062   DisplayMessage("", ""); curMess = 0;
10063   TwoMachinesEvent();
10064 }
10065
10066 char *
10067 MakeName (char *template)
10068 {
10069     time_t clock;
10070     struct tm *tm;
10071     static char buf[MSG_SIZ];
10072     char *p = buf;
10073     int i;
10074
10075     clock = time((time_t *)NULL);
10076     tm = localtime(&clock);
10077
10078     while(*p++ = *template++) if(p[-1] == '%') {
10079         switch(*template++) {
10080           case 0:   *p = 0; return buf;
10081           case 'Y': i = tm->tm_year+1900; break;
10082           case 'y': i = tm->tm_year-100; break;
10083           case 'M': i = tm->tm_mon+1; break;
10084           case 'd': i = tm->tm_mday; break;
10085           case 'h': i = tm->tm_hour; break;
10086           case 'm': i = tm->tm_min; break;
10087           case 's': i = tm->tm_sec; break;
10088           default:  i = 0;
10089         }
10090         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10091     }
10092     return buf;
10093 }
10094
10095 int
10096 CountPlayers (char *p)
10097 {
10098     int n = 0;
10099     while(p = strchr(p, '\n')) p++, n++; // count participants
10100     return n;
10101 }
10102
10103 FILE *
10104 WriteTourneyFile (char *results, FILE *f)
10105 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10106     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10107     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10108         // create a file with tournament description
10109         fprintf(f, "-participants {%s}\n", appData.participants);
10110         fprintf(f, "-seedBase %d\n", appData.seedBase);
10111         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10112         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10113         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10114         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10115         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10116         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10117         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10118         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10119         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10120         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10121         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10122         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10123         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10124         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10125         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10126         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10127         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10128         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10129         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10130         fprintf(f, "-smpCores %d\n", appData.smpCores);
10131         if(searchTime > 0)
10132                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10133         else {
10134                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10135                 fprintf(f, "-tc %s\n", appData.timeControl);
10136                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10137         }
10138         fprintf(f, "-results \"%s\"\n", results);
10139     }
10140     return f;
10141 }
10142
10143 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10144
10145 void
10146 Substitute (char *participants, int expunge)
10147 {
10148     int i, changed, changes=0, nPlayers=0;
10149     char *p, *q, *r, buf[MSG_SIZ];
10150     if(participants == NULL) return;
10151     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10152     r = p = participants; q = appData.participants;
10153     while(*p && *p == *q) {
10154         if(*p == '\n') r = p+1, nPlayers++;
10155         p++; q++;
10156     }
10157     if(*p) { // difference
10158         while(*p && *p++ != '\n');
10159         while(*q && *q++ != '\n');
10160       changed = nPlayers;
10161         changes = 1 + (strcmp(p, q) != 0);
10162     }
10163     if(changes == 1) { // a single engine mnemonic was changed
10164         q = r; while(*q) nPlayers += (*q++ == '\n');
10165         p = buf; while(*r && (*p = *r++) != '\n') p++;
10166         *p = NULLCHAR;
10167         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10168         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10169         if(mnemonic[i]) { // The substitute is valid
10170             FILE *f;
10171             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10172                 flock(fileno(f), LOCK_EX);
10173                 ParseArgsFromFile(f);
10174                 fseek(f, 0, SEEK_SET);
10175                 FREE(appData.participants); appData.participants = participants;
10176                 if(expunge) { // erase results of replaced engine
10177                     int len = strlen(appData.results), w, b, dummy;
10178                     for(i=0; i<len; i++) {
10179                         Pairing(i, nPlayers, &w, &b, &dummy);
10180                         if((w == changed || b == changed) && appData.results[i] == '*') {
10181                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10182                             fclose(f);
10183                             return;
10184                         }
10185                     }
10186                     for(i=0; i<len; i++) {
10187                         Pairing(i, nPlayers, &w, &b, &dummy);
10188                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10189                     }
10190                 }
10191                 WriteTourneyFile(appData.results, f);
10192                 fclose(f); // release lock
10193                 return;
10194             }
10195         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10196     }
10197     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10198     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10199     free(participants);
10200     return;
10201 }
10202
10203 int
10204 CheckPlayers (char *participants)
10205 {
10206         int i;
10207         char buf[MSG_SIZ], *p;
10208         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10209         while(p = strchr(participants, '\n')) {
10210             *p = NULLCHAR;
10211             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10212             if(!mnemonic[i]) {
10213                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10214                 *p = '\n';
10215                 DisplayError(buf, 0);
10216                 return 1;
10217             }
10218             *p = '\n';
10219             participants = p + 1;
10220         }
10221         return 0;
10222 }
10223
10224 int
10225 CreateTourney (char *name)
10226 {
10227         FILE *f;
10228         if(matchMode && strcmp(name, appData.tourneyFile)) {
10229              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10230         }
10231         if(name[0] == NULLCHAR) {
10232             if(appData.participants[0])
10233                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10234             return 0;
10235         }
10236         f = fopen(name, "r");
10237         if(f) { // file exists
10238             ASSIGN(appData.tourneyFile, name);
10239             ParseArgsFromFile(f); // parse it
10240         } else {
10241             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10242             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10243                 DisplayError(_("Not enough participants"), 0);
10244                 return 0;
10245             }
10246             if(CheckPlayers(appData.participants)) return 0;
10247             ASSIGN(appData.tourneyFile, name);
10248             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10249             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10250         }
10251         fclose(f);
10252         appData.noChessProgram = FALSE;
10253         appData.clockMode = TRUE;
10254         SetGNUMode();
10255         return 1;
10256 }
10257
10258 int
10259 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10260 {
10261     char buf[MSG_SIZ], *p, *q;
10262     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10263     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10264     skip = !all && group[0]; // if group requested, we start in skip mode
10265     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10266         p = names; q = buf; header = 0;
10267         while(*p && *p != '\n') *q++ = *p++;
10268         *q = 0;
10269         if(*p == '\n') p++;
10270         if(buf[0] == '#') {
10271             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10272             depth++; // we must be entering a new group
10273             if(all) continue; // suppress printing group headers when complete list requested
10274             header = 1;
10275             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10276         }
10277         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10278         if(engineList[i]) free(engineList[i]);
10279         engineList[i] = strdup(buf);
10280         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10281         if(engineMnemonic[i]) free(engineMnemonic[i]);
10282         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10283             strcat(buf, " (");
10284             sscanf(q + 8, "%s", buf + strlen(buf));
10285             strcat(buf, ")");
10286         }
10287         engineMnemonic[i] = strdup(buf);
10288         i++;
10289     }
10290     engineList[i] = engineMnemonic[i] = NULL;
10291     return i;
10292 }
10293
10294 // following implemented as macro to avoid type limitations
10295 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10296
10297 void
10298 SwapEngines (int n)
10299 {   // swap settings for first engine and other engine (so far only some selected options)
10300     int h;
10301     char *p;
10302     if(n == 0) return;
10303     SWAP(directory, p)
10304     SWAP(chessProgram, p)
10305     SWAP(isUCI, h)
10306     SWAP(hasOwnBookUCI, h)
10307     SWAP(protocolVersion, h)
10308     SWAP(reuse, h)
10309     SWAP(scoreIsAbsolute, h)
10310     SWAP(timeOdds, h)
10311     SWAP(logo, p)
10312     SWAP(pgnName, p)
10313     SWAP(pvSAN, h)
10314     SWAP(engOptions, p)
10315     SWAP(engInitString, p)
10316     SWAP(computerString, p)
10317     SWAP(features, p)
10318     SWAP(fenOverride, p)
10319     SWAP(NPS, h)
10320     SWAP(accumulateTC, h)
10321     SWAP(host, p)
10322 }
10323
10324 int
10325 GetEngineLine (char *s, int n)
10326 {
10327     int i;
10328     char buf[MSG_SIZ];
10329     extern char *icsNames;
10330     if(!s || !*s) return 0;
10331     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10332     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10333     if(!mnemonic[i]) return 0;
10334     if(n == 11) return 1; // just testing if there was a match
10335     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10336     if(n == 1) SwapEngines(n);
10337     ParseArgsFromString(buf);
10338     if(n == 1) SwapEngines(n);
10339     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10340         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10341         ParseArgsFromString(buf);
10342     }
10343     return 1;
10344 }
10345
10346 int
10347 SetPlayer (int player, char *p)
10348 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10349     int i;
10350     char buf[MSG_SIZ], *engineName;
10351     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10352     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10353     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10354     if(mnemonic[i]) {
10355         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10356         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10357         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10358         ParseArgsFromString(buf);
10359     } else { // no engine with this nickname is installed!
10360         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10361         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10362         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10363         ModeHighlight();
10364         DisplayError(buf, 0);
10365         return 0;
10366     }
10367     free(engineName);
10368     return i;
10369 }
10370
10371 char *recentEngines;
10372
10373 void
10374 RecentEngineEvent (int nr)
10375 {
10376     int n;
10377 //    SwapEngines(1); // bump first to second
10378 //    ReplaceEngine(&second, 1); // and load it there
10379     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10380     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10381     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10382         ReplaceEngine(&first, 0);
10383         FloatToFront(&appData.recentEngineList, command[n]);
10384     }
10385 }
10386
10387 int
10388 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10389 {   // determine players from game number
10390     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10391
10392     if(appData.tourneyType == 0) {
10393         roundsPerCycle = (nPlayers - 1) | 1;
10394         pairingsPerRound = nPlayers / 2;
10395     } else if(appData.tourneyType > 0) {
10396         roundsPerCycle = nPlayers - appData.tourneyType;
10397         pairingsPerRound = appData.tourneyType;
10398     }
10399     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10400     gamesPerCycle = gamesPerRound * roundsPerCycle;
10401     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10402     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10403     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10404     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10405     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10406     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10407
10408     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10409     if(appData.roundSync) *syncInterval = gamesPerRound;
10410
10411     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10412
10413     if(appData.tourneyType == 0) {
10414         if(curPairing == (nPlayers-1)/2 ) {
10415             *whitePlayer = curRound;
10416             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10417         } else {
10418             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10419             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10420             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10421             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10422         }
10423     } else if(appData.tourneyType > 1) {
10424         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10425         *whitePlayer = curRound + appData.tourneyType;
10426     } else if(appData.tourneyType > 0) {
10427         *whitePlayer = curPairing;
10428         *blackPlayer = curRound + appData.tourneyType;
10429     }
10430
10431     // take care of white/black alternation per round.
10432     // For cycles and games this is already taken care of by default, derived from matchGame!
10433     return curRound & 1;
10434 }
10435
10436 int
10437 NextTourneyGame (int nr, int *swapColors)
10438 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10439     char *p, *q;
10440     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10441     FILE *tf;
10442     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10443     tf = fopen(appData.tourneyFile, "r");
10444     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10445     ParseArgsFromFile(tf); fclose(tf);
10446     InitTimeControls(); // TC might be altered from tourney file
10447
10448     nPlayers = CountPlayers(appData.participants); // count participants
10449     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10450     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10451
10452     if(syncInterval) {
10453         p = q = appData.results;
10454         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10455         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10456             DisplayMessage(_("Waiting for other game(s)"),"");
10457             waitingForGame = TRUE;
10458             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10459             return 0;
10460         }
10461         waitingForGame = FALSE;
10462     }
10463
10464     if(appData.tourneyType < 0) {
10465         if(nr>=0 && !pairingReceived) {
10466             char buf[1<<16];
10467             if(pairing.pr == NoProc) {
10468                 if(!appData.pairingEngine[0]) {
10469                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10470                     return 0;
10471                 }
10472                 StartChessProgram(&pairing); // starts the pairing engine
10473             }
10474             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10475             SendToProgram(buf, &pairing);
10476             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10477             SendToProgram(buf, &pairing);
10478             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10479         }
10480         pairingReceived = 0;                              // ... so we continue here
10481         *swapColors = 0;
10482         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10483         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10484         matchGame = 1; roundNr = nr / syncInterval + 1;
10485     }
10486
10487     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10488
10489     // redefine engines, engine dir, etc.
10490     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10491     if(first.pr == NoProc) {
10492       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10493       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10494     }
10495     if(second.pr == NoProc) {
10496       SwapEngines(1);
10497       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10498       SwapEngines(1);         // and make that valid for second engine by swapping
10499       InitEngine(&second, 1);
10500     }
10501     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10502     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10503     return OK;
10504 }
10505
10506 void
10507 NextMatchGame ()
10508 {   // performs game initialization that does not invoke engines, and then tries to start the game
10509     int res, firstWhite, swapColors = 0;
10510     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10511     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
10512         char buf[MSG_SIZ];
10513         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10514         if(strcmp(buf, currentDebugFile)) { // name has changed
10515             FILE *f = fopen(buf, "w");
10516             if(f) { // if opening the new file failed, just keep using the old one
10517                 ASSIGN(currentDebugFile, buf);
10518                 fclose(debugFP);
10519                 debugFP = f;
10520             }
10521             if(appData.serverFileName) {
10522                 if(serverFP) fclose(serverFP);
10523                 serverFP = fopen(appData.serverFileName, "w");
10524                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10525                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10526             }
10527         }
10528     }
10529     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10530     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10531     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10532     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10533     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10534     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10535     Reset(FALSE, first.pr != NoProc);
10536     res = LoadGameOrPosition(matchGame); // setup game
10537     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10538     if(!res) return; // abort when bad game/pos file
10539     TwoMachinesEvent();
10540 }
10541
10542 void
10543 UserAdjudicationEvent (int result)
10544 {
10545     ChessMove gameResult = GameIsDrawn;
10546
10547     if( result > 0 ) {
10548         gameResult = WhiteWins;
10549     }
10550     else if( result < 0 ) {
10551         gameResult = BlackWins;
10552     }
10553
10554     if( gameMode == TwoMachinesPlay ) {
10555         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10556     }
10557 }
10558
10559
10560 // [HGM] save: calculate checksum of game to make games easily identifiable
10561 int
10562 StringCheckSum (char *s)
10563 {
10564         int i = 0;
10565         if(s==NULL) return 0;
10566         while(*s) i = i*259 + *s++;
10567         return i;
10568 }
10569
10570 int
10571 GameCheckSum ()
10572 {
10573         int i, sum=0;
10574         for(i=backwardMostMove; i<forwardMostMove; i++) {
10575                 sum += pvInfoList[i].depth;
10576                 sum += StringCheckSum(parseList[i]);
10577                 sum += StringCheckSum(commentList[i]);
10578                 sum *= 261;
10579         }
10580         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10581         return sum + StringCheckSum(commentList[i]);
10582 } // end of save patch
10583
10584 void
10585 GameEnds (ChessMove result, char *resultDetails, int whosays)
10586 {
10587     GameMode nextGameMode;
10588     int isIcsGame;
10589     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10590
10591     if(endingGame) return; /* [HGM] crash: forbid recursion */
10592     endingGame = 1;
10593     if(twoBoards) { // [HGM] dual: switch back to one board
10594         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10595         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10596     }
10597     if (appData.debugMode) {
10598       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10599               result, resultDetails ? resultDetails : "(null)", whosays);
10600     }
10601
10602     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10603
10604     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10605
10606     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10607         /* If we are playing on ICS, the server decides when the
10608            game is over, but the engine can offer to draw, claim
10609            a draw, or resign.
10610          */
10611 #if ZIPPY
10612         if (appData.zippyPlay && first.initDone) {
10613             if (result == GameIsDrawn) {
10614                 /* In case draw still needs to be claimed */
10615                 SendToICS(ics_prefix);
10616                 SendToICS("draw\n");
10617             } else if (StrCaseStr(resultDetails, "resign")) {
10618                 SendToICS(ics_prefix);
10619                 SendToICS("resign\n");
10620             }
10621         }
10622 #endif
10623         endingGame = 0; /* [HGM] crash */
10624         return;
10625     }
10626
10627     /* If we're loading the game from a file, stop */
10628     if (whosays == GE_FILE) {
10629       (void) StopLoadGameTimer();
10630       gameFileFP = NULL;
10631     }
10632
10633     /* Cancel draw offers */
10634     first.offeredDraw = second.offeredDraw = 0;
10635
10636     /* If this is an ICS game, only ICS can really say it's done;
10637        if not, anyone can. */
10638     isIcsGame = (gameMode == IcsPlayingWhite ||
10639                  gameMode == IcsPlayingBlack ||
10640                  gameMode == IcsObserving    ||
10641                  gameMode == IcsExamining);
10642
10643     if (!isIcsGame || whosays == GE_ICS) {
10644         /* OK -- not an ICS game, or ICS said it was done */
10645         StopClocks();
10646         if (!isIcsGame && !appData.noChessProgram)
10647           SetUserThinkingEnables();
10648
10649         /* [HGM] if a machine claims the game end we verify this claim */
10650         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10651             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10652                 char claimer;
10653                 ChessMove trueResult = (ChessMove) -1;
10654
10655                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10656                                             first.twoMachinesColor[0] :
10657                                             second.twoMachinesColor[0] ;
10658
10659                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10660                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10661                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10662                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10663                 } else
10664                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10665                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10666                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10667                 } else
10668                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10669                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10670                 }
10671
10672                 // now verify win claims, but not in drop games, as we don't understand those yet
10673                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10674                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10675                     (result == WhiteWins && claimer == 'w' ||
10676                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10677                       if (appData.debugMode) {
10678                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10679                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10680                       }
10681                       if(result != trueResult) {
10682                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10683                               result = claimer == 'w' ? BlackWins : WhiteWins;
10684                               resultDetails = buf;
10685                       }
10686                 } else
10687                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10688                     && (forwardMostMove <= backwardMostMove ||
10689                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10690                         (claimer=='b')==(forwardMostMove&1))
10691                                                                                   ) {
10692                       /* [HGM] verify: draws that were not flagged are false claims */
10693                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10694                       result = claimer == 'w' ? BlackWins : WhiteWins;
10695                       resultDetails = buf;
10696                 }
10697                 /* (Claiming a loss is accepted no questions asked!) */
10698             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10699                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10700                 result = GameUnfinished;
10701                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10702             }
10703             /* [HGM] bare: don't allow bare King to win */
10704             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10705                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10706                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10707                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10708                && result != GameIsDrawn)
10709             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10710                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10711                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10712                         if(p >= 0 && p <= (int)WhiteKing) k++;
10713                 }
10714                 if (appData.debugMode) {
10715                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10716                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10717                 }
10718                 if(k <= 1) {
10719                         result = GameIsDrawn;
10720                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10721                         resultDetails = buf;
10722                 }
10723             }
10724         }
10725
10726
10727         if(serverMoves != NULL && !loadFlag) { char c = '=';
10728             if(result==WhiteWins) c = '+';
10729             if(result==BlackWins) c = '-';
10730             if(resultDetails != NULL)
10731                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10732         }
10733         if (resultDetails != NULL) {
10734             gameInfo.result = result;
10735             gameInfo.resultDetails = StrSave(resultDetails);
10736
10737             /* display last move only if game was not loaded from file */
10738             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10739                 DisplayMove(currentMove - 1);
10740
10741             if (forwardMostMove != 0) {
10742                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10743                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10744                                                                 ) {
10745                     if (*appData.saveGameFile != NULLCHAR) {
10746                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10747                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10748                         else
10749                         SaveGameToFile(appData.saveGameFile, TRUE);
10750                     } else if (appData.autoSaveGames) {
10751                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10752                     }
10753                     if (*appData.savePositionFile != NULLCHAR) {
10754                         SavePositionToFile(appData.savePositionFile);
10755                     }
10756                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10757                 }
10758             }
10759
10760             /* Tell program how game ended in case it is learning */
10761             /* [HGM] Moved this to after saving the PGN, just in case */
10762             /* engine died and we got here through time loss. In that */
10763             /* case we will get a fatal error writing the pipe, which */
10764             /* would otherwise lose us the PGN.                       */
10765             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10766             /* output during GameEnds should never be fatal anymore   */
10767             if (gameMode == MachinePlaysWhite ||
10768                 gameMode == MachinePlaysBlack ||
10769                 gameMode == TwoMachinesPlay ||
10770                 gameMode == IcsPlayingWhite ||
10771                 gameMode == IcsPlayingBlack ||
10772                 gameMode == BeginningOfGame) {
10773                 char buf[MSG_SIZ];
10774                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10775                         resultDetails);
10776                 if (first.pr != NoProc) {
10777                     SendToProgram(buf, &first);
10778                 }
10779                 if (second.pr != NoProc &&
10780                     gameMode == TwoMachinesPlay) {
10781                     SendToProgram(buf, &second);
10782                 }
10783             }
10784         }
10785
10786         if (appData.icsActive) {
10787             if (appData.quietPlay &&
10788                 (gameMode == IcsPlayingWhite ||
10789                  gameMode == IcsPlayingBlack)) {
10790                 SendToICS(ics_prefix);
10791                 SendToICS("set shout 1\n");
10792             }
10793             nextGameMode = IcsIdle;
10794             ics_user_moved = FALSE;
10795             /* clean up premove.  It's ugly when the game has ended and the
10796              * premove highlights are still on the board.
10797              */
10798             if (gotPremove) {
10799               gotPremove = FALSE;
10800               ClearPremoveHighlights();
10801               DrawPosition(FALSE, boards[currentMove]);
10802             }
10803             if (whosays == GE_ICS) {
10804                 switch (result) {
10805                 case WhiteWins:
10806                     if (gameMode == IcsPlayingWhite)
10807                         PlayIcsWinSound();
10808                     else if(gameMode == IcsPlayingBlack)
10809                         PlayIcsLossSound();
10810                     break;
10811                 case BlackWins:
10812                     if (gameMode == IcsPlayingBlack)
10813                         PlayIcsWinSound();
10814                     else if(gameMode == IcsPlayingWhite)
10815                         PlayIcsLossSound();
10816                     break;
10817                 case GameIsDrawn:
10818                     PlayIcsDrawSound();
10819                     break;
10820                 default:
10821                     PlayIcsUnfinishedSound();
10822                 }
10823             }
10824         } else if (gameMode == EditGame ||
10825                    gameMode == PlayFromGameFile ||
10826                    gameMode == AnalyzeMode ||
10827                    gameMode == AnalyzeFile) {
10828             nextGameMode = gameMode;
10829         } else {
10830             nextGameMode = EndOfGame;
10831         }
10832         pausing = FALSE;
10833         ModeHighlight();
10834     } else {
10835         nextGameMode = gameMode;
10836     }
10837
10838     if (appData.noChessProgram) {
10839         gameMode = nextGameMode;
10840         ModeHighlight();
10841         endingGame = 0; /* [HGM] crash */
10842         return;
10843     }
10844
10845     if (first.reuse) {
10846         /* Put first chess program into idle state */
10847         if (first.pr != NoProc &&
10848             (gameMode == MachinePlaysWhite ||
10849              gameMode == MachinePlaysBlack ||
10850              gameMode == TwoMachinesPlay ||
10851              gameMode == IcsPlayingWhite ||
10852              gameMode == IcsPlayingBlack ||
10853              gameMode == BeginningOfGame)) {
10854             SendToProgram("force\n", &first);
10855             if (first.usePing) {
10856               char buf[MSG_SIZ];
10857               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10858               SendToProgram(buf, &first);
10859             }
10860         }
10861     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10862         /* Kill off first chess program */
10863         if (first.isr != NULL)
10864           RemoveInputSource(first.isr);
10865         first.isr = NULL;
10866
10867         if (first.pr != NoProc) {
10868             ExitAnalyzeMode();
10869             DoSleep( appData.delayBeforeQuit );
10870             SendToProgram("quit\n", &first);
10871             DoSleep( appData.delayAfterQuit );
10872             DestroyChildProcess(first.pr, first.useSigterm);
10873             first.reload = TRUE;
10874         }
10875         first.pr = NoProc;
10876     }
10877     if (second.reuse) {
10878         /* Put second chess program into idle state */
10879         if (second.pr != NoProc &&
10880             gameMode == TwoMachinesPlay) {
10881             SendToProgram("force\n", &second);
10882             if (second.usePing) {
10883               char buf[MSG_SIZ];
10884               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10885               SendToProgram(buf, &second);
10886             }
10887         }
10888     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10889         /* Kill off second chess program */
10890         if (second.isr != NULL)
10891           RemoveInputSource(second.isr);
10892         second.isr = NULL;
10893
10894         if (second.pr != NoProc) {
10895             DoSleep( appData.delayBeforeQuit );
10896             SendToProgram("quit\n", &second);
10897             DoSleep( appData.delayAfterQuit );
10898             DestroyChildProcess(second.pr, second.useSigterm);
10899             second.reload = TRUE;
10900         }
10901         second.pr = NoProc;
10902     }
10903
10904     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10905         char resChar = '=';
10906         switch (result) {
10907         case WhiteWins:
10908           resChar = '+';
10909           if (first.twoMachinesColor[0] == 'w') {
10910             first.matchWins++;
10911           } else {
10912             second.matchWins++;
10913           }
10914           break;
10915         case BlackWins:
10916           resChar = '-';
10917           if (first.twoMachinesColor[0] == 'b') {
10918             first.matchWins++;
10919           } else {
10920             second.matchWins++;
10921           }
10922           break;
10923         case GameUnfinished:
10924           resChar = ' ';
10925         default:
10926           break;
10927         }
10928
10929         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10930         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10931             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10932             ReserveGame(nextGame, resChar); // sets nextGame
10933             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10934             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10935         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10936
10937         if (nextGame <= appData.matchGames && !abortMatch) {
10938             gameMode = nextGameMode;
10939             matchGame = nextGame; // this will be overruled in tourney mode!
10940             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10941             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10942             endingGame = 0; /* [HGM] crash */
10943             return;
10944         } else {
10945             gameMode = nextGameMode;
10946             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10947                      first.tidy, second.tidy,
10948                      first.matchWins, second.matchWins,
10949                      appData.matchGames - (first.matchWins + second.matchWins));
10950             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10951             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10952             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10953             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10954                 first.twoMachinesColor = "black\n";
10955                 second.twoMachinesColor = "white\n";
10956             } else {
10957                 first.twoMachinesColor = "white\n";
10958                 second.twoMachinesColor = "black\n";
10959             }
10960         }
10961     }
10962     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10963         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10964       ExitAnalyzeMode();
10965     gameMode = nextGameMode;
10966     ModeHighlight();
10967     endingGame = 0;  /* [HGM] crash */
10968     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10969         if(matchMode == TRUE) { // match through command line: exit with or without popup
10970             if(ranking) {
10971                 ToNrEvent(forwardMostMove);
10972                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10973                 else ExitEvent(0);
10974             } else DisplayFatalError(buf, 0, 0);
10975         } else { // match through menu; just stop, with or without popup
10976             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10977             ModeHighlight();
10978             if(ranking){
10979                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10980             } else DisplayNote(buf);
10981       }
10982       if(ranking) free(ranking);
10983     }
10984 }
10985
10986 /* Assumes program was just initialized (initString sent).
10987    Leaves program in force mode. */
10988 void
10989 FeedMovesToProgram (ChessProgramState *cps, int upto)
10990 {
10991     int i;
10992
10993     if (appData.debugMode)
10994       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10995               startedFromSetupPosition ? "position and " : "",
10996               backwardMostMove, upto, cps->which);
10997     if(currentlyInitializedVariant != gameInfo.variant) {
10998       char buf[MSG_SIZ];
10999         // [HGM] variantswitch: make engine aware of new variant
11000         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11001                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11002         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11003         SendToProgram(buf, cps);
11004         currentlyInitializedVariant = gameInfo.variant;
11005     }
11006     SendToProgram("force\n", cps);
11007     if (startedFromSetupPosition) {
11008         SendBoard(cps, backwardMostMove);
11009     if (appData.debugMode) {
11010         fprintf(debugFP, "feedMoves\n");
11011     }
11012     }
11013     for (i = backwardMostMove; i < upto; i++) {
11014         SendMoveToProgram(i, cps);
11015     }
11016 }
11017
11018
11019 int
11020 ResurrectChessProgram ()
11021 {
11022      /* The chess program may have exited.
11023         If so, restart it and feed it all the moves made so far. */
11024     static int doInit = 0;
11025
11026     if (appData.noChessProgram) return 1;
11027
11028     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11029         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11030         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11031         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11032     } else {
11033         if (first.pr != NoProc) return 1;
11034         StartChessProgram(&first);
11035     }
11036     InitChessProgram(&first, FALSE);
11037     FeedMovesToProgram(&first, currentMove);
11038
11039     if (!first.sendTime) {
11040         /* can't tell gnuchess what its clock should read,
11041            so we bow to its notion. */
11042         ResetClocks();
11043         timeRemaining[0][currentMove] = whiteTimeRemaining;
11044         timeRemaining[1][currentMove] = blackTimeRemaining;
11045     }
11046
11047     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11048                 appData.icsEngineAnalyze) && first.analysisSupport) {
11049       SendToProgram("analyze\n", &first);
11050       first.analyzing = TRUE;
11051     }
11052     return 1;
11053 }
11054
11055 /*
11056  * Button procedures
11057  */
11058 void
11059 Reset (int redraw, int init)
11060 {
11061     int i;
11062
11063     if (appData.debugMode) {
11064         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11065                 redraw, init, gameMode);
11066     }
11067     CleanupTail(); // [HGM] vari: delete any stored variations
11068     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11069     pausing = pauseExamInvalid = FALSE;
11070     startedFromSetupPosition = blackPlaysFirst = FALSE;
11071     firstMove = TRUE;
11072     whiteFlag = blackFlag = FALSE;
11073     userOfferedDraw = FALSE;
11074     hintRequested = bookRequested = FALSE;
11075     first.maybeThinking = FALSE;
11076     second.maybeThinking = FALSE;
11077     first.bookSuspend = FALSE; // [HGM] book
11078     second.bookSuspend = FALSE;
11079     thinkOutput[0] = NULLCHAR;
11080     lastHint[0] = NULLCHAR;
11081     ClearGameInfo(&gameInfo);
11082     gameInfo.variant = StringToVariant(appData.variant);
11083     ics_user_moved = ics_clock_paused = FALSE;
11084     ics_getting_history = H_FALSE;
11085     ics_gamenum = -1;
11086     white_holding[0] = black_holding[0] = NULLCHAR;
11087     ClearProgramStats();
11088     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11089
11090     ResetFrontEnd();
11091     ClearHighlights();
11092     flipView = appData.flipView;
11093     ClearPremoveHighlights();
11094     gotPremove = FALSE;
11095     alarmSounded = FALSE;
11096
11097     GameEnds(EndOfFile, NULL, GE_PLAYER);
11098     if(appData.serverMovesName != NULL) {
11099         /* [HGM] prepare to make moves file for broadcasting */
11100         clock_t t = clock();
11101         if(serverMoves != NULL) fclose(serverMoves);
11102         serverMoves = fopen(appData.serverMovesName, "r");
11103         if(serverMoves != NULL) {
11104             fclose(serverMoves);
11105             /* delay 15 sec before overwriting, so all clients can see end */
11106             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11107         }
11108         serverMoves = fopen(appData.serverMovesName, "w");
11109     }
11110
11111     ExitAnalyzeMode();
11112     gameMode = BeginningOfGame;
11113     ModeHighlight();
11114     if(appData.icsActive) gameInfo.variant = VariantNormal;
11115     currentMove = forwardMostMove = backwardMostMove = 0;
11116     MarkTargetSquares(1);
11117     InitPosition(redraw);
11118     for (i = 0; i < MAX_MOVES; i++) {
11119         if (commentList[i] != NULL) {
11120             free(commentList[i]);
11121             commentList[i] = NULL;
11122         }
11123     }
11124     ResetClocks();
11125     timeRemaining[0][0] = whiteTimeRemaining;
11126     timeRemaining[1][0] = blackTimeRemaining;
11127
11128     if (first.pr == NoProc) {
11129         StartChessProgram(&first);
11130     }
11131     if (init) {
11132             InitChessProgram(&first, startedFromSetupPosition);
11133     }
11134     DisplayTitle("");
11135     DisplayMessage("", "");
11136     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11137     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11138     ClearMap();        // [HGM] exclude: invalidate map
11139 }
11140
11141 void
11142 AutoPlayGameLoop ()
11143 {
11144     for (;;) {
11145         if (!AutoPlayOneMove())
11146           return;
11147         if (matchMode || appData.timeDelay == 0)
11148           continue;
11149         if (appData.timeDelay < 0)
11150           return;
11151         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11152         break;
11153     }
11154 }
11155
11156 void
11157 AnalyzeNextGame()
11158 {
11159     ReloadGame(1); // next game
11160 }
11161
11162 int
11163 AutoPlayOneMove ()
11164 {
11165     int fromX, fromY, toX, toY;
11166
11167     if (appData.debugMode) {
11168       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11169     }
11170
11171     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11172       return FALSE;
11173
11174     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11175       pvInfoList[currentMove].depth = programStats.depth;
11176       pvInfoList[currentMove].score = programStats.score;
11177       pvInfoList[currentMove].time  = 0;
11178       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11179     }
11180
11181     if (currentMove >= forwardMostMove) {
11182       if(gameMode == AnalyzeFile) {
11183           if(appData.loadGameIndex == -1) {
11184             GameEnds(EndOfFile, NULL, GE_FILE);
11185           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11186           } else {
11187           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11188         }
11189       }
11190 //      gameMode = EndOfGame;
11191 //      ModeHighlight();
11192
11193       /* [AS] Clear current move marker at the end of a game */
11194       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11195
11196       return FALSE;
11197     }
11198
11199     toX = moveList[currentMove][2] - AAA;
11200     toY = moveList[currentMove][3] - ONE;
11201
11202     if (moveList[currentMove][1] == '@') {
11203         if (appData.highlightLastMove) {
11204             SetHighlights(-1, -1, toX, toY);
11205         }
11206     } else {
11207         fromX = moveList[currentMove][0] - AAA;
11208         fromY = moveList[currentMove][1] - ONE;
11209
11210         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11211
11212         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11213
11214         if (appData.highlightLastMove) {
11215             SetHighlights(fromX, fromY, toX, toY);
11216         }
11217     }
11218     DisplayMove(currentMove);
11219     SendMoveToProgram(currentMove++, &first);
11220     DisplayBothClocks();
11221     DrawPosition(FALSE, boards[currentMove]);
11222     // [HGM] PV info: always display, routine tests if empty
11223     DisplayComment(currentMove - 1, commentList[currentMove]);
11224     return TRUE;
11225 }
11226
11227
11228 int
11229 LoadGameOneMove (ChessMove readAhead)
11230 {
11231     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11232     char promoChar = NULLCHAR;
11233     ChessMove moveType;
11234     char move[MSG_SIZ];
11235     char *p, *q;
11236
11237     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11238         gameMode != AnalyzeMode && gameMode != Training) {
11239         gameFileFP = NULL;
11240         return FALSE;
11241     }
11242
11243     yyboardindex = forwardMostMove;
11244     if (readAhead != EndOfFile) {
11245       moveType = readAhead;
11246     } else {
11247       if (gameFileFP == NULL)
11248           return FALSE;
11249       moveType = (ChessMove) Myylex();
11250     }
11251
11252     done = FALSE;
11253     switch (moveType) {
11254       case Comment:
11255         if (appData.debugMode)
11256           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11257         p = yy_text;
11258
11259         /* append the comment but don't display it */
11260         AppendComment(currentMove, p, FALSE);
11261         return TRUE;
11262
11263       case WhiteCapturesEnPassant:
11264       case BlackCapturesEnPassant:
11265       case WhitePromotion:
11266       case BlackPromotion:
11267       case WhiteNonPromotion:
11268       case BlackNonPromotion:
11269       case NormalMove:
11270       case WhiteKingSideCastle:
11271       case WhiteQueenSideCastle:
11272       case BlackKingSideCastle:
11273       case BlackQueenSideCastle:
11274       case WhiteKingSideCastleWild:
11275       case WhiteQueenSideCastleWild:
11276       case BlackKingSideCastleWild:
11277       case BlackQueenSideCastleWild:
11278       /* PUSH Fabien */
11279       case WhiteHSideCastleFR:
11280       case WhiteASideCastleFR:
11281       case BlackHSideCastleFR:
11282       case BlackASideCastleFR:
11283       /* POP Fabien */
11284         if (appData.debugMode)
11285           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11286         fromX = currentMoveString[0] - AAA;
11287         fromY = currentMoveString[1] - ONE;
11288         toX = currentMoveString[2] - AAA;
11289         toY = currentMoveString[3] - ONE;
11290         promoChar = currentMoveString[4];
11291         break;
11292
11293       case WhiteDrop:
11294       case BlackDrop:
11295         if (appData.debugMode)
11296           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11297         fromX = moveType == WhiteDrop ?
11298           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11299         (int) CharToPiece(ToLower(currentMoveString[0]));
11300         fromY = DROP_RANK;
11301         toX = currentMoveString[2] - AAA;
11302         toY = currentMoveString[3] - ONE;
11303         break;
11304
11305       case WhiteWins:
11306       case BlackWins:
11307       case GameIsDrawn:
11308       case GameUnfinished:
11309         if (appData.debugMode)
11310           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11311         p = strchr(yy_text, '{');
11312         if (p == NULL) p = strchr(yy_text, '(');
11313         if (p == NULL) {
11314             p = yy_text;
11315             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11316         } else {
11317             q = strchr(p, *p == '{' ? '}' : ')');
11318             if (q != NULL) *q = NULLCHAR;
11319             p++;
11320         }
11321         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11322         GameEnds(moveType, p, GE_FILE);
11323         done = TRUE;
11324         if (cmailMsgLoaded) {
11325             ClearHighlights();
11326             flipView = WhiteOnMove(currentMove);
11327             if (moveType == GameUnfinished) flipView = !flipView;
11328             if (appData.debugMode)
11329               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11330         }
11331         break;
11332
11333       case EndOfFile:
11334         if (appData.debugMode)
11335           fprintf(debugFP, "Parser hit end of file\n");
11336         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11337           case MT_NONE:
11338           case MT_CHECK:
11339             break;
11340           case MT_CHECKMATE:
11341           case MT_STAINMATE:
11342             if (WhiteOnMove(currentMove)) {
11343                 GameEnds(BlackWins, "Black mates", GE_FILE);
11344             } else {
11345                 GameEnds(WhiteWins, "White mates", GE_FILE);
11346             }
11347             break;
11348           case MT_STALEMATE:
11349             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11350             break;
11351         }
11352         done = TRUE;
11353         break;
11354
11355       case MoveNumberOne:
11356         if (lastLoadGameStart == GNUChessGame) {
11357             /* GNUChessGames have numbers, but they aren't move numbers */
11358             if (appData.debugMode)
11359               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11360                       yy_text, (int) moveType);
11361             return LoadGameOneMove(EndOfFile); /* tail recursion */
11362         }
11363         /* else fall thru */
11364
11365       case XBoardGame:
11366       case GNUChessGame:
11367       case PGNTag:
11368         /* Reached start of next game in file */
11369         if (appData.debugMode)
11370           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11371         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11372           case MT_NONE:
11373           case MT_CHECK:
11374             break;
11375           case MT_CHECKMATE:
11376           case MT_STAINMATE:
11377             if (WhiteOnMove(currentMove)) {
11378                 GameEnds(BlackWins, "Black mates", GE_FILE);
11379             } else {
11380                 GameEnds(WhiteWins, "White mates", GE_FILE);
11381             }
11382             break;
11383           case MT_STALEMATE:
11384             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11385             break;
11386         }
11387         done = TRUE;
11388         break;
11389
11390       case PositionDiagram:     /* should not happen; ignore */
11391       case ElapsedTime:         /* ignore */
11392       case NAG:                 /* ignore */
11393         if (appData.debugMode)
11394           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11395                   yy_text, (int) moveType);
11396         return LoadGameOneMove(EndOfFile); /* tail recursion */
11397
11398       case IllegalMove:
11399         if (appData.testLegality) {
11400             if (appData.debugMode)
11401               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11402             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11403                     (forwardMostMove / 2) + 1,
11404                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11405             DisplayError(move, 0);
11406             done = TRUE;
11407         } else {
11408             if (appData.debugMode)
11409               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11410                       yy_text, currentMoveString);
11411             fromX = currentMoveString[0] - AAA;
11412             fromY = currentMoveString[1] - ONE;
11413             toX = currentMoveString[2] - AAA;
11414             toY = currentMoveString[3] - ONE;
11415             promoChar = currentMoveString[4];
11416         }
11417         break;
11418
11419       case AmbiguousMove:
11420         if (appData.debugMode)
11421           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11422         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11423                 (forwardMostMove / 2) + 1,
11424                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11425         DisplayError(move, 0);
11426         done = TRUE;
11427         break;
11428
11429       default:
11430       case ImpossibleMove:
11431         if (appData.debugMode)
11432           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11433         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11434                 (forwardMostMove / 2) + 1,
11435                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11436         DisplayError(move, 0);
11437         done = TRUE;
11438         break;
11439     }
11440
11441     if (done) {
11442         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11443             DrawPosition(FALSE, boards[currentMove]);
11444             DisplayBothClocks();
11445             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11446               DisplayComment(currentMove - 1, commentList[currentMove]);
11447         }
11448         (void) StopLoadGameTimer();
11449         gameFileFP = NULL;
11450         cmailOldMove = forwardMostMove;
11451         return FALSE;
11452     } else {
11453         /* currentMoveString is set as a side-effect of yylex */
11454
11455         thinkOutput[0] = NULLCHAR;
11456         MakeMove(fromX, fromY, toX, toY, promoChar);
11457         currentMove = forwardMostMove;
11458         return TRUE;
11459     }
11460 }
11461
11462 /* Load the nth game from the given file */
11463 int
11464 LoadGameFromFile (char *filename, int n, char *title, int useList)
11465 {
11466     FILE *f;
11467     char buf[MSG_SIZ];
11468
11469     if (strcmp(filename, "-") == 0) {
11470         f = stdin;
11471         title = "stdin";
11472     } else {
11473         f = fopen(filename, "rb");
11474         if (f == NULL) {
11475           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11476             DisplayError(buf, errno);
11477             return FALSE;
11478         }
11479     }
11480     if (fseek(f, 0, 0) == -1) {
11481         /* f is not seekable; probably a pipe */
11482         useList = FALSE;
11483     }
11484     if (useList && n == 0) {
11485         int error = GameListBuild(f);
11486         if (error) {
11487             DisplayError(_("Cannot build game list"), error);
11488         } else if (!ListEmpty(&gameList) &&
11489                    ((ListGame *) gameList.tailPred)->number > 1) {
11490             GameListPopUp(f, title);
11491             return TRUE;
11492         }
11493         GameListDestroy();
11494         n = 1;
11495     }
11496     if (n == 0) n = 1;
11497     return LoadGame(f, n, title, FALSE);
11498 }
11499
11500
11501 void
11502 MakeRegisteredMove ()
11503 {
11504     int fromX, fromY, toX, toY;
11505     char promoChar;
11506     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11507         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11508           case CMAIL_MOVE:
11509           case CMAIL_DRAW:
11510             if (appData.debugMode)
11511               fprintf(debugFP, "Restoring %s for game %d\n",
11512                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11513
11514             thinkOutput[0] = NULLCHAR;
11515             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11516             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11517             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11518             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11519             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11520             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11521             MakeMove(fromX, fromY, toX, toY, promoChar);
11522             ShowMove(fromX, fromY, toX, toY);
11523
11524             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11525               case MT_NONE:
11526               case MT_CHECK:
11527                 break;
11528
11529               case MT_CHECKMATE:
11530               case MT_STAINMATE:
11531                 if (WhiteOnMove(currentMove)) {
11532                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11533                 } else {
11534                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11535                 }
11536                 break;
11537
11538               case MT_STALEMATE:
11539                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11540                 break;
11541             }
11542
11543             break;
11544
11545           case CMAIL_RESIGN:
11546             if (WhiteOnMove(currentMove)) {
11547                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11548             } else {
11549                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11550             }
11551             break;
11552
11553           case CMAIL_ACCEPT:
11554             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11555             break;
11556
11557           default:
11558             break;
11559         }
11560     }
11561
11562     return;
11563 }
11564
11565 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11566 int
11567 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11568 {
11569     int retVal;
11570
11571     if (gameNumber > nCmailGames) {
11572         DisplayError(_("No more games in this message"), 0);
11573         return FALSE;
11574     }
11575     if (f == lastLoadGameFP) {
11576         int offset = gameNumber - lastLoadGameNumber;
11577         if (offset == 0) {
11578             cmailMsg[0] = NULLCHAR;
11579             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11580                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11581                 nCmailMovesRegistered--;
11582             }
11583             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11584             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11585                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11586             }
11587         } else {
11588             if (! RegisterMove()) return FALSE;
11589         }
11590     }
11591
11592     retVal = LoadGame(f, gameNumber, title, useList);
11593
11594     /* Make move registered during previous look at this game, if any */
11595     MakeRegisteredMove();
11596
11597     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11598         commentList[currentMove]
11599           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11600         DisplayComment(currentMove - 1, commentList[currentMove]);
11601     }
11602
11603     return retVal;
11604 }
11605
11606 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11607 int
11608 ReloadGame (int offset)
11609 {
11610     int gameNumber = lastLoadGameNumber + offset;
11611     if (lastLoadGameFP == NULL) {
11612         DisplayError(_("No game has been loaded yet"), 0);
11613         return FALSE;
11614     }
11615     if (gameNumber <= 0) {
11616         DisplayError(_("Can't back up any further"), 0);
11617         return FALSE;
11618     }
11619     if (cmailMsgLoaded) {
11620         return CmailLoadGame(lastLoadGameFP, gameNumber,
11621                              lastLoadGameTitle, lastLoadGameUseList);
11622     } else {
11623         return LoadGame(lastLoadGameFP, gameNumber,
11624                         lastLoadGameTitle, lastLoadGameUseList);
11625     }
11626 }
11627
11628 int keys[EmptySquare+1];
11629
11630 int
11631 PositionMatches (Board b1, Board b2)
11632 {
11633     int r, f, sum=0;
11634     switch(appData.searchMode) {
11635         case 1: return CompareWithRights(b1, b2);
11636         case 2:
11637             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11638                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11639             }
11640             return TRUE;
11641         case 3:
11642             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11643               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11644                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11645             }
11646             return sum==0;
11647         case 4:
11648             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11649                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11650             }
11651             return sum==0;
11652     }
11653     return TRUE;
11654 }
11655
11656 #define Q_PROMO  4
11657 #define Q_EP     3
11658 #define Q_BCASTL 2
11659 #define Q_WCASTL 1
11660
11661 int pieceList[256], quickBoard[256];
11662 ChessSquare pieceType[256] = { EmptySquare };
11663 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11664 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11665 int soughtTotal, turn;
11666 Boolean epOK, flipSearch;
11667
11668 typedef struct {
11669     unsigned char piece, to;
11670 } Move;
11671
11672 #define DSIZE (250000)
11673
11674 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11675 Move *moveDatabase = initialSpace;
11676 unsigned int movePtr, dataSize = DSIZE;
11677
11678 int
11679 MakePieceList (Board board, int *counts)
11680 {
11681     int r, f, n=Q_PROMO, total=0;
11682     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11683     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11684         int sq = f + (r<<4);
11685         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11686             quickBoard[sq] = ++n;
11687             pieceList[n] = sq;
11688             pieceType[n] = board[r][f];
11689             counts[board[r][f]]++;
11690             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11691             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11692             total++;
11693         }
11694     }
11695     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11696     return total;
11697 }
11698
11699 void
11700 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11701 {
11702     int sq = fromX + (fromY<<4);
11703     int piece = quickBoard[sq];
11704     quickBoard[sq] = 0;
11705     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11706     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11707         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11708         moveDatabase[movePtr++].piece = Q_WCASTL;
11709         quickBoard[sq] = piece;
11710         piece = quickBoard[from]; quickBoard[from] = 0;
11711         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11712     } else
11713     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11714         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11715         moveDatabase[movePtr++].piece = Q_BCASTL;
11716         quickBoard[sq] = piece;
11717         piece = quickBoard[from]; quickBoard[from] = 0;
11718         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11719     } else
11720     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11721         quickBoard[(fromY<<4)+toX] = 0;
11722         moveDatabase[movePtr].piece = Q_EP;
11723         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11724         moveDatabase[movePtr].to = sq;
11725     } else
11726     if(promoPiece != pieceType[piece]) {
11727         moveDatabase[movePtr++].piece = Q_PROMO;
11728         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11729     }
11730     moveDatabase[movePtr].piece = piece;
11731     quickBoard[sq] = piece;
11732     movePtr++;
11733 }
11734
11735 int
11736 PackGame (Board board)
11737 {
11738     Move *newSpace = NULL;
11739     moveDatabase[movePtr].piece = 0; // terminate previous game
11740     if(movePtr > dataSize) {
11741         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11742         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11743         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11744         if(newSpace) {
11745             int i;
11746             Move *p = moveDatabase, *q = newSpace;
11747             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11748             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11749             moveDatabase = newSpace;
11750         } else { // calloc failed, we must be out of memory. Too bad...
11751             dataSize = 0; // prevent calloc events for all subsequent games
11752             return 0;     // and signal this one isn't cached
11753         }
11754     }
11755     movePtr++;
11756     MakePieceList(board, counts);
11757     return movePtr;
11758 }
11759
11760 int
11761 QuickCompare (Board board, int *minCounts, int *maxCounts)
11762 {   // compare according to search mode
11763     int r, f;
11764     switch(appData.searchMode)
11765     {
11766       case 1: // exact position match
11767         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11768         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11769             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11770         }
11771         break;
11772       case 2: // can have extra material on empty squares
11773         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11774             if(board[r][f] == EmptySquare) continue;
11775             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11776         }
11777         break;
11778       case 3: // material with exact Pawn structure
11779         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11780             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11781             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11782         } // fall through to material comparison
11783       case 4: // exact material
11784         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11785         break;
11786       case 6: // material range with given imbalance
11787         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11788         // fall through to range comparison
11789       case 5: // material range
11790         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11791     }
11792     return TRUE;
11793 }
11794
11795 int
11796 QuickScan (Board board, Move *move)
11797 {   // reconstruct game,and compare all positions in it
11798     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11799     do {
11800         int piece = move->piece;
11801         int to = move->to, from = pieceList[piece];
11802         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11803           if(!piece) return -1;
11804           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11805             piece = (++move)->piece;
11806             from = pieceList[piece];
11807             counts[pieceType[piece]]--;
11808             pieceType[piece] = (ChessSquare) move->to;
11809             counts[move->to]++;
11810           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11811             counts[pieceType[quickBoard[to]]]--;
11812             quickBoard[to] = 0; total--;
11813             move++;
11814             continue;
11815           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11816             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11817             from  = pieceList[piece]; // so this must be King
11818             quickBoard[from] = 0;
11819             pieceList[piece] = to;
11820             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11821             quickBoard[from] = 0; // rook
11822             quickBoard[to] = piece;
11823             to = move->to; piece = move->piece;
11824             goto aftercastle;
11825           }
11826         }
11827         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11828         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11829         quickBoard[from] = 0;
11830       aftercastle:
11831         quickBoard[to] = piece;
11832         pieceList[piece] = to;
11833         cnt++; turn ^= 3;
11834         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11835            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11836            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11837                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11838           ) {
11839             static int lastCounts[EmptySquare+1];
11840             int i;
11841             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11842             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11843         } else stretch = 0;
11844         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11845         move++;
11846     } while(1);
11847 }
11848
11849 void
11850 InitSearch ()
11851 {
11852     int r, f;
11853     flipSearch = FALSE;
11854     CopyBoard(soughtBoard, boards[currentMove]);
11855     soughtTotal = MakePieceList(soughtBoard, maxSought);
11856     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11857     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11858     CopyBoard(reverseBoard, boards[currentMove]);
11859     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11860         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11861         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11862         reverseBoard[r][f] = piece;
11863     }
11864     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11865     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11866     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11867                  || (boards[currentMove][CASTLING][2] == NoRights ||
11868                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11869                  && (boards[currentMove][CASTLING][5] == NoRights ||
11870                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11871       ) {
11872         flipSearch = TRUE;
11873         CopyBoard(flipBoard, soughtBoard);
11874         CopyBoard(rotateBoard, reverseBoard);
11875         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11876             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11877             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11878         }
11879     }
11880     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11881     if(appData.searchMode >= 5) {
11882         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11883         MakePieceList(soughtBoard, minSought);
11884         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11885     }
11886     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11887         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11888 }
11889
11890 GameInfo dummyInfo;
11891 static int creatingBook;
11892
11893 int
11894 GameContainsPosition (FILE *f, ListGame *lg)
11895 {
11896     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11897     int fromX, fromY, toX, toY;
11898     char promoChar;
11899     static int initDone=FALSE;
11900
11901     // weed out games based on numerical tag comparison
11902     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11903     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11904     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11905     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11906     if(!initDone) {
11907         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11908         initDone = TRUE;
11909     }
11910     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11911     else CopyBoard(boards[scratch], initialPosition); // default start position
11912     if(lg->moves) {
11913         turn = btm + 1;
11914         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11915         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11916     }
11917     if(btm) plyNr++;
11918     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11919     fseek(f, lg->offset, 0);
11920     yynewfile(f);
11921     while(1) {
11922         yyboardindex = scratch;
11923         quickFlag = plyNr+1;
11924         next = Myylex();
11925         quickFlag = 0;
11926         switch(next) {
11927             case PGNTag:
11928                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11929             default:
11930                 continue;
11931
11932             case XBoardGame:
11933             case GNUChessGame:
11934                 if(plyNr) return -1; // after we have seen moves, this is for new game
11935               continue;
11936
11937             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11938             case ImpossibleMove:
11939             case WhiteWins: // game ends here with these four
11940             case BlackWins:
11941             case GameIsDrawn:
11942             case GameUnfinished:
11943                 return -1;
11944
11945             case IllegalMove:
11946                 if(appData.testLegality) return -1;
11947             case WhiteCapturesEnPassant:
11948             case BlackCapturesEnPassant:
11949             case WhitePromotion:
11950             case BlackPromotion:
11951             case WhiteNonPromotion:
11952             case BlackNonPromotion:
11953             case NormalMove:
11954             case WhiteKingSideCastle:
11955             case WhiteQueenSideCastle:
11956             case BlackKingSideCastle:
11957             case BlackQueenSideCastle:
11958             case WhiteKingSideCastleWild:
11959             case WhiteQueenSideCastleWild:
11960             case BlackKingSideCastleWild:
11961             case BlackQueenSideCastleWild:
11962             case WhiteHSideCastleFR:
11963             case WhiteASideCastleFR:
11964             case BlackHSideCastleFR:
11965             case BlackASideCastleFR:
11966                 fromX = currentMoveString[0] - AAA;
11967                 fromY = currentMoveString[1] - ONE;
11968                 toX = currentMoveString[2] - AAA;
11969                 toY = currentMoveString[3] - ONE;
11970                 promoChar = currentMoveString[4];
11971                 break;
11972             case WhiteDrop:
11973             case BlackDrop:
11974                 fromX = next == WhiteDrop ?
11975                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11976                   (int) CharToPiece(ToLower(currentMoveString[0]));
11977                 fromY = DROP_RANK;
11978                 toX = currentMoveString[2] - AAA;
11979                 toY = currentMoveString[3] - ONE;
11980                 promoChar = 0;
11981                 break;
11982         }
11983         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11984         plyNr++;
11985         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11986         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11987         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11988         if(appData.findMirror) {
11989             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11990             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11991         }
11992     }
11993 }
11994
11995 /* Load the nth game from open file f */
11996 int
11997 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11998 {
11999     ChessMove cm;
12000     char buf[MSG_SIZ];
12001     int gn = gameNumber;
12002     ListGame *lg = NULL;
12003     int numPGNTags = 0;
12004     int err, pos = -1;
12005     GameMode oldGameMode;
12006     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12007
12008     if (appData.debugMode)
12009         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12010
12011     if (gameMode == Training )
12012         SetTrainingModeOff();
12013
12014     oldGameMode = gameMode;
12015     if (gameMode != BeginningOfGame) {
12016       Reset(FALSE, TRUE);
12017     }
12018
12019     gameFileFP = f;
12020     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12021         fclose(lastLoadGameFP);
12022     }
12023
12024     if (useList) {
12025         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12026
12027         if (lg) {
12028             fseek(f, lg->offset, 0);
12029             GameListHighlight(gameNumber);
12030             pos = lg->position;
12031             gn = 1;
12032         }
12033         else {
12034             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12035               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12036             else
12037             DisplayError(_("Game number out of range"), 0);
12038             return FALSE;
12039         }
12040     } else {
12041         GameListDestroy();
12042         if (fseek(f, 0, 0) == -1) {
12043             if (f == lastLoadGameFP ?
12044                 gameNumber == lastLoadGameNumber + 1 :
12045                 gameNumber == 1) {
12046                 gn = 1;
12047             } else {
12048                 DisplayError(_("Can't seek on game file"), 0);
12049                 return FALSE;
12050             }
12051         }
12052     }
12053     lastLoadGameFP = f;
12054     lastLoadGameNumber = gameNumber;
12055     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12056     lastLoadGameUseList = useList;
12057
12058     yynewfile(f);
12059
12060     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12061       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12062                 lg->gameInfo.black);
12063             DisplayTitle(buf);
12064     } else if (*title != NULLCHAR) {
12065         if (gameNumber > 1) {
12066           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12067             DisplayTitle(buf);
12068         } else {
12069             DisplayTitle(title);
12070         }
12071     }
12072
12073     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12074         gameMode = PlayFromGameFile;
12075         ModeHighlight();
12076     }
12077
12078     currentMove = forwardMostMove = backwardMostMove = 0;
12079     CopyBoard(boards[0], initialPosition);
12080     StopClocks();
12081
12082     /*
12083      * Skip the first gn-1 games in the file.
12084      * Also skip over anything that precedes an identifiable
12085      * start of game marker, to avoid being confused by
12086      * garbage at the start of the file.  Currently
12087      * recognized start of game markers are the move number "1",
12088      * the pattern "gnuchess .* game", the pattern
12089      * "^[#;%] [^ ]* game file", and a PGN tag block.
12090      * A game that starts with one of the latter two patterns
12091      * will also have a move number 1, possibly
12092      * following a position diagram.
12093      * 5-4-02: Let's try being more lenient and allowing a game to
12094      * start with an unnumbered move.  Does that break anything?
12095      */
12096     cm = lastLoadGameStart = EndOfFile;
12097     while (gn > 0) {
12098         yyboardindex = forwardMostMove;
12099         cm = (ChessMove) Myylex();
12100         switch (cm) {
12101           case EndOfFile:
12102             if (cmailMsgLoaded) {
12103                 nCmailGames = CMAIL_MAX_GAMES - gn;
12104             } else {
12105                 Reset(TRUE, TRUE);
12106                 DisplayError(_("Game not found in file"), 0);
12107             }
12108             return FALSE;
12109
12110           case GNUChessGame:
12111           case XBoardGame:
12112             gn--;
12113             lastLoadGameStart = cm;
12114             break;
12115
12116           case MoveNumberOne:
12117             switch (lastLoadGameStart) {
12118               case GNUChessGame:
12119               case XBoardGame:
12120               case PGNTag:
12121                 break;
12122               case MoveNumberOne:
12123               case EndOfFile:
12124                 gn--;           /* count this game */
12125                 lastLoadGameStart = cm;
12126                 break;
12127               default:
12128                 /* impossible */
12129                 break;
12130             }
12131             break;
12132
12133           case PGNTag:
12134             switch (lastLoadGameStart) {
12135               case GNUChessGame:
12136               case PGNTag:
12137               case MoveNumberOne:
12138               case EndOfFile:
12139                 gn--;           /* count this game */
12140                 lastLoadGameStart = cm;
12141                 break;
12142               case XBoardGame:
12143                 lastLoadGameStart = cm; /* game counted already */
12144                 break;
12145               default:
12146                 /* impossible */
12147                 break;
12148             }
12149             if (gn > 0) {
12150                 do {
12151                     yyboardindex = forwardMostMove;
12152                     cm = (ChessMove) Myylex();
12153                 } while (cm == PGNTag || cm == Comment);
12154             }
12155             break;
12156
12157           case WhiteWins:
12158           case BlackWins:
12159           case GameIsDrawn:
12160             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12161                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12162                     != CMAIL_OLD_RESULT) {
12163                     nCmailResults ++ ;
12164                     cmailResult[  CMAIL_MAX_GAMES
12165                                 - gn - 1] = CMAIL_OLD_RESULT;
12166                 }
12167             }
12168             break;
12169
12170           case NormalMove:
12171             /* Only a NormalMove can be at the start of a game
12172              * without a position diagram. */
12173             if (lastLoadGameStart == EndOfFile ) {
12174               gn--;
12175               lastLoadGameStart = MoveNumberOne;
12176             }
12177             break;
12178
12179           default:
12180             break;
12181         }
12182     }
12183
12184     if (appData.debugMode)
12185       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12186
12187     if (cm == XBoardGame) {
12188         /* Skip any header junk before position diagram and/or move 1 */
12189         for (;;) {
12190             yyboardindex = forwardMostMove;
12191             cm = (ChessMove) Myylex();
12192
12193             if (cm == EndOfFile ||
12194                 cm == GNUChessGame || cm == XBoardGame) {
12195                 /* Empty game; pretend end-of-file and handle later */
12196                 cm = EndOfFile;
12197                 break;
12198             }
12199
12200             if (cm == MoveNumberOne || cm == PositionDiagram ||
12201                 cm == PGNTag || cm == Comment)
12202               break;
12203         }
12204     } else if (cm == GNUChessGame) {
12205         if (gameInfo.event != NULL) {
12206             free(gameInfo.event);
12207         }
12208         gameInfo.event = StrSave(yy_text);
12209     }
12210
12211     startedFromSetupPosition = FALSE;
12212     while (cm == PGNTag) {
12213         if (appData.debugMode)
12214           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12215         err = ParsePGNTag(yy_text, &gameInfo);
12216         if (!err) numPGNTags++;
12217
12218         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12219         if(gameInfo.variant != oldVariant) {
12220             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12221             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12222             InitPosition(TRUE);
12223             oldVariant = gameInfo.variant;
12224             if (appData.debugMode)
12225               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12226         }
12227
12228
12229         if (gameInfo.fen != NULL) {
12230           Board initial_position;
12231           startedFromSetupPosition = TRUE;
12232           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12233             Reset(TRUE, TRUE);
12234             DisplayError(_("Bad FEN position in file"), 0);
12235             return FALSE;
12236           }
12237           CopyBoard(boards[0], initial_position);
12238           if (blackPlaysFirst) {
12239             currentMove = forwardMostMove = backwardMostMove = 1;
12240             CopyBoard(boards[1], initial_position);
12241             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12242             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12243             timeRemaining[0][1] = whiteTimeRemaining;
12244             timeRemaining[1][1] = blackTimeRemaining;
12245             if (commentList[0] != NULL) {
12246               commentList[1] = commentList[0];
12247               commentList[0] = NULL;
12248             }
12249           } else {
12250             currentMove = forwardMostMove = backwardMostMove = 0;
12251           }
12252           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12253           {   int i;
12254               initialRulePlies = FENrulePlies;
12255               for( i=0; i< nrCastlingRights; i++ )
12256                   initialRights[i] = initial_position[CASTLING][i];
12257           }
12258           yyboardindex = forwardMostMove;
12259           free(gameInfo.fen);
12260           gameInfo.fen = NULL;
12261         }
12262
12263         yyboardindex = forwardMostMove;
12264         cm = (ChessMove) Myylex();
12265
12266         /* Handle comments interspersed among the tags */
12267         while (cm == Comment) {
12268             char *p;
12269             if (appData.debugMode)
12270               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12271             p = yy_text;
12272             AppendComment(currentMove, p, FALSE);
12273             yyboardindex = forwardMostMove;
12274             cm = (ChessMove) Myylex();
12275         }
12276     }
12277
12278     /* don't rely on existence of Event tag since if game was
12279      * pasted from clipboard the Event tag may not exist
12280      */
12281     if (numPGNTags > 0){
12282         char *tags;
12283         if (gameInfo.variant == VariantNormal) {
12284           VariantClass v = StringToVariant(gameInfo.event);
12285           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12286           if(v < VariantShogi) gameInfo.variant = v;
12287         }
12288         if (!matchMode) {
12289           if( appData.autoDisplayTags ) {
12290             tags = PGNTags(&gameInfo);
12291             TagsPopUp(tags, CmailMsg());
12292             free(tags);
12293           }
12294         }
12295     } else {
12296         /* Make something up, but don't display it now */
12297         SetGameInfo();
12298         TagsPopDown();
12299     }
12300
12301     if (cm == PositionDiagram) {
12302         int i, j;
12303         char *p;
12304         Board initial_position;
12305
12306         if (appData.debugMode)
12307           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12308
12309         if (!startedFromSetupPosition) {
12310             p = yy_text;
12311             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12312               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12313                 switch (*p) {
12314                   case '{':
12315                   case '[':
12316                   case '-':
12317                   case ' ':
12318                   case '\t':
12319                   case '\n':
12320                   case '\r':
12321                     break;
12322                   default:
12323                     initial_position[i][j++] = CharToPiece(*p);
12324                     break;
12325                 }
12326             while (*p == ' ' || *p == '\t' ||
12327                    *p == '\n' || *p == '\r') p++;
12328
12329             if (strncmp(p, "black", strlen("black"))==0)
12330               blackPlaysFirst = TRUE;
12331             else
12332               blackPlaysFirst = FALSE;
12333             startedFromSetupPosition = TRUE;
12334
12335             CopyBoard(boards[0], initial_position);
12336             if (blackPlaysFirst) {
12337                 currentMove = forwardMostMove = backwardMostMove = 1;
12338                 CopyBoard(boards[1], initial_position);
12339                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12340                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12341                 timeRemaining[0][1] = whiteTimeRemaining;
12342                 timeRemaining[1][1] = blackTimeRemaining;
12343                 if (commentList[0] != NULL) {
12344                     commentList[1] = commentList[0];
12345                     commentList[0] = NULL;
12346                 }
12347             } else {
12348                 currentMove = forwardMostMove = backwardMostMove = 0;
12349             }
12350         }
12351         yyboardindex = forwardMostMove;
12352         cm = (ChessMove) Myylex();
12353     }
12354
12355   if(!creatingBook) {
12356     if (first.pr == NoProc) {
12357         StartChessProgram(&first);
12358     }
12359     InitChessProgram(&first, FALSE);
12360     SendToProgram("force\n", &first);
12361     if (startedFromSetupPosition) {
12362         SendBoard(&first, forwardMostMove);
12363     if (appData.debugMode) {
12364         fprintf(debugFP, "Load Game\n");
12365     }
12366         DisplayBothClocks();
12367     }
12368   }
12369
12370     /* [HGM] server: flag to write setup moves in broadcast file as one */
12371     loadFlag = appData.suppressLoadMoves;
12372
12373     while (cm == Comment) {
12374         char *p;
12375         if (appData.debugMode)
12376           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12377         p = yy_text;
12378         AppendComment(currentMove, p, FALSE);
12379         yyboardindex = forwardMostMove;
12380         cm = (ChessMove) Myylex();
12381     }
12382
12383     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12384         cm == WhiteWins || cm == BlackWins ||
12385         cm == GameIsDrawn || cm == GameUnfinished) {
12386         DisplayMessage("", _("No moves in game"));
12387         if (cmailMsgLoaded) {
12388             if (appData.debugMode)
12389               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12390             ClearHighlights();
12391             flipView = FALSE;
12392         }
12393         DrawPosition(FALSE, boards[currentMove]);
12394         DisplayBothClocks();
12395         gameMode = EditGame;
12396         ModeHighlight();
12397         gameFileFP = NULL;
12398         cmailOldMove = 0;
12399         return TRUE;
12400     }
12401
12402     // [HGM] PV info: routine tests if comment empty
12403     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12404         DisplayComment(currentMove - 1, commentList[currentMove]);
12405     }
12406     if (!matchMode && appData.timeDelay != 0)
12407       DrawPosition(FALSE, boards[currentMove]);
12408
12409     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12410       programStats.ok_to_send = 1;
12411     }
12412
12413     /* if the first token after the PGN tags is a move
12414      * and not move number 1, retrieve it from the parser
12415      */
12416     if (cm != MoveNumberOne)
12417         LoadGameOneMove(cm);
12418
12419     /* load the remaining moves from the file */
12420     while (LoadGameOneMove(EndOfFile)) {
12421       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12422       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12423     }
12424
12425     /* rewind to the start of the game */
12426     currentMove = backwardMostMove;
12427
12428     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12429
12430     if (oldGameMode == AnalyzeFile ||
12431         oldGameMode == AnalyzeMode) {
12432       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12433       AnalyzeFileEvent();
12434     }
12435
12436     if(creatingBook) return TRUE;
12437     if (!matchMode && pos > 0) {
12438         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12439     } else
12440     if (matchMode || appData.timeDelay == 0) {
12441       ToEndEvent();
12442     } else if (appData.timeDelay > 0) {
12443       AutoPlayGameLoop();
12444     }
12445
12446     if (appData.debugMode)
12447         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12448
12449     loadFlag = 0; /* [HGM] true game starts */
12450     return TRUE;
12451 }
12452
12453 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12454 int
12455 ReloadPosition (int offset)
12456 {
12457     int positionNumber = lastLoadPositionNumber + offset;
12458     if (lastLoadPositionFP == NULL) {
12459         DisplayError(_("No position has been loaded yet"), 0);
12460         return FALSE;
12461     }
12462     if (positionNumber <= 0) {
12463         DisplayError(_("Can't back up any further"), 0);
12464         return FALSE;
12465     }
12466     return LoadPosition(lastLoadPositionFP, positionNumber,
12467                         lastLoadPositionTitle);
12468 }
12469
12470 /* Load the nth position from the given file */
12471 int
12472 LoadPositionFromFile (char *filename, int n, char *title)
12473 {
12474     FILE *f;
12475     char buf[MSG_SIZ];
12476
12477     if (strcmp(filename, "-") == 0) {
12478         return LoadPosition(stdin, n, "stdin");
12479     } else {
12480         f = fopen(filename, "rb");
12481         if (f == NULL) {
12482             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12483             DisplayError(buf, errno);
12484             return FALSE;
12485         } else {
12486             return LoadPosition(f, n, title);
12487         }
12488     }
12489 }
12490
12491 /* Load the nth position from the given open file, and close it */
12492 int
12493 LoadPosition (FILE *f, int positionNumber, char *title)
12494 {
12495     char *p, line[MSG_SIZ];
12496     Board initial_position;
12497     int i, j, fenMode, pn;
12498
12499     if (gameMode == Training )
12500         SetTrainingModeOff();
12501
12502     if (gameMode != BeginningOfGame) {
12503         Reset(FALSE, TRUE);
12504     }
12505     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12506         fclose(lastLoadPositionFP);
12507     }
12508     if (positionNumber == 0) positionNumber = 1;
12509     lastLoadPositionFP = f;
12510     lastLoadPositionNumber = positionNumber;
12511     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12512     if (first.pr == NoProc && !appData.noChessProgram) {
12513       StartChessProgram(&first);
12514       InitChessProgram(&first, FALSE);
12515     }
12516     pn = positionNumber;
12517     if (positionNumber < 0) {
12518         /* Negative position number means to seek to that byte offset */
12519         if (fseek(f, -positionNumber, 0) == -1) {
12520             DisplayError(_("Can't seek on position file"), 0);
12521             return FALSE;
12522         };
12523         pn = 1;
12524     } else {
12525         if (fseek(f, 0, 0) == -1) {
12526             if (f == lastLoadPositionFP ?
12527                 positionNumber == lastLoadPositionNumber + 1 :
12528                 positionNumber == 1) {
12529                 pn = 1;
12530             } else {
12531                 DisplayError(_("Can't seek on position file"), 0);
12532                 return FALSE;
12533             }
12534         }
12535     }
12536     /* See if this file is FEN or old-style xboard */
12537     if (fgets(line, MSG_SIZ, f) == NULL) {
12538         DisplayError(_("Position not found in file"), 0);
12539         return FALSE;
12540     }
12541     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12542     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12543
12544     if (pn >= 2) {
12545         if (fenMode || line[0] == '#') pn--;
12546         while (pn > 0) {
12547             /* skip positions before number pn */
12548             if (fgets(line, MSG_SIZ, f) == NULL) {
12549                 Reset(TRUE, TRUE);
12550                 DisplayError(_("Position not found in file"), 0);
12551                 return FALSE;
12552             }
12553             if (fenMode || line[0] == '#') pn--;
12554         }
12555     }
12556
12557     if (fenMode) {
12558         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12559             DisplayError(_("Bad FEN position in file"), 0);
12560             return FALSE;
12561         }
12562     } else {
12563         (void) fgets(line, MSG_SIZ, f);
12564         (void) fgets(line, MSG_SIZ, f);
12565
12566         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12567             (void) fgets(line, MSG_SIZ, f);
12568             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12569                 if (*p == ' ')
12570                   continue;
12571                 initial_position[i][j++] = CharToPiece(*p);
12572             }
12573         }
12574
12575         blackPlaysFirst = FALSE;
12576         if (!feof(f)) {
12577             (void) fgets(line, MSG_SIZ, f);
12578             if (strncmp(line, "black", strlen("black"))==0)
12579               blackPlaysFirst = TRUE;
12580         }
12581     }
12582     startedFromSetupPosition = TRUE;
12583
12584     CopyBoard(boards[0], initial_position);
12585     if (blackPlaysFirst) {
12586         currentMove = forwardMostMove = backwardMostMove = 1;
12587         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12588         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12589         CopyBoard(boards[1], initial_position);
12590         DisplayMessage("", _("Black to play"));
12591     } else {
12592         currentMove = forwardMostMove = backwardMostMove = 0;
12593         DisplayMessage("", _("White to play"));
12594     }
12595     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12596     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12597         SendToProgram("force\n", &first);
12598         SendBoard(&first, forwardMostMove);
12599     }
12600     if (appData.debugMode) {
12601 int i, j;
12602   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12603   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12604         fprintf(debugFP, "Load Position\n");
12605     }
12606
12607     if (positionNumber > 1) {
12608       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12609         DisplayTitle(line);
12610     } else {
12611         DisplayTitle(title);
12612     }
12613     gameMode = EditGame;
12614     ModeHighlight();
12615     ResetClocks();
12616     timeRemaining[0][1] = whiteTimeRemaining;
12617     timeRemaining[1][1] = blackTimeRemaining;
12618     DrawPosition(FALSE, boards[currentMove]);
12619
12620     return TRUE;
12621 }
12622
12623
12624 void
12625 CopyPlayerNameIntoFileName (char **dest, char *src)
12626 {
12627     while (*src != NULLCHAR && *src != ',') {
12628         if (*src == ' ') {
12629             *(*dest)++ = '_';
12630             src++;
12631         } else {
12632             *(*dest)++ = *src++;
12633         }
12634     }
12635 }
12636
12637 char *
12638 DefaultFileName (char *ext)
12639 {
12640     static char def[MSG_SIZ];
12641     char *p;
12642
12643     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12644         p = def;
12645         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12646         *p++ = '-';
12647         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12648         *p++ = '.';
12649         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12650     } else {
12651         def[0] = NULLCHAR;
12652     }
12653     return def;
12654 }
12655
12656 /* Save the current game to the given file */
12657 int
12658 SaveGameToFile (char *filename, int append)
12659 {
12660     FILE *f;
12661     char buf[MSG_SIZ];
12662     int result, i, t,tot=0;
12663
12664     if (strcmp(filename, "-") == 0) {
12665         return SaveGame(stdout, 0, NULL);
12666     } else {
12667         for(i=0; i<10; i++) { // upto 10 tries
12668              f = fopen(filename, append ? "a" : "w");
12669              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12670              if(f || errno != 13) break;
12671              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12672              tot += t;
12673         }
12674         if (f == NULL) {
12675             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12676             DisplayError(buf, errno);
12677             return FALSE;
12678         } else {
12679             safeStrCpy(buf, lastMsg, MSG_SIZ);
12680             DisplayMessage(_("Waiting for access to save file"), "");
12681             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12682             DisplayMessage(_("Saving game"), "");
12683             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12684             result = SaveGame(f, 0, NULL);
12685             DisplayMessage(buf, "");
12686             return result;
12687         }
12688     }
12689 }
12690
12691 char *
12692 SavePart (char *str)
12693 {
12694     static char buf[MSG_SIZ];
12695     char *p;
12696
12697     p = strchr(str, ' ');
12698     if (p == NULL) return str;
12699     strncpy(buf, str, p - str);
12700     buf[p - str] = NULLCHAR;
12701     return buf;
12702 }
12703
12704 #define PGN_MAX_LINE 75
12705
12706 #define PGN_SIDE_WHITE  0
12707 #define PGN_SIDE_BLACK  1
12708
12709 static int
12710 FindFirstMoveOutOfBook (int side)
12711 {
12712     int result = -1;
12713
12714     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12715         int index = backwardMostMove;
12716         int has_book_hit = 0;
12717
12718         if( (index % 2) != side ) {
12719             index++;
12720         }
12721
12722         while( index < forwardMostMove ) {
12723             /* Check to see if engine is in book */
12724             int depth = pvInfoList[index].depth;
12725             int score = pvInfoList[index].score;
12726             int in_book = 0;
12727
12728             if( depth <= 2 ) {
12729                 in_book = 1;
12730             }
12731             else if( score == 0 && depth == 63 ) {
12732                 in_book = 1; /* Zappa */
12733             }
12734             else if( score == 2 && depth == 99 ) {
12735                 in_book = 1; /* Abrok */
12736             }
12737
12738             has_book_hit += in_book;
12739
12740             if( ! in_book ) {
12741                 result = index;
12742
12743                 break;
12744             }
12745
12746             index += 2;
12747         }
12748     }
12749
12750     return result;
12751 }
12752
12753 void
12754 GetOutOfBookInfo (char * buf)
12755 {
12756     int oob[2];
12757     int i;
12758     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12759
12760     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12761     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12762
12763     *buf = '\0';
12764
12765     if( oob[0] >= 0 || oob[1] >= 0 ) {
12766         for( i=0; i<2; i++ ) {
12767             int idx = oob[i];
12768
12769             if( idx >= 0 ) {
12770                 if( i > 0 && oob[0] >= 0 ) {
12771                     strcat( buf, "   " );
12772                 }
12773
12774                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12775                 sprintf( buf+strlen(buf), "%s%.2f",
12776                     pvInfoList[idx].score >= 0 ? "+" : "",
12777                     pvInfoList[idx].score / 100.0 );
12778             }
12779         }
12780     }
12781 }
12782
12783 /* Save game in PGN style and close the file */
12784 int
12785 SaveGamePGN (FILE *f)
12786 {
12787     int i, offset, linelen, newblock;
12788 //    char *movetext;
12789     char numtext[32];
12790     int movelen, numlen, blank;
12791     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12792
12793     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12794
12795     PrintPGNTags(f, &gameInfo);
12796
12797     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12798
12799     if (backwardMostMove > 0 || startedFromSetupPosition) {
12800         char *fen = PositionToFEN(backwardMostMove, NULL);
12801         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12802         fprintf(f, "\n{--------------\n");
12803         PrintPosition(f, backwardMostMove);
12804         fprintf(f, "--------------}\n");
12805         free(fen);
12806     }
12807     else {
12808         /* [AS] Out of book annotation */
12809         if( appData.saveOutOfBookInfo ) {
12810             char buf[64];
12811
12812             GetOutOfBookInfo( buf );
12813
12814             if( buf[0] != '\0' ) {
12815                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12816             }
12817         }
12818
12819         fprintf(f, "\n");
12820     }
12821
12822     i = backwardMostMove;
12823     linelen = 0;
12824     newblock = TRUE;
12825
12826     while (i < forwardMostMove) {
12827         /* Print comments preceding this move */
12828         if (commentList[i] != NULL) {
12829             if (linelen > 0) fprintf(f, "\n");
12830             fprintf(f, "%s", commentList[i]);
12831             linelen = 0;
12832             newblock = TRUE;
12833         }
12834
12835         /* Format move number */
12836         if ((i % 2) == 0)
12837           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12838         else
12839           if (newblock)
12840             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12841           else
12842             numtext[0] = NULLCHAR;
12843
12844         numlen = strlen(numtext);
12845         newblock = FALSE;
12846
12847         /* Print move number */
12848         blank = linelen > 0 && numlen > 0;
12849         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12850             fprintf(f, "\n");
12851             linelen = 0;
12852             blank = 0;
12853         }
12854         if (blank) {
12855             fprintf(f, " ");
12856             linelen++;
12857         }
12858         fprintf(f, "%s", numtext);
12859         linelen += numlen;
12860
12861         /* Get move */
12862         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12863         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12864
12865         /* Print move */
12866         blank = linelen > 0 && movelen > 0;
12867         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12868             fprintf(f, "\n");
12869             linelen = 0;
12870             blank = 0;
12871         }
12872         if (blank) {
12873             fprintf(f, " ");
12874             linelen++;
12875         }
12876         fprintf(f, "%s", move_buffer);
12877         linelen += movelen;
12878
12879         /* [AS] Add PV info if present */
12880         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12881             /* [HGM] add time */
12882             char buf[MSG_SIZ]; int seconds;
12883
12884             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12885
12886             if( seconds <= 0)
12887               buf[0] = 0;
12888             else
12889               if( seconds < 30 )
12890                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12891               else
12892                 {
12893                   seconds = (seconds + 4)/10; // round to full seconds
12894                   if( seconds < 60 )
12895                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12896                   else
12897                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12898                 }
12899
12900             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12901                       pvInfoList[i].score >= 0 ? "+" : "",
12902                       pvInfoList[i].score / 100.0,
12903                       pvInfoList[i].depth,
12904                       buf );
12905
12906             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12907
12908             /* Print score/depth */
12909             blank = linelen > 0 && movelen > 0;
12910             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12911                 fprintf(f, "\n");
12912                 linelen = 0;
12913                 blank = 0;
12914             }
12915             if (blank) {
12916                 fprintf(f, " ");
12917                 linelen++;
12918             }
12919             fprintf(f, "%s", move_buffer);
12920             linelen += movelen;
12921         }
12922
12923         i++;
12924     }
12925
12926     /* Start a new line */
12927     if (linelen > 0) fprintf(f, "\n");
12928
12929     /* Print comments after last move */
12930     if (commentList[i] != NULL) {
12931         fprintf(f, "%s\n", commentList[i]);
12932     }
12933
12934     /* Print result */
12935     if (gameInfo.resultDetails != NULL &&
12936         gameInfo.resultDetails[0] != NULLCHAR) {
12937         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12938                 PGNResult(gameInfo.result));
12939     } else {
12940         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12941     }
12942
12943     fclose(f);
12944     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12945     return TRUE;
12946 }
12947
12948 /* Save game in old style and close the file */
12949 int
12950 SaveGameOldStyle (FILE *f)
12951 {
12952     int i, offset;
12953     time_t tm;
12954
12955     tm = time((time_t *) NULL);
12956
12957     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12958     PrintOpponents(f);
12959
12960     if (backwardMostMove > 0 || startedFromSetupPosition) {
12961         fprintf(f, "\n[--------------\n");
12962         PrintPosition(f, backwardMostMove);
12963         fprintf(f, "--------------]\n");
12964     } else {
12965         fprintf(f, "\n");
12966     }
12967
12968     i = backwardMostMove;
12969     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12970
12971     while (i < forwardMostMove) {
12972         if (commentList[i] != NULL) {
12973             fprintf(f, "[%s]\n", commentList[i]);
12974         }
12975
12976         if ((i % 2) == 1) {
12977             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12978             i++;
12979         } else {
12980             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12981             i++;
12982             if (commentList[i] != NULL) {
12983                 fprintf(f, "\n");
12984                 continue;
12985             }
12986             if (i >= forwardMostMove) {
12987                 fprintf(f, "\n");
12988                 break;
12989             }
12990             fprintf(f, "%s\n", parseList[i]);
12991             i++;
12992         }
12993     }
12994
12995     if (commentList[i] != NULL) {
12996         fprintf(f, "[%s]\n", commentList[i]);
12997     }
12998
12999     /* This isn't really the old style, but it's close enough */
13000     if (gameInfo.resultDetails != NULL &&
13001         gameInfo.resultDetails[0] != NULLCHAR) {
13002         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13003                 gameInfo.resultDetails);
13004     } else {
13005         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13006     }
13007
13008     fclose(f);
13009     return TRUE;
13010 }
13011
13012 /* Save the current game to open file f and close the file */
13013 int
13014 SaveGame (FILE *f, int dummy, char *dummy2)
13015 {
13016     if (gameMode == EditPosition) EditPositionDone(TRUE);
13017     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13018     if (appData.oldSaveStyle)
13019       return SaveGameOldStyle(f);
13020     else
13021       return SaveGamePGN(f);
13022 }
13023
13024 /* Save the current position to the given file */
13025 int
13026 SavePositionToFile (char *filename)
13027 {
13028     FILE *f;
13029     char buf[MSG_SIZ];
13030
13031     if (strcmp(filename, "-") == 0) {
13032         return SavePosition(stdout, 0, NULL);
13033     } else {
13034         f = fopen(filename, "a");
13035         if (f == NULL) {
13036             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13037             DisplayError(buf, errno);
13038             return FALSE;
13039         } else {
13040             safeStrCpy(buf, lastMsg, MSG_SIZ);
13041             DisplayMessage(_("Waiting for access to save file"), "");
13042             flock(fileno(f), LOCK_EX); // [HGM] lock
13043             DisplayMessage(_("Saving position"), "");
13044             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13045             SavePosition(f, 0, NULL);
13046             DisplayMessage(buf, "");
13047             return TRUE;
13048         }
13049     }
13050 }
13051
13052 /* Save the current position to the given open file and close the file */
13053 int
13054 SavePosition (FILE *f, int dummy, char *dummy2)
13055 {
13056     time_t tm;
13057     char *fen;
13058
13059     if (gameMode == EditPosition) EditPositionDone(TRUE);
13060     if (appData.oldSaveStyle) {
13061         tm = time((time_t *) NULL);
13062
13063         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13064         PrintOpponents(f);
13065         fprintf(f, "[--------------\n");
13066         PrintPosition(f, currentMove);
13067         fprintf(f, "--------------]\n");
13068     } else {
13069         fen = PositionToFEN(currentMove, NULL);
13070         fprintf(f, "%s\n", fen);
13071         free(fen);
13072     }
13073     fclose(f);
13074     return TRUE;
13075 }
13076
13077 void
13078 ReloadCmailMsgEvent (int unregister)
13079 {
13080 #if !WIN32
13081     static char *inFilename = NULL;
13082     static char *outFilename;
13083     int i;
13084     struct stat inbuf, outbuf;
13085     int status;
13086
13087     /* Any registered moves are unregistered if unregister is set, */
13088     /* i.e. invoked by the signal handler */
13089     if (unregister) {
13090         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13091             cmailMoveRegistered[i] = FALSE;
13092             if (cmailCommentList[i] != NULL) {
13093                 free(cmailCommentList[i]);
13094                 cmailCommentList[i] = NULL;
13095             }
13096         }
13097         nCmailMovesRegistered = 0;
13098     }
13099
13100     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13101         cmailResult[i] = CMAIL_NOT_RESULT;
13102     }
13103     nCmailResults = 0;
13104
13105     if (inFilename == NULL) {
13106         /* Because the filenames are static they only get malloced once  */
13107         /* and they never get freed                                      */
13108         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13109         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13110
13111         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13112         sprintf(outFilename, "%s.out", appData.cmailGameName);
13113     }
13114
13115     status = stat(outFilename, &outbuf);
13116     if (status < 0) {
13117         cmailMailedMove = FALSE;
13118     } else {
13119         status = stat(inFilename, &inbuf);
13120         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13121     }
13122
13123     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13124        counts the games, notes how each one terminated, etc.
13125
13126        It would be nice to remove this kludge and instead gather all
13127        the information while building the game list.  (And to keep it
13128        in the game list nodes instead of having a bunch of fixed-size
13129        parallel arrays.)  Note this will require getting each game's
13130        termination from the PGN tags, as the game list builder does
13131        not process the game moves.  --mann
13132        */
13133     cmailMsgLoaded = TRUE;
13134     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13135
13136     /* Load first game in the file or popup game menu */
13137     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13138
13139 #endif /* !WIN32 */
13140     return;
13141 }
13142
13143 int
13144 RegisterMove ()
13145 {
13146     FILE *f;
13147     char string[MSG_SIZ];
13148
13149     if (   cmailMailedMove
13150         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13151         return TRUE;            /* Allow free viewing  */
13152     }
13153
13154     /* Unregister move to ensure that we don't leave RegisterMove        */
13155     /* with the move registered when the conditions for registering no   */
13156     /* longer hold                                                       */
13157     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13158         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13159         nCmailMovesRegistered --;
13160
13161         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13162           {
13163               free(cmailCommentList[lastLoadGameNumber - 1]);
13164               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13165           }
13166     }
13167
13168     if (cmailOldMove == -1) {
13169         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13170         return FALSE;
13171     }
13172
13173     if (currentMove > cmailOldMove + 1) {
13174         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13175         return FALSE;
13176     }
13177
13178     if (currentMove < cmailOldMove) {
13179         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13180         return FALSE;
13181     }
13182
13183     if (forwardMostMove > currentMove) {
13184         /* Silently truncate extra moves */
13185         TruncateGame();
13186     }
13187
13188     if (   (currentMove == cmailOldMove + 1)
13189         || (   (currentMove == cmailOldMove)
13190             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13191                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13192         if (gameInfo.result != GameUnfinished) {
13193             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13194         }
13195
13196         if (commentList[currentMove] != NULL) {
13197             cmailCommentList[lastLoadGameNumber - 1]
13198               = StrSave(commentList[currentMove]);
13199         }
13200         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13201
13202         if (appData.debugMode)
13203           fprintf(debugFP, "Saving %s for game %d\n",
13204                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13205
13206         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13207
13208         f = fopen(string, "w");
13209         if (appData.oldSaveStyle) {
13210             SaveGameOldStyle(f); /* also closes the file */
13211
13212             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13213             f = fopen(string, "w");
13214             SavePosition(f, 0, NULL); /* also closes the file */
13215         } else {
13216             fprintf(f, "{--------------\n");
13217             PrintPosition(f, currentMove);
13218             fprintf(f, "--------------}\n\n");
13219
13220             SaveGame(f, 0, NULL); /* also closes the file*/
13221         }
13222
13223         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13224         nCmailMovesRegistered ++;
13225     } else if (nCmailGames == 1) {
13226         DisplayError(_("You have not made a move yet"), 0);
13227         return FALSE;
13228     }
13229
13230     return TRUE;
13231 }
13232
13233 void
13234 MailMoveEvent ()
13235 {
13236 #if !WIN32
13237     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13238     FILE *commandOutput;
13239     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13240     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13241     int nBuffers;
13242     int i;
13243     int archived;
13244     char *arcDir;
13245
13246     if (! cmailMsgLoaded) {
13247         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13248         return;
13249     }
13250
13251     if (nCmailGames == nCmailResults) {
13252         DisplayError(_("No unfinished games"), 0);
13253         return;
13254     }
13255
13256 #if CMAIL_PROHIBIT_REMAIL
13257     if (cmailMailedMove) {
13258       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);
13259         DisplayError(msg, 0);
13260         return;
13261     }
13262 #endif
13263
13264     if (! (cmailMailedMove || RegisterMove())) return;
13265
13266     if (   cmailMailedMove
13267         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13268       snprintf(string, MSG_SIZ, partCommandString,
13269                appData.debugMode ? " -v" : "", appData.cmailGameName);
13270         commandOutput = popen(string, "r");
13271
13272         if (commandOutput == NULL) {
13273             DisplayError(_("Failed to invoke cmail"), 0);
13274         } else {
13275             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13276                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13277             }
13278             if (nBuffers > 1) {
13279                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13280                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13281                 nBytes = MSG_SIZ - 1;
13282             } else {
13283                 (void) memcpy(msg, buffer, nBytes);
13284             }
13285             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13286
13287             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13288                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13289
13290                 archived = TRUE;
13291                 for (i = 0; i < nCmailGames; i ++) {
13292                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13293                         archived = FALSE;
13294                     }
13295                 }
13296                 if (   archived
13297                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13298                         != NULL)) {
13299                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13300                            arcDir,
13301                            appData.cmailGameName,
13302                            gameInfo.date);
13303                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13304                     cmailMsgLoaded = FALSE;
13305                 }
13306             }
13307
13308             DisplayInformation(msg);
13309             pclose(commandOutput);
13310         }
13311     } else {
13312         if ((*cmailMsg) != '\0') {
13313             DisplayInformation(cmailMsg);
13314         }
13315     }
13316
13317     return;
13318 #endif /* !WIN32 */
13319 }
13320
13321 char *
13322 CmailMsg ()
13323 {
13324 #if WIN32
13325     return NULL;
13326 #else
13327     int  prependComma = 0;
13328     char number[5];
13329     char string[MSG_SIZ];       /* Space for game-list */
13330     int  i;
13331
13332     if (!cmailMsgLoaded) return "";
13333
13334     if (cmailMailedMove) {
13335       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13336     } else {
13337         /* Create a list of games left */
13338       snprintf(string, MSG_SIZ, "[");
13339         for (i = 0; i < nCmailGames; i ++) {
13340             if (! (   cmailMoveRegistered[i]
13341                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13342                 if (prependComma) {
13343                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13344                 } else {
13345                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13346                     prependComma = 1;
13347                 }
13348
13349                 strcat(string, number);
13350             }
13351         }
13352         strcat(string, "]");
13353
13354         if (nCmailMovesRegistered + nCmailResults == 0) {
13355             switch (nCmailGames) {
13356               case 1:
13357                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13358                 break;
13359
13360               case 2:
13361                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13362                 break;
13363
13364               default:
13365                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13366                          nCmailGames);
13367                 break;
13368             }
13369         } else {
13370             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13371               case 1:
13372                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13373                          string);
13374                 break;
13375
13376               case 0:
13377                 if (nCmailResults == nCmailGames) {
13378                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13379                 } else {
13380                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13381                 }
13382                 break;
13383
13384               default:
13385                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13386                          string);
13387             }
13388         }
13389     }
13390     return cmailMsg;
13391 #endif /* WIN32 */
13392 }
13393
13394 void
13395 ResetGameEvent ()
13396 {
13397     if (gameMode == Training)
13398       SetTrainingModeOff();
13399
13400     Reset(TRUE, TRUE);
13401     cmailMsgLoaded = FALSE;
13402     if (appData.icsActive) {
13403       SendToICS(ics_prefix);
13404       SendToICS("refresh\n");
13405     }
13406 }
13407
13408 void
13409 ExitEvent (int status)
13410 {
13411     exiting++;
13412     if (exiting > 2) {
13413       /* Give up on clean exit */
13414       exit(status);
13415     }
13416     if (exiting > 1) {
13417       /* Keep trying for clean exit */
13418       return;
13419     }
13420
13421     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13422
13423     if (telnetISR != NULL) {
13424       RemoveInputSource(telnetISR);
13425     }
13426     if (icsPR != NoProc) {
13427       DestroyChildProcess(icsPR, TRUE);
13428     }
13429
13430     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13431     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13432
13433     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13434     /* make sure this other one finishes before killing it!                  */
13435     if(endingGame) { int count = 0;
13436         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13437         while(endingGame && count++ < 10) DoSleep(1);
13438         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13439     }
13440
13441     /* Kill off chess programs */
13442     if (first.pr != NoProc) {
13443         ExitAnalyzeMode();
13444
13445         DoSleep( appData.delayBeforeQuit );
13446         SendToProgram("quit\n", &first);
13447         DoSleep( appData.delayAfterQuit );
13448         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13449     }
13450     if (second.pr != NoProc) {
13451         DoSleep( appData.delayBeforeQuit );
13452         SendToProgram("quit\n", &second);
13453         DoSleep( appData.delayAfterQuit );
13454         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13455     }
13456     if (first.isr != NULL) {
13457         RemoveInputSource(first.isr);
13458     }
13459     if (second.isr != NULL) {
13460         RemoveInputSource(second.isr);
13461     }
13462
13463     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13464     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13465
13466     ShutDownFrontEnd();
13467     exit(status);
13468 }
13469
13470 void
13471 PauseEngine (ChessProgramState *cps)
13472 {
13473     SendToProgram("pause\n", cps);
13474     cps->pause = 2;
13475 }
13476
13477 void
13478 UnPauseEngine (ChessProgramState *cps)
13479 {
13480     SendToProgram("resume\n", cps);
13481     cps->pause = 1;
13482 }
13483
13484 void
13485 PauseEvent ()
13486 {
13487     if (appData.debugMode)
13488         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13489     if (pausing) {
13490         pausing = FALSE;
13491         ModeHighlight();
13492         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13493             StartClocks();
13494             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13495                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13496                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13497             }
13498             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13499             HandleMachineMove(stashedInputMove, stalledEngine);
13500             stalledEngine = NULL;
13501             return;
13502         }
13503         if (gameMode == MachinePlaysWhite ||
13504             gameMode == TwoMachinesPlay   ||
13505             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13506             if(first.pause)  UnPauseEngine(&first);
13507             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13508             if(second.pause) UnPauseEngine(&second);
13509             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13510             StartClocks();
13511         } else {
13512             DisplayBothClocks();
13513         }
13514         if (gameMode == PlayFromGameFile) {
13515             if (appData.timeDelay >= 0)
13516                 AutoPlayGameLoop();
13517         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13518             Reset(FALSE, TRUE);
13519             SendToICS(ics_prefix);
13520             SendToICS("refresh\n");
13521         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13522             ForwardInner(forwardMostMove);
13523         }
13524         pauseExamInvalid = FALSE;
13525     } else {
13526         switch (gameMode) {
13527           default:
13528             return;
13529           case IcsExamining:
13530             pauseExamForwardMostMove = forwardMostMove;
13531             pauseExamInvalid = FALSE;
13532             /* fall through */
13533           case IcsObserving:
13534           case IcsPlayingWhite:
13535           case IcsPlayingBlack:
13536             pausing = TRUE;
13537             ModeHighlight();
13538             return;
13539           case PlayFromGameFile:
13540             (void) StopLoadGameTimer();
13541             pausing = TRUE;
13542             ModeHighlight();
13543             break;
13544           case BeginningOfGame:
13545             if (appData.icsActive) return;
13546             /* else fall through */
13547           case MachinePlaysWhite:
13548           case MachinePlaysBlack:
13549           case TwoMachinesPlay:
13550             if (forwardMostMove == 0)
13551               return;           /* don't pause if no one has moved */
13552             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13553                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13554                 if(onMove->pause) {           // thinking engine can be paused
13555                     PauseEngine(onMove);      // do it
13556                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13557                         PauseEngine(onMove->other);
13558                     else
13559                         SendToProgram("easy\n", onMove->other);
13560                     StopClocks();
13561                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13562             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13563                 if(first.pause) {
13564                     PauseEngine(&first);
13565                     StopClocks();
13566                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13567             } else { // human on move, pause pondering by either method
13568                 if(first.pause)
13569                     PauseEngine(&first);
13570                 else if(appData.ponderNextMove)
13571                     SendToProgram("easy\n", &first);
13572                 StopClocks();
13573             }
13574             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13575           case AnalyzeMode:
13576             pausing = TRUE;
13577             ModeHighlight();
13578             break;
13579         }
13580     }
13581 }
13582
13583 void
13584 EditCommentEvent ()
13585 {
13586     char title[MSG_SIZ];
13587
13588     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13589       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13590     } else {
13591       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13592                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13593                parseList[currentMove - 1]);
13594     }
13595
13596     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13597 }
13598
13599
13600 void
13601 EditTagsEvent ()
13602 {
13603     char *tags = PGNTags(&gameInfo);
13604     bookUp = FALSE;
13605     EditTagsPopUp(tags, NULL);
13606     free(tags);
13607 }
13608
13609 void
13610 ToggleSecond ()
13611 {
13612   if(second.analyzing) {
13613     SendToProgram("exit\n", &second);
13614     second.analyzing = FALSE;
13615   } else {
13616     if (second.pr == NoProc) StartChessProgram(&second);
13617     InitChessProgram(&second, FALSE);
13618     FeedMovesToProgram(&second, currentMove);
13619
13620     SendToProgram("analyze\n", &second);
13621     second.analyzing = TRUE;
13622   }
13623 }
13624
13625 /* Toggle ShowThinking */
13626 void
13627 ToggleShowThinking()
13628 {
13629   appData.showThinking = !appData.showThinking;
13630   ShowThinkingEvent();
13631 }
13632
13633 int
13634 AnalyzeModeEvent ()
13635 {
13636     char buf[MSG_SIZ];
13637
13638     if (!first.analysisSupport) {
13639       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13640       DisplayError(buf, 0);
13641       return 0;
13642     }
13643     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13644     if (appData.icsActive) {
13645         if (gameMode != IcsObserving) {
13646           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13647             DisplayError(buf, 0);
13648             /* secure check */
13649             if (appData.icsEngineAnalyze) {
13650                 if (appData.debugMode)
13651                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13652                 ExitAnalyzeMode();
13653                 ModeHighlight();
13654             }
13655             return 0;
13656         }
13657         /* if enable, user wants to disable icsEngineAnalyze */
13658         if (appData.icsEngineAnalyze) {
13659                 ExitAnalyzeMode();
13660                 ModeHighlight();
13661                 return 0;
13662         }
13663         appData.icsEngineAnalyze = TRUE;
13664         if (appData.debugMode)
13665             fprintf(debugFP, "ICS engine analyze starting... \n");
13666     }
13667
13668     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13669     if (appData.noChessProgram || gameMode == AnalyzeMode)
13670       return 0;
13671
13672     if (gameMode != AnalyzeFile) {
13673         if (!appData.icsEngineAnalyze) {
13674                EditGameEvent();
13675                if (gameMode != EditGame) return 0;
13676         }
13677         if (!appData.showThinking) ToggleShowThinking();
13678         ResurrectChessProgram();
13679         SendToProgram("analyze\n", &first);
13680         first.analyzing = TRUE;
13681         /*first.maybeThinking = TRUE;*/
13682         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13683         EngineOutputPopUp();
13684     }
13685     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13686     pausing = FALSE;
13687     ModeHighlight();
13688     SetGameInfo();
13689
13690     StartAnalysisClock();
13691     GetTimeMark(&lastNodeCountTime);
13692     lastNodeCount = 0;
13693     return 1;
13694 }
13695
13696 void
13697 AnalyzeFileEvent ()
13698 {
13699     if (appData.noChessProgram || gameMode == AnalyzeFile)
13700       return;
13701
13702     if (!first.analysisSupport) {
13703       char buf[MSG_SIZ];
13704       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13705       DisplayError(buf, 0);
13706       return;
13707     }
13708
13709     if (gameMode != AnalyzeMode) {
13710         keepInfo = 1; // mere annotating should not alter PGN tags
13711         EditGameEvent();
13712         keepInfo = 0;
13713         if (gameMode != EditGame) return;
13714         if (!appData.showThinking) ToggleShowThinking();
13715         ResurrectChessProgram();
13716         SendToProgram("analyze\n", &first);
13717         first.analyzing = TRUE;
13718         /*first.maybeThinking = TRUE;*/
13719         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13720         EngineOutputPopUp();
13721     }
13722     gameMode = AnalyzeFile;
13723     pausing = FALSE;
13724     ModeHighlight();
13725
13726     StartAnalysisClock();
13727     GetTimeMark(&lastNodeCountTime);
13728     lastNodeCount = 0;
13729     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13730     AnalysisPeriodicEvent(1);
13731 }
13732
13733 void
13734 MachineWhiteEvent ()
13735 {
13736     char buf[MSG_SIZ];
13737     char *bookHit = NULL;
13738
13739     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13740       return;
13741
13742
13743     if (gameMode == PlayFromGameFile ||
13744         gameMode == TwoMachinesPlay  ||
13745         gameMode == Training         ||
13746         gameMode == AnalyzeMode      ||
13747         gameMode == EndOfGame)
13748         EditGameEvent();
13749
13750     if (gameMode == EditPosition)
13751         EditPositionDone(TRUE);
13752
13753     if (!WhiteOnMove(currentMove)) {
13754         DisplayError(_("It is not White's turn"), 0);
13755         return;
13756     }
13757
13758     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13759       ExitAnalyzeMode();
13760
13761     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13762         gameMode == AnalyzeFile)
13763         TruncateGame();
13764
13765     ResurrectChessProgram();    /* in case it isn't running */
13766     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13767         gameMode = MachinePlaysWhite;
13768         ResetClocks();
13769     } else
13770     gameMode = MachinePlaysWhite;
13771     pausing = FALSE;
13772     ModeHighlight();
13773     SetGameInfo();
13774     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13775     DisplayTitle(buf);
13776     if (first.sendName) {
13777       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13778       SendToProgram(buf, &first);
13779     }
13780     if (first.sendTime) {
13781       if (first.useColors) {
13782         SendToProgram("black\n", &first); /*gnu kludge*/
13783       }
13784       SendTimeRemaining(&first, TRUE);
13785     }
13786     if (first.useColors) {
13787       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13788     }
13789     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13790     SetMachineThinkingEnables();
13791     first.maybeThinking = TRUE;
13792     StartClocks();
13793     firstMove = FALSE;
13794
13795     if (appData.autoFlipView && !flipView) {
13796       flipView = !flipView;
13797       DrawPosition(FALSE, NULL);
13798       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13799     }
13800
13801     if(bookHit) { // [HGM] book: simulate book reply
13802         static char bookMove[MSG_SIZ]; // a bit generous?
13803
13804         programStats.nodes = programStats.depth = programStats.time =
13805         programStats.score = programStats.got_only_move = 0;
13806         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13807
13808         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13809         strcat(bookMove, bookHit);
13810         HandleMachineMove(bookMove, &first);
13811     }
13812 }
13813
13814 void
13815 MachineBlackEvent ()
13816 {
13817   char buf[MSG_SIZ];
13818   char *bookHit = NULL;
13819
13820     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13821         return;
13822
13823
13824     if (gameMode == PlayFromGameFile ||
13825         gameMode == TwoMachinesPlay  ||
13826         gameMode == Training         ||
13827         gameMode == AnalyzeMode      ||
13828         gameMode == EndOfGame)
13829         EditGameEvent();
13830
13831     if (gameMode == EditPosition)
13832         EditPositionDone(TRUE);
13833
13834     if (WhiteOnMove(currentMove)) {
13835         DisplayError(_("It is not Black's turn"), 0);
13836         return;
13837     }
13838
13839     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13840       ExitAnalyzeMode();
13841
13842     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13843         gameMode == AnalyzeFile)
13844         TruncateGame();
13845
13846     ResurrectChessProgram();    /* in case it isn't running */
13847     gameMode = MachinePlaysBlack;
13848     pausing = FALSE;
13849     ModeHighlight();
13850     SetGameInfo();
13851     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13852     DisplayTitle(buf);
13853     if (first.sendName) {
13854       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13855       SendToProgram(buf, &first);
13856     }
13857     if (first.sendTime) {
13858       if (first.useColors) {
13859         SendToProgram("white\n", &first); /*gnu kludge*/
13860       }
13861       SendTimeRemaining(&first, FALSE);
13862     }
13863     if (first.useColors) {
13864       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13865     }
13866     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13867     SetMachineThinkingEnables();
13868     first.maybeThinking = TRUE;
13869     StartClocks();
13870
13871     if (appData.autoFlipView && flipView) {
13872       flipView = !flipView;
13873       DrawPosition(FALSE, NULL);
13874       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13875     }
13876     if(bookHit) { // [HGM] book: simulate book reply
13877         static char bookMove[MSG_SIZ]; // a bit generous?
13878
13879         programStats.nodes = programStats.depth = programStats.time =
13880         programStats.score = programStats.got_only_move = 0;
13881         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13882
13883         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13884         strcat(bookMove, bookHit);
13885         HandleMachineMove(bookMove, &first);
13886     }
13887 }
13888
13889
13890 void
13891 DisplayTwoMachinesTitle ()
13892 {
13893     char buf[MSG_SIZ];
13894     if (appData.matchGames > 0) {
13895         if(appData.tourneyFile[0]) {
13896           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13897                    gameInfo.white, _("vs."), gameInfo.black,
13898                    nextGame+1, appData.matchGames+1,
13899                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13900         } else
13901         if (first.twoMachinesColor[0] == 'w') {
13902           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13903                    gameInfo.white, _("vs."),  gameInfo.black,
13904                    first.matchWins, second.matchWins,
13905                    matchGame - 1 - (first.matchWins + second.matchWins));
13906         } else {
13907           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13908                    gameInfo.white, _("vs."), gameInfo.black,
13909                    second.matchWins, first.matchWins,
13910                    matchGame - 1 - (first.matchWins + second.matchWins));
13911         }
13912     } else {
13913       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13914     }
13915     DisplayTitle(buf);
13916 }
13917
13918 void
13919 SettingsMenuIfReady ()
13920 {
13921   if (second.lastPing != second.lastPong) {
13922     DisplayMessage("", _("Waiting for second chess program"));
13923     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13924     return;
13925   }
13926   ThawUI();
13927   DisplayMessage("", "");
13928   SettingsPopUp(&second);
13929 }
13930
13931 int
13932 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13933 {
13934     char buf[MSG_SIZ];
13935     if (cps->pr == NoProc) {
13936         StartChessProgram(cps);
13937         if (cps->protocolVersion == 1) {
13938           retry();
13939           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13940         } else {
13941           /* kludge: allow timeout for initial "feature" command */
13942           if(retry != TwoMachinesEventIfReady) FreezeUI();
13943           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13944           DisplayMessage("", buf);
13945           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13946         }
13947         return 1;
13948     }
13949     return 0;
13950 }
13951
13952 void
13953 TwoMachinesEvent P((void))
13954 {
13955     int i;
13956     char buf[MSG_SIZ];
13957     ChessProgramState *onmove;
13958     char *bookHit = NULL;
13959     static int stalling = 0;
13960     TimeMark now;
13961     long wait;
13962
13963     if (appData.noChessProgram) return;
13964
13965     switch (gameMode) {
13966       case TwoMachinesPlay:
13967         return;
13968       case MachinePlaysWhite:
13969       case MachinePlaysBlack:
13970         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13971             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13972             return;
13973         }
13974         /* fall through */
13975       case BeginningOfGame:
13976       case PlayFromGameFile:
13977       case EndOfGame:
13978         EditGameEvent();
13979         if (gameMode != EditGame) return;
13980         break;
13981       case EditPosition:
13982         EditPositionDone(TRUE);
13983         break;
13984       case AnalyzeMode:
13985       case AnalyzeFile:
13986         ExitAnalyzeMode();
13987         break;
13988       case EditGame:
13989       default:
13990         break;
13991     }
13992
13993 //    forwardMostMove = currentMove;
13994     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13995     startingEngine = TRUE;
13996
13997     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13998
13999     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14000     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14001       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14002       return;
14003     }
14004     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14005
14006     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14007         startingEngine = FALSE;
14008         DisplayError("second engine does not play this", 0);
14009         return;
14010     }
14011
14012     if(!stalling) {
14013       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14014       SendToProgram("force\n", &second);
14015       stalling = 1;
14016       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14017       return;
14018     }
14019     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14020     if(appData.matchPause>10000 || appData.matchPause<10)
14021                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14022     wait = SubtractTimeMarks(&now, &pauseStart);
14023     if(wait < appData.matchPause) {
14024         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14025         return;
14026     }
14027     // we are now committed to starting the game
14028     stalling = 0;
14029     DisplayMessage("", "");
14030     if (startedFromSetupPosition) {
14031         SendBoard(&second, backwardMostMove);
14032     if (appData.debugMode) {
14033         fprintf(debugFP, "Two Machines\n");
14034     }
14035     }
14036     for (i = backwardMostMove; i < forwardMostMove; i++) {
14037         SendMoveToProgram(i, &second);
14038     }
14039
14040     gameMode = TwoMachinesPlay;
14041     pausing = startingEngine = FALSE;
14042     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14043     SetGameInfo();
14044     DisplayTwoMachinesTitle();
14045     firstMove = TRUE;
14046     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14047         onmove = &first;
14048     } else {
14049         onmove = &second;
14050     }
14051     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14052     SendToProgram(first.computerString, &first);
14053     if (first.sendName) {
14054       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14055       SendToProgram(buf, &first);
14056     }
14057     SendToProgram(second.computerString, &second);
14058     if (second.sendName) {
14059       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14060       SendToProgram(buf, &second);
14061     }
14062
14063     ResetClocks();
14064     if (!first.sendTime || !second.sendTime) {
14065         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14066         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14067     }
14068     if (onmove->sendTime) {
14069       if (onmove->useColors) {
14070         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14071       }
14072       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14073     }
14074     if (onmove->useColors) {
14075       SendToProgram(onmove->twoMachinesColor, onmove);
14076     }
14077     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14078 //    SendToProgram("go\n", onmove);
14079     onmove->maybeThinking = TRUE;
14080     SetMachineThinkingEnables();
14081
14082     StartClocks();
14083
14084     if(bookHit) { // [HGM] book: simulate book reply
14085         static char bookMove[MSG_SIZ]; // a bit generous?
14086
14087         programStats.nodes = programStats.depth = programStats.time =
14088         programStats.score = programStats.got_only_move = 0;
14089         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14090
14091         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14092         strcat(bookMove, bookHit);
14093         savedMessage = bookMove; // args for deferred call
14094         savedState = onmove;
14095         ScheduleDelayedEvent(DeferredBookMove, 1);
14096     }
14097 }
14098
14099 void
14100 TrainingEvent ()
14101 {
14102     if (gameMode == Training) {
14103       SetTrainingModeOff();
14104       gameMode = PlayFromGameFile;
14105       DisplayMessage("", _("Training mode off"));
14106     } else {
14107       gameMode = Training;
14108       animateTraining = appData.animate;
14109
14110       /* make sure we are not already at the end of the game */
14111       if (currentMove < forwardMostMove) {
14112         SetTrainingModeOn();
14113         DisplayMessage("", _("Training mode on"));
14114       } else {
14115         gameMode = PlayFromGameFile;
14116         DisplayError(_("Already at end of game"), 0);
14117       }
14118     }
14119     ModeHighlight();
14120 }
14121
14122 void
14123 IcsClientEvent ()
14124 {
14125     if (!appData.icsActive) return;
14126     switch (gameMode) {
14127       case IcsPlayingWhite:
14128       case IcsPlayingBlack:
14129       case IcsObserving:
14130       case IcsIdle:
14131       case BeginningOfGame:
14132       case IcsExamining:
14133         return;
14134
14135       case EditGame:
14136         break;
14137
14138       case EditPosition:
14139         EditPositionDone(TRUE);
14140         break;
14141
14142       case AnalyzeMode:
14143       case AnalyzeFile:
14144         ExitAnalyzeMode();
14145         break;
14146
14147       default:
14148         EditGameEvent();
14149         break;
14150     }
14151
14152     gameMode = IcsIdle;
14153     ModeHighlight();
14154     return;
14155 }
14156
14157 void
14158 EditGameEvent ()
14159 {
14160     int i;
14161
14162     switch (gameMode) {
14163       case Training:
14164         SetTrainingModeOff();
14165         break;
14166       case MachinePlaysWhite:
14167       case MachinePlaysBlack:
14168       case BeginningOfGame:
14169         SendToProgram("force\n", &first);
14170         SetUserThinkingEnables();
14171         break;
14172       case PlayFromGameFile:
14173         (void) StopLoadGameTimer();
14174         if (gameFileFP != NULL) {
14175             gameFileFP = NULL;
14176         }
14177         break;
14178       case EditPosition:
14179         EditPositionDone(TRUE);
14180         break;
14181       case AnalyzeMode:
14182       case AnalyzeFile:
14183         ExitAnalyzeMode();
14184         SendToProgram("force\n", &first);
14185         break;
14186       case TwoMachinesPlay:
14187         GameEnds(EndOfFile, NULL, GE_PLAYER);
14188         ResurrectChessProgram();
14189         SetUserThinkingEnables();
14190         break;
14191       case EndOfGame:
14192         ResurrectChessProgram();
14193         break;
14194       case IcsPlayingBlack:
14195       case IcsPlayingWhite:
14196         DisplayError(_("Warning: You are still playing a game"), 0);
14197         break;
14198       case IcsObserving:
14199         DisplayError(_("Warning: You are still observing a game"), 0);
14200         break;
14201       case IcsExamining:
14202         DisplayError(_("Warning: You are still examining a game"), 0);
14203         break;
14204       case IcsIdle:
14205         break;
14206       case EditGame:
14207       default:
14208         return;
14209     }
14210
14211     pausing = FALSE;
14212     StopClocks();
14213     first.offeredDraw = second.offeredDraw = 0;
14214
14215     if (gameMode == PlayFromGameFile) {
14216         whiteTimeRemaining = timeRemaining[0][currentMove];
14217         blackTimeRemaining = timeRemaining[1][currentMove];
14218         DisplayTitle("");
14219     }
14220
14221     if (gameMode == MachinePlaysWhite ||
14222         gameMode == MachinePlaysBlack ||
14223         gameMode == TwoMachinesPlay ||
14224         gameMode == EndOfGame) {
14225         i = forwardMostMove;
14226         while (i > currentMove) {
14227             SendToProgram("undo\n", &first);
14228             i--;
14229         }
14230         if(!adjustedClock) {
14231         whiteTimeRemaining = timeRemaining[0][currentMove];
14232         blackTimeRemaining = timeRemaining[1][currentMove];
14233         DisplayBothClocks();
14234         }
14235         if (whiteFlag || blackFlag) {
14236             whiteFlag = blackFlag = 0;
14237         }
14238         DisplayTitle("");
14239     }
14240
14241     gameMode = EditGame;
14242     ModeHighlight();
14243     SetGameInfo();
14244 }
14245
14246
14247 void
14248 EditPositionEvent ()
14249 {
14250     if (gameMode == EditPosition) {
14251         EditGameEvent();
14252         return;
14253     }
14254
14255     EditGameEvent();
14256     if (gameMode != EditGame) return;
14257
14258     gameMode = EditPosition;
14259     ModeHighlight();
14260     SetGameInfo();
14261     if (currentMove > 0)
14262       CopyBoard(boards[0], boards[currentMove]);
14263
14264     blackPlaysFirst = !WhiteOnMove(currentMove);
14265     ResetClocks();
14266     currentMove = forwardMostMove = backwardMostMove = 0;
14267     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14268     DisplayMove(-1);
14269     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14270 }
14271
14272 void
14273 ExitAnalyzeMode ()
14274 {
14275     /* [DM] icsEngineAnalyze - possible call from other functions */
14276     if (appData.icsEngineAnalyze) {
14277         appData.icsEngineAnalyze = FALSE;
14278
14279         DisplayMessage("",_("Close ICS engine analyze..."));
14280     }
14281     if (first.analysisSupport && first.analyzing) {
14282       SendToBoth("exit\n");
14283       first.analyzing = second.analyzing = FALSE;
14284     }
14285     thinkOutput[0] = NULLCHAR;
14286 }
14287
14288 void
14289 EditPositionDone (Boolean fakeRights)
14290 {
14291     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14292
14293     startedFromSetupPosition = TRUE;
14294     InitChessProgram(&first, FALSE);
14295     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14296       boards[0][EP_STATUS] = EP_NONE;
14297       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14298       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14299         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14300         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14301       } else boards[0][CASTLING][2] = NoRights;
14302       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14303         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14304         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14305       } else boards[0][CASTLING][5] = NoRights;
14306       if(gameInfo.variant == VariantSChess) {
14307         int i;
14308         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14309           boards[0][VIRGIN][i] = 0;
14310           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14311           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14312         }
14313       }
14314     }
14315     SendToProgram("force\n", &first);
14316     if (blackPlaysFirst) {
14317         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14318         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14319         currentMove = forwardMostMove = backwardMostMove = 1;
14320         CopyBoard(boards[1], boards[0]);
14321     } else {
14322         currentMove = forwardMostMove = backwardMostMove = 0;
14323     }
14324     SendBoard(&first, forwardMostMove);
14325     if (appData.debugMode) {
14326         fprintf(debugFP, "EditPosDone\n");
14327     }
14328     DisplayTitle("");
14329     DisplayMessage("", "");
14330     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14331     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14332     gameMode = EditGame;
14333     ModeHighlight();
14334     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14335     ClearHighlights(); /* [AS] */
14336 }
14337
14338 /* Pause for `ms' milliseconds */
14339 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14340 void
14341 TimeDelay (long ms)
14342 {
14343     TimeMark m1, m2;
14344
14345     GetTimeMark(&m1);
14346     do {
14347         GetTimeMark(&m2);
14348     } while (SubtractTimeMarks(&m2, &m1) < ms);
14349 }
14350
14351 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14352 void
14353 SendMultiLineToICS (char *buf)
14354 {
14355     char temp[MSG_SIZ+1], *p;
14356     int len;
14357
14358     len = strlen(buf);
14359     if (len > MSG_SIZ)
14360       len = MSG_SIZ;
14361
14362     strncpy(temp, buf, len);
14363     temp[len] = 0;
14364
14365     p = temp;
14366     while (*p) {
14367         if (*p == '\n' || *p == '\r')
14368           *p = ' ';
14369         ++p;
14370     }
14371
14372     strcat(temp, "\n");
14373     SendToICS(temp);
14374     SendToPlayer(temp, strlen(temp));
14375 }
14376
14377 void
14378 SetWhiteToPlayEvent ()
14379 {
14380     if (gameMode == EditPosition) {
14381         blackPlaysFirst = FALSE;
14382         DisplayBothClocks();    /* works because currentMove is 0 */
14383     } else if (gameMode == IcsExamining) {
14384         SendToICS(ics_prefix);
14385         SendToICS("tomove white\n");
14386     }
14387 }
14388
14389 void
14390 SetBlackToPlayEvent ()
14391 {
14392     if (gameMode == EditPosition) {
14393         blackPlaysFirst = TRUE;
14394         currentMove = 1;        /* kludge */
14395         DisplayBothClocks();
14396         currentMove = 0;
14397     } else if (gameMode == IcsExamining) {
14398         SendToICS(ics_prefix);
14399         SendToICS("tomove black\n");
14400     }
14401 }
14402
14403 void
14404 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14405 {
14406     char buf[MSG_SIZ];
14407     ChessSquare piece = boards[0][y][x];
14408
14409     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14410
14411     switch (selection) {
14412       case ClearBoard:
14413         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14414             SendToICS(ics_prefix);
14415             SendToICS("bsetup clear\n");
14416         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14417             SendToICS(ics_prefix);
14418             SendToICS("clearboard\n");
14419         } else {
14420             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14421                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14422                 for (y = 0; y < BOARD_HEIGHT; y++) {
14423                     if (gameMode == IcsExamining) {
14424                         if (boards[currentMove][y][x] != EmptySquare) {
14425                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14426                                     AAA + x, ONE + y);
14427                             SendToICS(buf);
14428                         }
14429                     } else {
14430                         boards[0][y][x] = p;
14431                     }
14432                 }
14433             }
14434         }
14435         if (gameMode == EditPosition) {
14436             DrawPosition(FALSE, boards[0]);
14437         }
14438         break;
14439
14440       case WhitePlay:
14441         SetWhiteToPlayEvent();
14442         break;
14443
14444       case BlackPlay:
14445         SetBlackToPlayEvent();
14446         break;
14447
14448       case EmptySquare:
14449         if (gameMode == IcsExamining) {
14450             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14451             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14452             SendToICS(buf);
14453         } else {
14454             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14455                 if(x == BOARD_LEFT-2) {
14456                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14457                     boards[0][y][1] = 0;
14458                 } else
14459                 if(x == BOARD_RGHT+1) {
14460                     if(y >= gameInfo.holdingsSize) break;
14461                     boards[0][y][BOARD_WIDTH-2] = 0;
14462                 } else break;
14463             }
14464             boards[0][y][x] = EmptySquare;
14465             DrawPosition(FALSE, boards[0]);
14466         }
14467         break;
14468
14469       case PromotePiece:
14470         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14471            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14472             selection = (ChessSquare) (PROMOTED piece);
14473         } else if(piece == EmptySquare) selection = WhiteSilver;
14474         else selection = (ChessSquare)((int)piece - 1);
14475         goto defaultlabel;
14476
14477       case DemotePiece:
14478         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14479            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14480             selection = (ChessSquare) (DEMOTED piece);
14481         } else if(piece == EmptySquare) selection = BlackSilver;
14482         else selection = (ChessSquare)((int)piece + 1);
14483         goto defaultlabel;
14484
14485       case WhiteQueen:
14486       case BlackQueen:
14487         if(gameInfo.variant == VariantShatranj ||
14488            gameInfo.variant == VariantXiangqi  ||
14489            gameInfo.variant == VariantCourier  ||
14490            gameInfo.variant == VariantMakruk     )
14491             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14492         goto defaultlabel;
14493
14494       case WhiteKing:
14495       case BlackKing:
14496         if(gameInfo.variant == VariantXiangqi)
14497             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14498         if(gameInfo.variant == VariantKnightmate)
14499             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14500       default:
14501         defaultlabel:
14502         if (gameMode == IcsExamining) {
14503             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14504             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14505                      PieceToChar(selection), AAA + x, ONE + y);
14506             SendToICS(buf);
14507         } else {
14508             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14509                 int n;
14510                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14511                     n = PieceToNumber(selection - BlackPawn);
14512                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14513                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14514                     boards[0][BOARD_HEIGHT-1-n][1]++;
14515                 } else
14516                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14517                     n = PieceToNumber(selection);
14518                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14519                     boards[0][n][BOARD_WIDTH-1] = selection;
14520                     boards[0][n][BOARD_WIDTH-2]++;
14521                 }
14522             } else
14523             boards[0][y][x] = selection;
14524             DrawPosition(TRUE, boards[0]);
14525             ClearHighlights();
14526             fromX = fromY = -1;
14527         }
14528         break;
14529     }
14530 }
14531
14532
14533 void
14534 DropMenuEvent (ChessSquare selection, int x, int y)
14535 {
14536     ChessMove moveType;
14537
14538     switch (gameMode) {
14539       case IcsPlayingWhite:
14540       case MachinePlaysBlack:
14541         if (!WhiteOnMove(currentMove)) {
14542             DisplayMoveError(_("It is Black's turn"));
14543             return;
14544         }
14545         moveType = WhiteDrop;
14546         break;
14547       case IcsPlayingBlack:
14548       case MachinePlaysWhite:
14549         if (WhiteOnMove(currentMove)) {
14550             DisplayMoveError(_("It is White's turn"));
14551             return;
14552         }
14553         moveType = BlackDrop;
14554         break;
14555       case EditGame:
14556         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14557         break;
14558       default:
14559         return;
14560     }
14561
14562     if (moveType == BlackDrop && selection < BlackPawn) {
14563       selection = (ChessSquare) ((int) selection
14564                                  + (int) BlackPawn - (int) WhitePawn);
14565     }
14566     if (boards[currentMove][y][x] != EmptySquare) {
14567         DisplayMoveError(_("That square is occupied"));
14568         return;
14569     }
14570
14571     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14572 }
14573
14574 void
14575 AcceptEvent ()
14576 {
14577     /* Accept a pending offer of any kind from opponent */
14578
14579     if (appData.icsActive) {
14580         SendToICS(ics_prefix);
14581         SendToICS("accept\n");
14582     } else if (cmailMsgLoaded) {
14583         if (currentMove == cmailOldMove &&
14584             commentList[cmailOldMove] != NULL &&
14585             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14586                    "Black offers a draw" : "White offers a draw")) {
14587             TruncateGame();
14588             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14589             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14590         } else {
14591             DisplayError(_("There is no pending offer on this move"), 0);
14592             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14593         }
14594     } else {
14595         /* Not used for offers from chess program */
14596     }
14597 }
14598
14599 void
14600 DeclineEvent ()
14601 {
14602     /* Decline a pending offer of any kind from opponent */
14603
14604     if (appData.icsActive) {
14605         SendToICS(ics_prefix);
14606         SendToICS("decline\n");
14607     } else if (cmailMsgLoaded) {
14608         if (currentMove == cmailOldMove &&
14609             commentList[cmailOldMove] != NULL &&
14610             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14611                    "Black offers a draw" : "White offers a draw")) {
14612 #ifdef NOTDEF
14613             AppendComment(cmailOldMove, "Draw declined", TRUE);
14614             DisplayComment(cmailOldMove - 1, "Draw declined");
14615 #endif /*NOTDEF*/
14616         } else {
14617             DisplayError(_("There is no pending offer on this move"), 0);
14618         }
14619     } else {
14620         /* Not used for offers from chess program */
14621     }
14622 }
14623
14624 void
14625 RematchEvent ()
14626 {
14627     /* Issue ICS rematch command */
14628     if (appData.icsActive) {
14629         SendToICS(ics_prefix);
14630         SendToICS("rematch\n");
14631     }
14632 }
14633
14634 void
14635 CallFlagEvent ()
14636 {
14637     /* Call your opponent's flag (claim a win on time) */
14638     if (appData.icsActive) {
14639         SendToICS(ics_prefix);
14640         SendToICS("flag\n");
14641     } else {
14642         switch (gameMode) {
14643           default:
14644             return;
14645           case MachinePlaysWhite:
14646             if (whiteFlag) {
14647                 if (blackFlag)
14648                   GameEnds(GameIsDrawn, "Both players ran out of time",
14649                            GE_PLAYER);
14650                 else
14651                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14652             } else {
14653                 DisplayError(_("Your opponent is not out of time"), 0);
14654             }
14655             break;
14656           case MachinePlaysBlack:
14657             if (blackFlag) {
14658                 if (whiteFlag)
14659                   GameEnds(GameIsDrawn, "Both players ran out of time",
14660                            GE_PLAYER);
14661                 else
14662                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14663             } else {
14664                 DisplayError(_("Your opponent is not out of time"), 0);
14665             }
14666             break;
14667         }
14668     }
14669 }
14670
14671 void
14672 ClockClick (int which)
14673 {       // [HGM] code moved to back-end from winboard.c
14674         if(which) { // black clock
14675           if (gameMode == EditPosition || gameMode == IcsExamining) {
14676             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14677             SetBlackToPlayEvent();
14678           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14679           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14680           } else if (shiftKey) {
14681             AdjustClock(which, -1);
14682           } else if (gameMode == IcsPlayingWhite ||
14683                      gameMode == MachinePlaysBlack) {
14684             CallFlagEvent();
14685           }
14686         } else { // white clock
14687           if (gameMode == EditPosition || gameMode == IcsExamining) {
14688             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14689             SetWhiteToPlayEvent();
14690           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14691           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14692           } else if (shiftKey) {
14693             AdjustClock(which, -1);
14694           } else if (gameMode == IcsPlayingBlack ||
14695                    gameMode == MachinePlaysWhite) {
14696             CallFlagEvent();
14697           }
14698         }
14699 }
14700
14701 void
14702 DrawEvent ()
14703 {
14704     /* Offer draw or accept pending draw offer from opponent */
14705
14706     if (appData.icsActive) {
14707         /* Note: tournament rules require draw offers to be
14708            made after you make your move but before you punch
14709            your clock.  Currently ICS doesn't let you do that;
14710            instead, you immediately punch your clock after making
14711            a move, but you can offer a draw at any time. */
14712
14713         SendToICS(ics_prefix);
14714         SendToICS("draw\n");
14715         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14716     } else if (cmailMsgLoaded) {
14717         if (currentMove == cmailOldMove &&
14718             commentList[cmailOldMove] != NULL &&
14719             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14720                    "Black offers a draw" : "White offers a draw")) {
14721             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14722             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14723         } else if (currentMove == cmailOldMove + 1) {
14724             char *offer = WhiteOnMove(cmailOldMove) ?
14725               "White offers a draw" : "Black offers a draw";
14726             AppendComment(currentMove, offer, TRUE);
14727             DisplayComment(currentMove - 1, offer);
14728             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14729         } else {
14730             DisplayError(_("You must make your move before offering a draw"), 0);
14731             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14732         }
14733     } else if (first.offeredDraw) {
14734         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14735     } else {
14736         if (first.sendDrawOffers) {
14737             SendToProgram("draw\n", &first);
14738             userOfferedDraw = TRUE;
14739         }
14740     }
14741 }
14742
14743 void
14744 AdjournEvent ()
14745 {
14746     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14747
14748     if (appData.icsActive) {
14749         SendToICS(ics_prefix);
14750         SendToICS("adjourn\n");
14751     } else {
14752         /* Currently GNU Chess doesn't offer or accept Adjourns */
14753     }
14754 }
14755
14756
14757 void
14758 AbortEvent ()
14759 {
14760     /* Offer Abort or accept pending Abort offer from opponent */
14761
14762     if (appData.icsActive) {
14763         SendToICS(ics_prefix);
14764         SendToICS("abort\n");
14765     } else {
14766         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14767     }
14768 }
14769
14770 void
14771 ResignEvent ()
14772 {
14773     /* Resign.  You can do this even if it's not your turn. */
14774
14775     if (appData.icsActive) {
14776         SendToICS(ics_prefix);
14777         SendToICS("resign\n");
14778     } else {
14779         switch (gameMode) {
14780           case MachinePlaysWhite:
14781             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14782             break;
14783           case MachinePlaysBlack:
14784             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14785             break;
14786           case EditGame:
14787             if (cmailMsgLoaded) {
14788                 TruncateGame();
14789                 if (WhiteOnMove(cmailOldMove)) {
14790                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14791                 } else {
14792                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14793                 }
14794                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14795             }
14796             break;
14797           default:
14798             break;
14799         }
14800     }
14801 }
14802
14803
14804 void
14805 StopObservingEvent ()
14806 {
14807     /* Stop observing current games */
14808     SendToICS(ics_prefix);
14809     SendToICS("unobserve\n");
14810 }
14811
14812 void
14813 StopExaminingEvent ()
14814 {
14815     /* Stop observing current game */
14816     SendToICS(ics_prefix);
14817     SendToICS("unexamine\n");
14818 }
14819
14820 void
14821 ForwardInner (int target)
14822 {
14823     int limit; int oldSeekGraphUp = seekGraphUp;
14824
14825     if (appData.debugMode)
14826         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14827                 target, currentMove, forwardMostMove);
14828
14829     if (gameMode == EditPosition)
14830       return;
14831
14832     seekGraphUp = FALSE;
14833     MarkTargetSquares(1);
14834
14835     if (gameMode == PlayFromGameFile && !pausing)
14836       PauseEvent();
14837
14838     if (gameMode == IcsExamining && pausing)
14839       limit = pauseExamForwardMostMove;
14840     else
14841       limit = forwardMostMove;
14842
14843     if (target > limit) target = limit;
14844
14845     if (target > 0 && moveList[target - 1][0]) {
14846         int fromX, fromY, toX, toY;
14847         toX = moveList[target - 1][2] - AAA;
14848         toY = moveList[target - 1][3] - ONE;
14849         if (moveList[target - 1][1] == '@') {
14850             if (appData.highlightLastMove) {
14851                 SetHighlights(-1, -1, toX, toY);
14852             }
14853         } else {
14854             fromX = moveList[target - 1][0] - AAA;
14855             fromY = moveList[target - 1][1] - ONE;
14856             if (target == currentMove + 1) {
14857                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14858             }
14859             if (appData.highlightLastMove) {
14860                 SetHighlights(fromX, fromY, toX, toY);
14861             }
14862         }
14863     }
14864     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14865         gameMode == Training || gameMode == PlayFromGameFile ||
14866         gameMode == AnalyzeFile) {
14867         while (currentMove < target) {
14868             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14869             SendMoveToProgram(currentMove++, &first);
14870         }
14871     } else {
14872         currentMove = target;
14873     }
14874
14875     if (gameMode == EditGame || gameMode == EndOfGame) {
14876         whiteTimeRemaining = timeRemaining[0][currentMove];
14877         blackTimeRemaining = timeRemaining[1][currentMove];
14878     }
14879     DisplayBothClocks();
14880     DisplayMove(currentMove - 1);
14881     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14882     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14883     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14884         DisplayComment(currentMove - 1, commentList[currentMove]);
14885     }
14886     ClearMap(); // [HGM] exclude: invalidate map
14887 }
14888
14889
14890 void
14891 ForwardEvent ()
14892 {
14893     if (gameMode == IcsExamining && !pausing) {
14894         SendToICS(ics_prefix);
14895         SendToICS("forward\n");
14896     } else {
14897         ForwardInner(currentMove + 1);
14898     }
14899 }
14900
14901 void
14902 ToEndEvent ()
14903 {
14904     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14905         /* to optimze, we temporarily turn off analysis mode while we feed
14906          * the remaining moves to the engine. Otherwise we get analysis output
14907          * after each move.
14908          */
14909         if (first.analysisSupport) {
14910           SendToProgram("exit\nforce\n", &first);
14911           first.analyzing = FALSE;
14912         }
14913     }
14914
14915     if (gameMode == IcsExamining && !pausing) {
14916         SendToICS(ics_prefix);
14917         SendToICS("forward 999999\n");
14918     } else {
14919         ForwardInner(forwardMostMove);
14920     }
14921
14922     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14923         /* we have fed all the moves, so reactivate analysis mode */
14924         SendToProgram("analyze\n", &first);
14925         first.analyzing = TRUE;
14926         /*first.maybeThinking = TRUE;*/
14927         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14928     }
14929 }
14930
14931 void
14932 BackwardInner (int target)
14933 {
14934     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14935
14936     if (appData.debugMode)
14937         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14938                 target, currentMove, forwardMostMove);
14939
14940     if (gameMode == EditPosition) return;
14941     seekGraphUp = FALSE;
14942     MarkTargetSquares(1);
14943     if (currentMove <= backwardMostMove) {
14944         ClearHighlights();
14945         DrawPosition(full_redraw, boards[currentMove]);
14946         return;
14947     }
14948     if (gameMode == PlayFromGameFile && !pausing)
14949       PauseEvent();
14950
14951     if (moveList[target][0]) {
14952         int fromX, fromY, toX, toY;
14953         toX = moveList[target][2] - AAA;
14954         toY = moveList[target][3] - ONE;
14955         if (moveList[target][1] == '@') {
14956             if (appData.highlightLastMove) {
14957                 SetHighlights(-1, -1, toX, toY);
14958             }
14959         } else {
14960             fromX = moveList[target][0] - AAA;
14961             fromY = moveList[target][1] - ONE;
14962             if (target == currentMove - 1) {
14963                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14964             }
14965             if (appData.highlightLastMove) {
14966                 SetHighlights(fromX, fromY, toX, toY);
14967             }
14968         }
14969     }
14970     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14971         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14972         while (currentMove > target) {
14973             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14974                 // null move cannot be undone. Reload program with move history before it.
14975                 int i;
14976                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14977                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14978                 }
14979                 SendBoard(&first, i);
14980               if(second.analyzing) SendBoard(&second, i);
14981                 for(currentMove=i; currentMove<target; currentMove++) {
14982                     SendMoveToProgram(currentMove, &first);
14983                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14984                 }
14985                 break;
14986             }
14987             SendToBoth("undo\n");
14988             currentMove--;
14989         }
14990     } else {
14991         currentMove = target;
14992     }
14993
14994     if (gameMode == EditGame || gameMode == EndOfGame) {
14995         whiteTimeRemaining = timeRemaining[0][currentMove];
14996         blackTimeRemaining = timeRemaining[1][currentMove];
14997     }
14998     DisplayBothClocks();
14999     DisplayMove(currentMove - 1);
15000     DrawPosition(full_redraw, boards[currentMove]);
15001     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15002     // [HGM] PV info: routine tests if comment empty
15003     DisplayComment(currentMove - 1, commentList[currentMove]);
15004     ClearMap(); // [HGM] exclude: invalidate map
15005 }
15006
15007 void
15008 BackwardEvent ()
15009 {
15010     if (gameMode == IcsExamining && !pausing) {
15011         SendToICS(ics_prefix);
15012         SendToICS("backward\n");
15013     } else {
15014         BackwardInner(currentMove - 1);
15015     }
15016 }
15017
15018 void
15019 ToStartEvent ()
15020 {
15021     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15022         /* to optimize, we temporarily turn off analysis mode while we undo
15023          * all the moves. Otherwise we get analysis output after each undo.
15024          */
15025         if (first.analysisSupport) {
15026           SendToProgram("exit\nforce\n", &first);
15027           first.analyzing = FALSE;
15028         }
15029     }
15030
15031     if (gameMode == IcsExamining && !pausing) {
15032         SendToICS(ics_prefix);
15033         SendToICS("backward 999999\n");
15034     } else {
15035         BackwardInner(backwardMostMove);
15036     }
15037
15038     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15039         /* we have fed all the moves, so reactivate analysis mode */
15040         SendToProgram("analyze\n", &first);
15041         first.analyzing = TRUE;
15042         /*first.maybeThinking = TRUE;*/
15043         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15044     }
15045 }
15046
15047 void
15048 ToNrEvent (int to)
15049 {
15050   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15051   if (to >= forwardMostMove) to = forwardMostMove;
15052   if (to <= backwardMostMove) to = backwardMostMove;
15053   if (to < currentMove) {
15054     BackwardInner(to);
15055   } else {
15056     ForwardInner(to);
15057   }
15058 }
15059
15060 void
15061 RevertEvent (Boolean annotate)
15062 {
15063     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15064         return;
15065     }
15066     if (gameMode != IcsExamining) {
15067         DisplayError(_("You are not examining a game"), 0);
15068         return;
15069     }
15070     if (pausing) {
15071         DisplayError(_("You can't revert while pausing"), 0);
15072         return;
15073     }
15074     SendToICS(ics_prefix);
15075     SendToICS("revert\n");
15076 }
15077
15078 void
15079 RetractMoveEvent ()
15080 {
15081     switch (gameMode) {
15082       case MachinePlaysWhite:
15083       case MachinePlaysBlack:
15084         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15085             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15086             return;
15087         }
15088         if (forwardMostMove < 2) return;
15089         currentMove = forwardMostMove = forwardMostMove - 2;
15090         whiteTimeRemaining = timeRemaining[0][currentMove];
15091         blackTimeRemaining = timeRemaining[1][currentMove];
15092         DisplayBothClocks();
15093         DisplayMove(currentMove - 1);
15094         ClearHighlights();/*!! could figure this out*/
15095         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15096         SendToProgram("remove\n", &first);
15097         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15098         break;
15099
15100       case BeginningOfGame:
15101       default:
15102         break;
15103
15104       case IcsPlayingWhite:
15105       case IcsPlayingBlack:
15106         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15107             SendToICS(ics_prefix);
15108             SendToICS("takeback 2\n");
15109         } else {
15110             SendToICS(ics_prefix);
15111             SendToICS("takeback 1\n");
15112         }
15113         break;
15114     }
15115 }
15116
15117 void
15118 MoveNowEvent ()
15119 {
15120     ChessProgramState *cps;
15121
15122     switch (gameMode) {
15123       case MachinePlaysWhite:
15124         if (!WhiteOnMove(forwardMostMove)) {
15125             DisplayError(_("It is your turn"), 0);
15126             return;
15127         }
15128         cps = &first;
15129         break;
15130       case MachinePlaysBlack:
15131         if (WhiteOnMove(forwardMostMove)) {
15132             DisplayError(_("It is your turn"), 0);
15133             return;
15134         }
15135         cps = &first;
15136         break;
15137       case TwoMachinesPlay:
15138         if (WhiteOnMove(forwardMostMove) ==
15139             (first.twoMachinesColor[0] == 'w')) {
15140             cps = &first;
15141         } else {
15142             cps = &second;
15143         }
15144         break;
15145       case BeginningOfGame:
15146       default:
15147         return;
15148     }
15149     SendToProgram("?\n", cps);
15150 }
15151
15152 void
15153 TruncateGameEvent ()
15154 {
15155     EditGameEvent();
15156     if (gameMode != EditGame) return;
15157     TruncateGame();
15158 }
15159
15160 void
15161 TruncateGame ()
15162 {
15163     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15164     if (forwardMostMove > currentMove) {
15165         if (gameInfo.resultDetails != NULL) {
15166             free(gameInfo.resultDetails);
15167             gameInfo.resultDetails = NULL;
15168             gameInfo.result = GameUnfinished;
15169         }
15170         forwardMostMove = currentMove;
15171         HistorySet(parseList, backwardMostMove, forwardMostMove,
15172                    currentMove-1);
15173     }
15174 }
15175
15176 void
15177 HintEvent ()
15178 {
15179     if (appData.noChessProgram) return;
15180     switch (gameMode) {
15181       case MachinePlaysWhite:
15182         if (WhiteOnMove(forwardMostMove)) {
15183             DisplayError(_("Wait until your turn"), 0);
15184             return;
15185         }
15186         break;
15187       case BeginningOfGame:
15188       case MachinePlaysBlack:
15189         if (!WhiteOnMove(forwardMostMove)) {
15190             DisplayError(_("Wait until your turn"), 0);
15191             return;
15192         }
15193         break;
15194       default:
15195         DisplayError(_("No hint available"), 0);
15196         return;
15197     }
15198     SendToProgram("hint\n", &first);
15199     hintRequested = TRUE;
15200 }
15201
15202 void
15203 CreateBookEvent ()
15204 {
15205     ListGame * lg = (ListGame *) gameList.head;
15206     FILE *f;
15207     int nItem;
15208     static int secondTime = FALSE;
15209
15210     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15211         DisplayError(_("Game list not loaded or empty"), 0);
15212         return;
15213     }
15214
15215     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15216         fclose(f);
15217         secondTime++;
15218         DisplayNote(_("Book file exists! Try again for overwrite."));
15219         return;
15220     }
15221
15222     creatingBook = TRUE;
15223     secondTime = FALSE;
15224
15225     /* Get list size */
15226     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15227         LoadGame(f, nItem, "", TRUE);
15228         AddGameToBook(TRUE);
15229         lg = (ListGame *) lg->node.succ;
15230     }
15231
15232     creatingBook = FALSE;
15233     FlushBook();
15234 }
15235
15236 void
15237 BookEvent ()
15238 {
15239     if (appData.noChessProgram) return;
15240     switch (gameMode) {
15241       case MachinePlaysWhite:
15242         if (WhiteOnMove(forwardMostMove)) {
15243             DisplayError(_("Wait until your turn"), 0);
15244             return;
15245         }
15246         break;
15247       case BeginningOfGame:
15248       case MachinePlaysBlack:
15249         if (!WhiteOnMove(forwardMostMove)) {
15250             DisplayError(_("Wait until your turn"), 0);
15251             return;
15252         }
15253         break;
15254       case EditPosition:
15255         EditPositionDone(TRUE);
15256         break;
15257       case TwoMachinesPlay:
15258         return;
15259       default:
15260         break;
15261     }
15262     SendToProgram("bk\n", &first);
15263     bookOutput[0] = NULLCHAR;
15264     bookRequested = TRUE;
15265 }
15266
15267 void
15268 AboutGameEvent ()
15269 {
15270     char *tags = PGNTags(&gameInfo);
15271     TagsPopUp(tags, CmailMsg());
15272     free(tags);
15273 }
15274
15275 /* end button procedures */
15276
15277 void
15278 PrintPosition (FILE *fp, int move)
15279 {
15280     int i, j;
15281
15282     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15283         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15284             char c = PieceToChar(boards[move][i][j]);
15285             fputc(c == 'x' ? '.' : c, fp);
15286             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15287         }
15288     }
15289     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15290       fprintf(fp, "white to play\n");
15291     else
15292       fprintf(fp, "black to play\n");
15293 }
15294
15295 void
15296 PrintOpponents (FILE *fp)
15297 {
15298     if (gameInfo.white != NULL) {
15299         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15300     } else {
15301         fprintf(fp, "\n");
15302     }
15303 }
15304
15305 /* Find last component of program's own name, using some heuristics */
15306 void
15307 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15308 {
15309     char *p, *q, c;
15310     int local = (strcmp(host, "localhost") == 0);
15311     while (!local && (p = strchr(prog, ';')) != NULL) {
15312         p++;
15313         while (*p == ' ') p++;
15314         prog = p;
15315     }
15316     if (*prog == '"' || *prog == '\'') {
15317         q = strchr(prog + 1, *prog);
15318     } else {
15319         q = strchr(prog, ' ');
15320     }
15321     if (q == NULL) q = prog + strlen(prog);
15322     p = q;
15323     while (p >= prog && *p != '/' && *p != '\\') p--;
15324     p++;
15325     if(p == prog && *p == '"') p++;
15326     c = *q; *q = 0;
15327     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15328     memcpy(buf, p, q - p);
15329     buf[q - p] = NULLCHAR;
15330     if (!local) {
15331         strcat(buf, "@");
15332         strcat(buf, host);
15333     }
15334 }
15335
15336 char *
15337 TimeControlTagValue ()
15338 {
15339     char buf[MSG_SIZ];
15340     if (!appData.clockMode) {
15341       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15342     } else if (movesPerSession > 0) {
15343       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15344     } else if (timeIncrement == 0) {
15345       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15346     } else {
15347       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15348     }
15349     return StrSave(buf);
15350 }
15351
15352 void
15353 SetGameInfo ()
15354 {
15355     /* This routine is used only for certain modes */
15356     VariantClass v = gameInfo.variant;
15357     ChessMove r = GameUnfinished;
15358     char *p = NULL;
15359
15360     if(keepInfo) return;
15361
15362     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15363         r = gameInfo.result;
15364         p = gameInfo.resultDetails;
15365         gameInfo.resultDetails = NULL;
15366     }
15367     ClearGameInfo(&gameInfo);
15368     gameInfo.variant = v;
15369
15370     switch (gameMode) {
15371       case MachinePlaysWhite:
15372         gameInfo.event = StrSave( appData.pgnEventHeader );
15373         gameInfo.site = StrSave(HostName());
15374         gameInfo.date = PGNDate();
15375         gameInfo.round = StrSave("-");
15376         gameInfo.white = StrSave(first.tidy);
15377         gameInfo.black = StrSave(UserName());
15378         gameInfo.timeControl = TimeControlTagValue();
15379         break;
15380
15381       case MachinePlaysBlack:
15382         gameInfo.event = StrSave( appData.pgnEventHeader );
15383         gameInfo.site = StrSave(HostName());
15384         gameInfo.date = PGNDate();
15385         gameInfo.round = StrSave("-");
15386         gameInfo.white = StrSave(UserName());
15387         gameInfo.black = StrSave(first.tidy);
15388         gameInfo.timeControl = TimeControlTagValue();
15389         break;
15390
15391       case TwoMachinesPlay:
15392         gameInfo.event = StrSave( appData.pgnEventHeader );
15393         gameInfo.site = StrSave(HostName());
15394         gameInfo.date = PGNDate();
15395         if (roundNr > 0) {
15396             char buf[MSG_SIZ];
15397             snprintf(buf, MSG_SIZ, "%d", roundNr);
15398             gameInfo.round = StrSave(buf);
15399         } else {
15400             gameInfo.round = StrSave("-");
15401         }
15402         if (first.twoMachinesColor[0] == 'w') {
15403             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15404             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15405         } else {
15406             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15407             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15408         }
15409         gameInfo.timeControl = TimeControlTagValue();
15410         break;
15411
15412       case EditGame:
15413         gameInfo.event = StrSave("Edited game");
15414         gameInfo.site = StrSave(HostName());
15415         gameInfo.date = PGNDate();
15416         gameInfo.round = StrSave("-");
15417         gameInfo.white = StrSave("-");
15418         gameInfo.black = StrSave("-");
15419         gameInfo.result = r;
15420         gameInfo.resultDetails = p;
15421         break;
15422
15423       case EditPosition:
15424         gameInfo.event = StrSave("Edited position");
15425         gameInfo.site = StrSave(HostName());
15426         gameInfo.date = PGNDate();
15427         gameInfo.round = StrSave("-");
15428         gameInfo.white = StrSave("-");
15429         gameInfo.black = StrSave("-");
15430         break;
15431
15432       case IcsPlayingWhite:
15433       case IcsPlayingBlack:
15434       case IcsObserving:
15435       case IcsExamining:
15436         break;
15437
15438       case PlayFromGameFile:
15439         gameInfo.event = StrSave("Game from non-PGN file");
15440         gameInfo.site = StrSave(HostName());
15441         gameInfo.date = PGNDate();
15442         gameInfo.round = StrSave("-");
15443         gameInfo.white = StrSave("?");
15444         gameInfo.black = StrSave("?");
15445         break;
15446
15447       default:
15448         break;
15449     }
15450 }
15451
15452 void
15453 ReplaceComment (int index, char *text)
15454 {
15455     int len;
15456     char *p;
15457     float score;
15458
15459     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15460        pvInfoList[index-1].depth == len &&
15461        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15462        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15463     while (*text == '\n') text++;
15464     len = strlen(text);
15465     while (len > 0 && text[len - 1] == '\n') len--;
15466
15467     if (commentList[index] != NULL)
15468       free(commentList[index]);
15469
15470     if (len == 0) {
15471         commentList[index] = NULL;
15472         return;
15473     }
15474   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15475       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15476       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15477     commentList[index] = (char *) malloc(len + 2);
15478     strncpy(commentList[index], text, len);
15479     commentList[index][len] = '\n';
15480     commentList[index][len + 1] = NULLCHAR;
15481   } else {
15482     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15483     char *p;
15484     commentList[index] = (char *) malloc(len + 7);
15485     safeStrCpy(commentList[index], "{\n", 3);
15486     safeStrCpy(commentList[index]+2, text, len+1);
15487     commentList[index][len+2] = NULLCHAR;
15488     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15489     strcat(commentList[index], "\n}\n");
15490   }
15491 }
15492
15493 void
15494 CrushCRs (char *text)
15495 {
15496   char *p = text;
15497   char *q = text;
15498   char ch;
15499
15500   do {
15501     ch = *p++;
15502     if (ch == '\r') continue;
15503     *q++ = ch;
15504   } while (ch != '\0');
15505 }
15506
15507 void
15508 AppendComment (int index, char *text, Boolean addBraces)
15509 /* addBraces  tells if we should add {} */
15510 {
15511     int oldlen, len;
15512     char *old;
15513
15514 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15515     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15516
15517     CrushCRs(text);
15518     while (*text == '\n') text++;
15519     len = strlen(text);
15520     while (len > 0 && text[len - 1] == '\n') len--;
15521     text[len] = NULLCHAR;
15522
15523     if (len == 0) return;
15524
15525     if (commentList[index] != NULL) {
15526       Boolean addClosingBrace = addBraces;
15527         old = commentList[index];
15528         oldlen = strlen(old);
15529         while(commentList[index][oldlen-1] ==  '\n')
15530           commentList[index][--oldlen] = NULLCHAR;
15531         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15532         safeStrCpy(commentList[index], old, oldlen + len + 6);
15533         free(old);
15534         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15535         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15536           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15537           while (*text == '\n') { text++; len--; }
15538           commentList[index][--oldlen] = NULLCHAR;
15539       }
15540         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15541         else          strcat(commentList[index], "\n");
15542         strcat(commentList[index], text);
15543         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15544         else          strcat(commentList[index], "\n");
15545     } else {
15546         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15547         if(addBraces)
15548           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15549         else commentList[index][0] = NULLCHAR;
15550         strcat(commentList[index], text);
15551         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15552         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15553     }
15554 }
15555
15556 static char *
15557 FindStr (char * text, char * sub_text)
15558 {
15559     char * result = strstr( text, sub_text );
15560
15561     if( result != NULL ) {
15562         result += strlen( sub_text );
15563     }
15564
15565     return result;
15566 }
15567
15568 /* [AS] Try to extract PV info from PGN comment */
15569 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15570 char *
15571 GetInfoFromComment (int index, char * text)
15572 {
15573     char * sep = text, *p;
15574
15575     if( text != NULL && index > 0 ) {
15576         int score = 0;
15577         int depth = 0;
15578         int time = -1, sec = 0, deci;
15579         char * s_eval = FindStr( text, "[%eval " );
15580         char * s_emt = FindStr( text, "[%emt " );
15581
15582         if( s_eval != NULL || s_emt != NULL ) {
15583             /* New style */
15584             char delim;
15585
15586             if( s_eval != NULL ) {
15587                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15588                     return text;
15589                 }
15590
15591                 if( delim != ']' ) {
15592                     return text;
15593                 }
15594             }
15595
15596             if( s_emt != NULL ) {
15597             }
15598                 return text;
15599         }
15600         else {
15601             /* We expect something like: [+|-]nnn.nn/dd */
15602             int score_lo = 0;
15603
15604             if(*text != '{') return text; // [HGM] braces: must be normal comment
15605
15606             sep = strchr( text, '/' );
15607             if( sep == NULL || sep < (text+4) ) {
15608                 return text;
15609             }
15610
15611             p = text;
15612             if(p[1] == '(') { // comment starts with PV
15613                p = strchr(p, ')'); // locate end of PV
15614                if(p == NULL || sep < p+5) return text;
15615                // at this point we have something like "{(.*) +0.23/6 ..."
15616                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15617                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15618                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15619             }
15620             time = -1; sec = -1; deci = -1;
15621             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15622                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15623                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15624                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15625                 return text;
15626             }
15627
15628             if( score_lo < 0 || score_lo >= 100 ) {
15629                 return text;
15630             }
15631
15632             if(sec >= 0) time = 600*time + 10*sec; else
15633             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15634
15635             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15636
15637             /* [HGM] PV time: now locate end of PV info */
15638             while( *++sep >= '0' && *sep <= '9'); // strip depth
15639             if(time >= 0)
15640             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15641             if(sec >= 0)
15642             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15643             if(deci >= 0)
15644             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15645             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15646         }
15647
15648         if( depth <= 0 ) {
15649             return text;
15650         }
15651
15652         if( time < 0 ) {
15653             time = -1;
15654         }
15655
15656         pvInfoList[index-1].depth = depth;
15657         pvInfoList[index-1].score = score;
15658         pvInfoList[index-1].time  = 10*time; // centi-sec
15659         if(*sep == '}') *sep = 0; else *--sep = '{';
15660         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15661     }
15662     return sep;
15663 }
15664
15665 void
15666 SendToProgram (char *message, ChessProgramState *cps)
15667 {
15668     int count, outCount, error;
15669     char buf[MSG_SIZ];
15670
15671     if (cps->pr == NoProc) return;
15672     Attention(cps);
15673
15674     if (appData.debugMode) {
15675         TimeMark now;
15676         GetTimeMark(&now);
15677         fprintf(debugFP, "%ld >%-6s: %s",
15678                 SubtractTimeMarks(&now, &programStartTime),
15679                 cps->which, message);
15680         if(serverFP)
15681             fprintf(serverFP, "%ld >%-6s: %s",
15682                 SubtractTimeMarks(&now, &programStartTime),
15683                 cps->which, message), fflush(serverFP);
15684     }
15685
15686     count = strlen(message);
15687     outCount = OutputToProcess(cps->pr, message, count, &error);
15688     if (outCount < count && !exiting
15689                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15690       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15691       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15692         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15693             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15694                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15695                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15696                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15697             } else {
15698                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15699                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15700                 gameInfo.result = res;
15701             }
15702             gameInfo.resultDetails = StrSave(buf);
15703         }
15704         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15705         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15706     }
15707 }
15708
15709 void
15710 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15711 {
15712     char *end_str;
15713     char buf[MSG_SIZ];
15714     ChessProgramState *cps = (ChessProgramState *)closure;
15715
15716     if (isr != cps->isr) return; /* Killed intentionally */
15717     if (count <= 0) {
15718         if (count == 0) {
15719             RemoveInputSource(cps->isr);
15720             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15721                     _(cps->which), cps->program);
15722             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15723             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15724                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15725                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15726                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15727                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15728                 } else {
15729                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15730                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15731                     gameInfo.result = res;
15732                 }
15733                 gameInfo.resultDetails = StrSave(buf);
15734             }
15735             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15736             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15737         } else {
15738             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15739                     _(cps->which), cps->program);
15740             RemoveInputSource(cps->isr);
15741
15742             /* [AS] Program is misbehaving badly... kill it */
15743             if( count == -2 ) {
15744                 DestroyChildProcess( cps->pr, 9 );
15745                 cps->pr = NoProc;
15746             }
15747
15748             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15749         }
15750         return;
15751     }
15752
15753     if ((end_str = strchr(message, '\r')) != NULL)
15754       *end_str = NULLCHAR;
15755     if ((end_str = strchr(message, '\n')) != NULL)
15756       *end_str = NULLCHAR;
15757
15758     if (appData.debugMode) {
15759         TimeMark now; int print = 1;
15760         char *quote = ""; char c; int i;
15761
15762         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15763                 char start = message[0];
15764                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15765                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15766                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15767                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15768                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15769                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15770                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15771                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15772                    sscanf(message, "hint: %c", &c)!=1 &&
15773                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15774                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15775                     print = (appData.engineComments >= 2);
15776                 }
15777                 message[0] = start; // restore original message
15778         }
15779         if(print) {
15780                 GetTimeMark(&now);
15781                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15782                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15783                         quote,
15784                         message);
15785                 if(serverFP)
15786                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15787                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15788                         quote,
15789                         message), fflush(serverFP);
15790         }
15791     }
15792
15793     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15794     if (appData.icsEngineAnalyze) {
15795         if (strstr(message, "whisper") != NULL ||
15796              strstr(message, "kibitz") != NULL ||
15797             strstr(message, "tellics") != NULL) return;
15798     }
15799
15800     HandleMachineMove(message, cps);
15801 }
15802
15803
15804 void
15805 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15806 {
15807     char buf[MSG_SIZ];
15808     int seconds;
15809
15810     if( timeControl_2 > 0 ) {
15811         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15812             tc = timeControl_2;
15813         }
15814     }
15815     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15816     inc /= cps->timeOdds;
15817     st  /= cps->timeOdds;
15818
15819     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15820
15821     if (st > 0) {
15822       /* Set exact time per move, normally using st command */
15823       if (cps->stKludge) {
15824         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15825         seconds = st % 60;
15826         if (seconds == 0) {
15827           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15828         } else {
15829           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15830         }
15831       } else {
15832         snprintf(buf, MSG_SIZ, "st %d\n", st);
15833       }
15834     } else {
15835       /* Set conventional or incremental time control, using level command */
15836       if (seconds == 0) {
15837         /* Note old gnuchess bug -- minutes:seconds used to not work.
15838            Fixed in later versions, but still avoid :seconds
15839            when seconds is 0. */
15840         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15841       } else {
15842         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15843                  seconds, inc/1000.);
15844       }
15845     }
15846     SendToProgram(buf, cps);
15847
15848     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15849     /* Orthogonally, limit search to given depth */
15850     if (sd > 0) {
15851       if (cps->sdKludge) {
15852         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15853       } else {
15854         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15855       }
15856       SendToProgram(buf, cps);
15857     }
15858
15859     if(cps->nps >= 0) { /* [HGM] nps */
15860         if(cps->supportsNPS == FALSE)
15861           cps->nps = -1; // don't use if engine explicitly says not supported!
15862         else {
15863           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15864           SendToProgram(buf, cps);
15865         }
15866     }
15867 }
15868
15869 ChessProgramState *
15870 WhitePlayer ()
15871 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15872 {
15873     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15874        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15875         return &second;
15876     return &first;
15877 }
15878
15879 void
15880 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15881 {
15882     char message[MSG_SIZ];
15883     long time, otime;
15884
15885     /* Note: this routine must be called when the clocks are stopped
15886        or when they have *just* been set or switched; otherwise
15887        it will be off by the time since the current tick started.
15888     */
15889     if (machineWhite) {
15890         time = whiteTimeRemaining / 10;
15891         otime = blackTimeRemaining / 10;
15892     } else {
15893         time = blackTimeRemaining / 10;
15894         otime = whiteTimeRemaining / 10;
15895     }
15896     /* [HGM] translate opponent's time by time-odds factor */
15897     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15898
15899     if (time <= 0) time = 1;
15900     if (otime <= 0) otime = 1;
15901
15902     snprintf(message, MSG_SIZ, "time %ld\n", time);
15903     SendToProgram(message, cps);
15904
15905     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15906     SendToProgram(message, cps);
15907 }
15908
15909 int
15910 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15911 {
15912   char buf[MSG_SIZ];
15913   int len = strlen(name);
15914   int val;
15915
15916   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15917     (*p) += len + 1;
15918     sscanf(*p, "%d", &val);
15919     *loc = (val != 0);
15920     while (**p && **p != ' ')
15921       (*p)++;
15922     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15923     SendToProgram(buf, cps);
15924     return TRUE;
15925   }
15926   return FALSE;
15927 }
15928
15929 int
15930 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15931 {
15932   char buf[MSG_SIZ];
15933   int len = strlen(name);
15934   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15935     (*p) += len + 1;
15936     sscanf(*p, "%d", loc);
15937     while (**p && **p != ' ') (*p)++;
15938     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15939     SendToProgram(buf, cps);
15940     return TRUE;
15941   }
15942   return FALSE;
15943 }
15944
15945 int
15946 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15947 {
15948   char buf[MSG_SIZ];
15949   int len = strlen(name);
15950   if (strncmp((*p), name, len) == 0
15951       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15952     (*p) += len + 2;
15953     sscanf(*p, "%[^\"]", loc);
15954     while (**p && **p != '\"') (*p)++;
15955     if (**p == '\"') (*p)++;
15956     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15957     SendToProgram(buf, cps);
15958     return TRUE;
15959   }
15960   return FALSE;
15961 }
15962
15963 int
15964 ParseOption (Option *opt, ChessProgramState *cps)
15965 // [HGM] options: process the string that defines an engine option, and determine
15966 // name, type, default value, and allowed value range
15967 {
15968         char *p, *q, buf[MSG_SIZ];
15969         int n, min = (-1)<<31, max = 1<<31, def;
15970
15971         if(p = strstr(opt->name, " -spin ")) {
15972             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15973             if(max < min) max = min; // enforce consistency
15974             if(def < min) def = min;
15975             if(def > max) def = max;
15976             opt->value = def;
15977             opt->min = min;
15978             opt->max = max;
15979             opt->type = Spin;
15980         } else if((p = strstr(opt->name, " -slider "))) {
15981             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15982             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15983             if(max < min) max = min; // enforce consistency
15984             if(def < min) def = min;
15985             if(def > max) def = max;
15986             opt->value = def;
15987             opt->min = min;
15988             opt->max = max;
15989             opt->type = Spin; // Slider;
15990         } else if((p = strstr(opt->name, " -string "))) {
15991             opt->textValue = p+9;
15992             opt->type = TextBox;
15993         } else if((p = strstr(opt->name, " -file "))) {
15994             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15995             opt->textValue = p+7;
15996             opt->type = FileName; // FileName;
15997         } else if((p = strstr(opt->name, " -path "))) {
15998             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15999             opt->textValue = p+7;
16000             opt->type = PathName; // PathName;
16001         } else if(p = strstr(opt->name, " -check ")) {
16002             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16003             opt->value = (def != 0);
16004             opt->type = CheckBox;
16005         } else if(p = strstr(opt->name, " -combo ")) {
16006             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16007             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16008             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16009             opt->value = n = 0;
16010             while(q = StrStr(q, " /// ")) {
16011                 n++; *q = 0;    // count choices, and null-terminate each of them
16012                 q += 5;
16013                 if(*q == '*') { // remember default, which is marked with * prefix
16014                     q++;
16015                     opt->value = n;
16016                 }
16017                 cps->comboList[cps->comboCnt++] = q;
16018             }
16019             cps->comboList[cps->comboCnt++] = NULL;
16020             opt->max = n + 1;
16021             opt->type = ComboBox;
16022         } else if(p = strstr(opt->name, " -button")) {
16023             opt->type = Button;
16024         } else if(p = strstr(opt->name, " -save")) {
16025             opt->type = SaveButton;
16026         } else return FALSE;
16027         *p = 0; // terminate option name
16028         // now look if the command-line options define a setting for this engine option.
16029         if(cps->optionSettings && cps->optionSettings[0])
16030             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16031         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16032           snprintf(buf, MSG_SIZ, "option %s", p);
16033                 if(p = strstr(buf, ",")) *p = 0;
16034                 if(q = strchr(buf, '=')) switch(opt->type) {
16035                     case ComboBox:
16036                         for(n=0; n<opt->max; n++)
16037                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16038                         break;
16039                     case TextBox:
16040                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16041                         break;
16042                     case Spin:
16043                     case CheckBox:
16044                         opt->value = atoi(q+1);
16045                     default:
16046                         break;
16047                 }
16048                 strcat(buf, "\n");
16049                 SendToProgram(buf, cps);
16050         }
16051         return TRUE;
16052 }
16053
16054 void
16055 FeatureDone (ChessProgramState *cps, int val)
16056 {
16057   DelayedEventCallback cb = GetDelayedEvent();
16058   if ((cb == InitBackEnd3 && cps == &first) ||
16059       (cb == SettingsMenuIfReady && cps == &second) ||
16060       (cb == LoadEngine) ||
16061       (cb == TwoMachinesEventIfReady)) {
16062     CancelDelayedEvent();
16063     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16064   }
16065   cps->initDone = val;
16066   if(val) cps->reload = FALSE;
16067 }
16068
16069 /* Parse feature command from engine */
16070 void
16071 ParseFeatures (char *args, ChessProgramState *cps)
16072 {
16073   char *p = args;
16074   char *q;
16075   int val;
16076   char buf[MSG_SIZ];
16077
16078   for (;;) {
16079     while (*p == ' ') p++;
16080     if (*p == NULLCHAR) return;
16081
16082     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16083     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16084     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16085     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16086     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16087     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16088     if (BoolFeature(&p, "reuse", &val, cps)) {
16089       /* Engine can disable reuse, but can't enable it if user said no */
16090       if (!val) cps->reuse = FALSE;
16091       continue;
16092     }
16093     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16094     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16095       if (gameMode == TwoMachinesPlay) {
16096         DisplayTwoMachinesTitle();
16097       } else {
16098         DisplayTitle("");
16099       }
16100       continue;
16101     }
16102     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16103     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16104     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16105     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16106     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16107     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16108     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16109     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16110     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16111     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16112     if (IntFeature(&p, "done", &val, cps)) {
16113       FeatureDone(cps, val);
16114       continue;
16115     }
16116     /* Added by Tord: */
16117     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16118     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16119     /* End of additions by Tord */
16120
16121     /* [HGM] added features: */
16122     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16123     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16124     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16125     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16126     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16127     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16128     if (StringFeature(&p, "option", buf, cps)) {
16129         if(cps->reload) continue; // we are reloading because of xreuse
16130         FREE(cps->option[cps->nrOptions].name);
16131         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16132         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16133         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16134           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16135             SendToProgram(buf, cps);
16136             continue;
16137         }
16138         if(cps->nrOptions >= MAX_OPTIONS) {
16139             cps->nrOptions--;
16140             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16141             DisplayError(buf, 0);
16142         }
16143         continue;
16144     }
16145     /* End of additions by HGM */
16146
16147     /* unknown feature: complain and skip */
16148     q = p;
16149     while (*q && *q != '=') q++;
16150     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16151     SendToProgram(buf, cps);
16152     p = q;
16153     if (*p == '=') {
16154       p++;
16155       if (*p == '\"') {
16156         p++;
16157         while (*p && *p != '\"') p++;
16158         if (*p == '\"') p++;
16159       } else {
16160         while (*p && *p != ' ') p++;
16161       }
16162     }
16163   }
16164
16165 }
16166
16167 void
16168 PeriodicUpdatesEvent (int newState)
16169 {
16170     if (newState == appData.periodicUpdates)
16171       return;
16172
16173     appData.periodicUpdates=newState;
16174
16175     /* Display type changes, so update it now */
16176 //    DisplayAnalysis();
16177
16178     /* Get the ball rolling again... */
16179     if (newState) {
16180         AnalysisPeriodicEvent(1);
16181         StartAnalysisClock();
16182     }
16183 }
16184
16185 void
16186 PonderNextMoveEvent (int newState)
16187 {
16188     if (newState == appData.ponderNextMove) return;
16189     if (gameMode == EditPosition) EditPositionDone(TRUE);
16190     if (newState) {
16191         SendToProgram("hard\n", &first);
16192         if (gameMode == TwoMachinesPlay) {
16193             SendToProgram("hard\n", &second);
16194         }
16195     } else {
16196         SendToProgram("easy\n", &first);
16197         thinkOutput[0] = NULLCHAR;
16198         if (gameMode == TwoMachinesPlay) {
16199             SendToProgram("easy\n", &second);
16200         }
16201     }
16202     appData.ponderNextMove = newState;
16203 }
16204
16205 void
16206 NewSettingEvent (int option, int *feature, char *command, int value)
16207 {
16208     char buf[MSG_SIZ];
16209
16210     if (gameMode == EditPosition) EditPositionDone(TRUE);
16211     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16212     if(feature == NULL || *feature) SendToProgram(buf, &first);
16213     if (gameMode == TwoMachinesPlay) {
16214         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16215     }
16216 }
16217
16218 void
16219 ShowThinkingEvent ()
16220 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16221 {
16222     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16223     int newState = appData.showThinking
16224         // [HGM] thinking: other features now need thinking output as well
16225         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16226
16227     if (oldState == newState) return;
16228     oldState = newState;
16229     if (gameMode == EditPosition) EditPositionDone(TRUE);
16230     if (oldState) {
16231         SendToProgram("post\n", &first);
16232         if (gameMode == TwoMachinesPlay) {
16233             SendToProgram("post\n", &second);
16234         }
16235     } else {
16236         SendToProgram("nopost\n", &first);
16237         thinkOutput[0] = NULLCHAR;
16238         if (gameMode == TwoMachinesPlay) {
16239             SendToProgram("nopost\n", &second);
16240         }
16241     }
16242 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16243 }
16244
16245 void
16246 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16247 {
16248   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16249   if (pr == NoProc) return;
16250   AskQuestion(title, question, replyPrefix, pr);
16251 }
16252
16253 void
16254 TypeInEvent (char firstChar)
16255 {
16256     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16257         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16258         gameMode == AnalyzeMode || gameMode == EditGame ||
16259         gameMode == EditPosition || gameMode == IcsExamining ||
16260         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16261         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16262                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16263                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16264         gameMode == Training) PopUpMoveDialog(firstChar);
16265 }
16266
16267 void
16268 TypeInDoneEvent (char *move)
16269 {
16270         Board board;
16271         int n, fromX, fromY, toX, toY;
16272         char promoChar;
16273         ChessMove moveType;
16274
16275         // [HGM] FENedit
16276         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16277                 EditPositionPasteFEN(move);
16278                 return;
16279         }
16280         // [HGM] movenum: allow move number to be typed in any mode
16281         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16282           ToNrEvent(2*n-1);
16283           return;
16284         }
16285         // undocumented kludge: allow command-line option to be typed in!
16286         // (potentially fatal, and does not implement the effect of the option.)
16287         // should only be used for options that are values on which future decisions will be made,
16288         // and definitely not on options that would be used during initialization.
16289         if(strstr(move, "!!! -") == move) {
16290             ParseArgsFromString(move+4);
16291             return;
16292         }
16293
16294       if (gameMode != EditGame && currentMove != forwardMostMove &&
16295         gameMode != Training) {
16296         DisplayMoveError(_("Displayed move is not current"));
16297       } else {
16298         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16299           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16300         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16301         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16302           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16303           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16304         } else {
16305           DisplayMoveError(_("Could not parse move"));
16306         }
16307       }
16308 }
16309
16310 void
16311 DisplayMove (int moveNumber)
16312 {
16313     char message[MSG_SIZ];
16314     char res[MSG_SIZ];
16315     char cpThinkOutput[MSG_SIZ];
16316
16317     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16318
16319     if (moveNumber == forwardMostMove - 1 ||
16320         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16321
16322         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16323
16324         if (strchr(cpThinkOutput, '\n')) {
16325             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16326         }
16327     } else {
16328         *cpThinkOutput = NULLCHAR;
16329     }
16330
16331     /* [AS] Hide thinking from human user */
16332     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16333         *cpThinkOutput = NULLCHAR;
16334         if( thinkOutput[0] != NULLCHAR ) {
16335             int i;
16336
16337             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16338                 cpThinkOutput[i] = '.';
16339             }
16340             cpThinkOutput[i] = NULLCHAR;
16341             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16342         }
16343     }
16344
16345     if (moveNumber == forwardMostMove - 1 &&
16346         gameInfo.resultDetails != NULL) {
16347         if (gameInfo.resultDetails[0] == NULLCHAR) {
16348           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16349         } else {
16350           snprintf(res, MSG_SIZ, " {%s} %s",
16351                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16352         }
16353     } else {
16354         res[0] = NULLCHAR;
16355     }
16356
16357     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16358         DisplayMessage(res, cpThinkOutput);
16359     } else {
16360       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16361                 WhiteOnMove(moveNumber) ? " " : ".. ",
16362                 parseList[moveNumber], res);
16363         DisplayMessage(message, cpThinkOutput);
16364     }
16365 }
16366
16367 void
16368 DisplayComment (int moveNumber, char *text)
16369 {
16370     char title[MSG_SIZ];
16371
16372     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16373       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16374     } else {
16375       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16376               WhiteOnMove(moveNumber) ? " " : ".. ",
16377               parseList[moveNumber]);
16378     }
16379     if (text != NULL && (appData.autoDisplayComment || commentUp))
16380         CommentPopUp(title, text);
16381 }
16382
16383 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16384  * might be busy thinking or pondering.  It can be omitted if your
16385  * gnuchess is configured to stop thinking immediately on any user
16386  * input.  However, that gnuchess feature depends on the FIONREAD
16387  * ioctl, which does not work properly on some flavors of Unix.
16388  */
16389 void
16390 Attention (ChessProgramState *cps)
16391 {
16392 #if ATTENTION
16393     if (!cps->useSigint) return;
16394     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16395     switch (gameMode) {
16396       case MachinePlaysWhite:
16397       case MachinePlaysBlack:
16398       case TwoMachinesPlay:
16399       case IcsPlayingWhite:
16400       case IcsPlayingBlack:
16401       case AnalyzeMode:
16402       case AnalyzeFile:
16403         /* Skip if we know it isn't thinking */
16404         if (!cps->maybeThinking) return;
16405         if (appData.debugMode)
16406           fprintf(debugFP, "Interrupting %s\n", cps->which);
16407         InterruptChildProcess(cps->pr);
16408         cps->maybeThinking = FALSE;
16409         break;
16410       default:
16411         break;
16412     }
16413 #endif /*ATTENTION*/
16414 }
16415
16416 int
16417 CheckFlags ()
16418 {
16419     if (whiteTimeRemaining <= 0) {
16420         if (!whiteFlag) {
16421             whiteFlag = TRUE;
16422             if (appData.icsActive) {
16423                 if (appData.autoCallFlag &&
16424                     gameMode == IcsPlayingBlack && !blackFlag) {
16425                   SendToICS(ics_prefix);
16426                   SendToICS("flag\n");
16427                 }
16428             } else {
16429                 if (blackFlag) {
16430                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16431                 } else {
16432                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16433                     if (appData.autoCallFlag) {
16434                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16435                         return TRUE;
16436                     }
16437                 }
16438             }
16439         }
16440     }
16441     if (blackTimeRemaining <= 0) {
16442         if (!blackFlag) {
16443             blackFlag = TRUE;
16444             if (appData.icsActive) {
16445                 if (appData.autoCallFlag &&
16446                     gameMode == IcsPlayingWhite && !whiteFlag) {
16447                   SendToICS(ics_prefix);
16448                   SendToICS("flag\n");
16449                 }
16450             } else {
16451                 if (whiteFlag) {
16452                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16453                 } else {
16454                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16455                     if (appData.autoCallFlag) {
16456                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16457                         return TRUE;
16458                     }
16459                 }
16460             }
16461         }
16462     }
16463     return FALSE;
16464 }
16465
16466 void
16467 CheckTimeControl ()
16468 {
16469     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16470         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16471
16472     /*
16473      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16474      */
16475     if ( !WhiteOnMove(forwardMostMove) ) {
16476         /* White made time control */
16477         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16478         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16479         /* [HGM] time odds: correct new time quota for time odds! */
16480                                             / WhitePlayer()->timeOdds;
16481         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16482     } else {
16483         lastBlack -= blackTimeRemaining;
16484         /* Black made time control */
16485         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16486                                             / WhitePlayer()->other->timeOdds;
16487         lastWhite = whiteTimeRemaining;
16488     }
16489 }
16490
16491 void
16492 DisplayBothClocks ()
16493 {
16494     int wom = gameMode == EditPosition ?
16495       !blackPlaysFirst : WhiteOnMove(currentMove);
16496     DisplayWhiteClock(whiteTimeRemaining, wom);
16497     DisplayBlackClock(blackTimeRemaining, !wom);
16498 }
16499
16500
16501 /* Timekeeping seems to be a portability nightmare.  I think everyone
16502    has ftime(), but I'm really not sure, so I'm including some ifdefs
16503    to use other calls if you don't.  Clocks will be less accurate if
16504    you have neither ftime nor gettimeofday.
16505 */
16506
16507 /* VS 2008 requires the #include outside of the function */
16508 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16509 #include <sys/timeb.h>
16510 #endif
16511
16512 /* Get the current time as a TimeMark */
16513 void
16514 GetTimeMark (TimeMark *tm)
16515 {
16516 #if HAVE_GETTIMEOFDAY
16517
16518     struct timeval timeVal;
16519     struct timezone timeZone;
16520
16521     gettimeofday(&timeVal, &timeZone);
16522     tm->sec = (long) timeVal.tv_sec;
16523     tm->ms = (int) (timeVal.tv_usec / 1000L);
16524
16525 #else /*!HAVE_GETTIMEOFDAY*/
16526 #if HAVE_FTIME
16527
16528 // include <sys/timeb.h> / moved to just above start of function
16529     struct timeb timeB;
16530
16531     ftime(&timeB);
16532     tm->sec = (long) timeB.time;
16533     tm->ms = (int) timeB.millitm;
16534
16535 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16536     tm->sec = (long) time(NULL);
16537     tm->ms = 0;
16538 #endif
16539 #endif
16540 }
16541
16542 /* Return the difference in milliseconds between two
16543    time marks.  We assume the difference will fit in a long!
16544 */
16545 long
16546 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16547 {
16548     return 1000L*(tm2->sec - tm1->sec) +
16549            (long) (tm2->ms - tm1->ms);
16550 }
16551
16552
16553 /*
16554  * Code to manage the game clocks.
16555  *
16556  * In tournament play, black starts the clock and then white makes a move.
16557  * We give the human user a slight advantage if he is playing white---the
16558  * clocks don't run until he makes his first move, so it takes zero time.
16559  * Also, we don't account for network lag, so we could get out of sync
16560  * with GNU Chess's clock -- but then, referees are always right.
16561  */
16562
16563 static TimeMark tickStartTM;
16564 static long intendedTickLength;
16565
16566 long
16567 NextTickLength (long timeRemaining)
16568 {
16569     long nominalTickLength, nextTickLength;
16570
16571     if (timeRemaining > 0L && timeRemaining <= 10000L)
16572       nominalTickLength = 100L;
16573     else
16574       nominalTickLength = 1000L;
16575     nextTickLength = timeRemaining % nominalTickLength;
16576     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16577
16578     return nextTickLength;
16579 }
16580
16581 /* Adjust clock one minute up or down */
16582 void
16583 AdjustClock (Boolean which, int dir)
16584 {
16585     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16586     if(which) blackTimeRemaining += 60000*dir;
16587     else      whiteTimeRemaining += 60000*dir;
16588     DisplayBothClocks();
16589     adjustedClock = TRUE;
16590 }
16591
16592 /* Stop clocks and reset to a fresh time control */
16593 void
16594 ResetClocks ()
16595 {
16596     (void) StopClockTimer();
16597     if (appData.icsActive) {
16598         whiteTimeRemaining = blackTimeRemaining = 0;
16599     } else if (searchTime) {
16600         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16601         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16602     } else { /* [HGM] correct new time quote for time odds */
16603         whiteTC = blackTC = fullTimeControlString;
16604         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16605         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16606     }
16607     if (whiteFlag || blackFlag) {
16608         DisplayTitle("");
16609         whiteFlag = blackFlag = FALSE;
16610     }
16611     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16612     DisplayBothClocks();
16613     adjustedClock = FALSE;
16614 }
16615
16616 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16617
16618 /* Decrement running clock by amount of time that has passed */
16619 void
16620 DecrementClocks ()
16621 {
16622     long timeRemaining;
16623     long lastTickLength, fudge;
16624     TimeMark now;
16625
16626     if (!appData.clockMode) return;
16627     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16628
16629     GetTimeMark(&now);
16630
16631     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16632
16633     /* Fudge if we woke up a little too soon */
16634     fudge = intendedTickLength - lastTickLength;
16635     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16636
16637     if (WhiteOnMove(forwardMostMove)) {
16638         if(whiteNPS >= 0) lastTickLength = 0;
16639         timeRemaining = whiteTimeRemaining -= lastTickLength;
16640         if(timeRemaining < 0 && !appData.icsActive) {
16641             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16642             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16643                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16644                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16645             }
16646         }
16647         DisplayWhiteClock(whiteTimeRemaining - fudge,
16648                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16649     } else {
16650         if(blackNPS >= 0) lastTickLength = 0;
16651         timeRemaining = blackTimeRemaining -= lastTickLength;
16652         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16653             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16654             if(suddenDeath) {
16655                 blackStartMove = forwardMostMove;
16656                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16657             }
16658         }
16659         DisplayBlackClock(blackTimeRemaining - fudge,
16660                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16661     }
16662     if (CheckFlags()) return;
16663
16664     if(twoBoards) { // count down secondary board's clocks as well
16665         activePartnerTime -= lastTickLength;
16666         partnerUp = 1;
16667         if(activePartner == 'W')
16668             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16669         else
16670             DisplayBlackClock(activePartnerTime, TRUE);
16671         partnerUp = 0;
16672     }
16673
16674     tickStartTM = now;
16675     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16676     StartClockTimer(intendedTickLength);
16677
16678     /* if the time remaining has fallen below the alarm threshold, sound the
16679      * alarm. if the alarm has sounded and (due to a takeback or time control
16680      * with increment) the time remaining has increased to a level above the
16681      * threshold, reset the alarm so it can sound again.
16682      */
16683
16684     if (appData.icsActive && appData.icsAlarm) {
16685
16686         /* make sure we are dealing with the user's clock */
16687         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16688                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16689            )) return;
16690
16691         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16692             alarmSounded = FALSE;
16693         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16694             PlayAlarmSound();
16695             alarmSounded = TRUE;
16696         }
16697     }
16698 }
16699
16700
16701 /* A player has just moved, so stop the previously running
16702    clock and (if in clock mode) start the other one.
16703    We redisplay both clocks in case we're in ICS mode, because
16704    ICS gives us an update to both clocks after every move.
16705    Note that this routine is called *after* forwardMostMove
16706    is updated, so the last fractional tick must be subtracted
16707    from the color that is *not* on move now.
16708 */
16709 void
16710 SwitchClocks (int newMoveNr)
16711 {
16712     long lastTickLength;
16713     TimeMark now;
16714     int flagged = FALSE;
16715
16716     GetTimeMark(&now);
16717
16718     if (StopClockTimer() && appData.clockMode) {
16719         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16720         if (!WhiteOnMove(forwardMostMove)) {
16721             if(blackNPS >= 0) lastTickLength = 0;
16722             blackTimeRemaining -= lastTickLength;
16723            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16724 //         if(pvInfoList[forwardMostMove].time == -1)
16725                  pvInfoList[forwardMostMove].time =               // use GUI time
16726                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16727         } else {
16728            if(whiteNPS >= 0) lastTickLength = 0;
16729            whiteTimeRemaining -= lastTickLength;
16730            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16731 //         if(pvInfoList[forwardMostMove].time == -1)
16732                  pvInfoList[forwardMostMove].time =
16733                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16734         }
16735         flagged = CheckFlags();
16736     }
16737     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16738     CheckTimeControl();
16739
16740     if (flagged || !appData.clockMode) return;
16741
16742     switch (gameMode) {
16743       case MachinePlaysBlack:
16744       case MachinePlaysWhite:
16745       case BeginningOfGame:
16746         if (pausing) return;
16747         break;
16748
16749       case EditGame:
16750       case PlayFromGameFile:
16751       case IcsExamining:
16752         return;
16753
16754       default:
16755         break;
16756     }
16757
16758     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16759         if(WhiteOnMove(forwardMostMove))
16760              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16761         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16762     }
16763
16764     tickStartTM = now;
16765     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16766       whiteTimeRemaining : blackTimeRemaining);
16767     StartClockTimer(intendedTickLength);
16768 }
16769
16770
16771 /* Stop both clocks */
16772 void
16773 StopClocks ()
16774 {
16775     long lastTickLength;
16776     TimeMark now;
16777
16778     if (!StopClockTimer()) return;
16779     if (!appData.clockMode) return;
16780
16781     GetTimeMark(&now);
16782
16783     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16784     if (WhiteOnMove(forwardMostMove)) {
16785         if(whiteNPS >= 0) lastTickLength = 0;
16786         whiteTimeRemaining -= lastTickLength;
16787         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16788     } else {
16789         if(blackNPS >= 0) lastTickLength = 0;
16790         blackTimeRemaining -= lastTickLength;
16791         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16792     }
16793     CheckFlags();
16794 }
16795
16796 /* Start clock of player on move.  Time may have been reset, so
16797    if clock is already running, stop and restart it. */
16798 void
16799 StartClocks ()
16800 {
16801     (void) StopClockTimer(); /* in case it was running already */
16802     DisplayBothClocks();
16803     if (CheckFlags()) return;
16804
16805     if (!appData.clockMode) return;
16806     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16807
16808     GetTimeMark(&tickStartTM);
16809     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16810       whiteTimeRemaining : blackTimeRemaining);
16811
16812    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16813     whiteNPS = blackNPS = -1;
16814     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16815        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16816         whiteNPS = first.nps;
16817     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16818        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16819         blackNPS = first.nps;
16820     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16821         whiteNPS = second.nps;
16822     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16823         blackNPS = second.nps;
16824     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16825
16826     StartClockTimer(intendedTickLength);
16827 }
16828
16829 char *
16830 TimeString (long ms)
16831 {
16832     long second, minute, hour, day;
16833     char *sign = "";
16834     static char buf[32];
16835
16836     if (ms > 0 && ms <= 9900) {
16837       /* convert milliseconds to tenths, rounding up */
16838       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16839
16840       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16841       return buf;
16842     }
16843
16844     /* convert milliseconds to seconds, rounding up */
16845     /* use floating point to avoid strangeness of integer division
16846        with negative dividends on many machines */
16847     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16848
16849     if (second < 0) {
16850         sign = "-";
16851         second = -second;
16852     }
16853
16854     day = second / (60 * 60 * 24);
16855     second = second % (60 * 60 * 24);
16856     hour = second / (60 * 60);
16857     second = second % (60 * 60);
16858     minute = second / 60;
16859     second = second % 60;
16860
16861     if (day > 0)
16862       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16863               sign, day, hour, minute, second);
16864     else if (hour > 0)
16865       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16866     else
16867       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16868
16869     return buf;
16870 }
16871
16872
16873 /*
16874  * This is necessary because some C libraries aren't ANSI C compliant yet.
16875  */
16876 char *
16877 StrStr (char *string, char *match)
16878 {
16879     int i, length;
16880
16881     length = strlen(match);
16882
16883     for (i = strlen(string) - length; i >= 0; i--, string++)
16884       if (!strncmp(match, string, length))
16885         return string;
16886
16887     return NULL;
16888 }
16889
16890 char *
16891 StrCaseStr (char *string, char *match)
16892 {
16893     int i, j, length;
16894
16895     length = strlen(match);
16896
16897     for (i = strlen(string) - length; i >= 0; i--, string++) {
16898         for (j = 0; j < length; j++) {
16899             if (ToLower(match[j]) != ToLower(string[j]))
16900               break;
16901         }
16902         if (j == length) return string;
16903     }
16904
16905     return NULL;
16906 }
16907
16908 #ifndef _amigados
16909 int
16910 StrCaseCmp (char *s1, char *s2)
16911 {
16912     char c1, c2;
16913
16914     for (;;) {
16915         c1 = ToLower(*s1++);
16916         c2 = ToLower(*s2++);
16917         if (c1 > c2) return 1;
16918         if (c1 < c2) return -1;
16919         if (c1 == NULLCHAR) return 0;
16920     }
16921 }
16922
16923
16924 int
16925 ToLower (int c)
16926 {
16927     return isupper(c) ? tolower(c) : c;
16928 }
16929
16930
16931 int
16932 ToUpper (int c)
16933 {
16934     return islower(c) ? toupper(c) : c;
16935 }
16936 #endif /* !_amigados    */
16937
16938 char *
16939 StrSave (char *s)
16940 {
16941   char *ret;
16942
16943   if ((ret = (char *) malloc(strlen(s) + 1)))
16944     {
16945       safeStrCpy(ret, s, strlen(s)+1);
16946     }
16947   return ret;
16948 }
16949
16950 char *
16951 StrSavePtr (char *s, char **savePtr)
16952 {
16953     if (*savePtr) {
16954         free(*savePtr);
16955     }
16956     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16957       safeStrCpy(*savePtr, s, strlen(s)+1);
16958     }
16959     return(*savePtr);
16960 }
16961
16962 char *
16963 PGNDate ()
16964 {
16965     time_t clock;
16966     struct tm *tm;
16967     char buf[MSG_SIZ];
16968
16969     clock = time((time_t *)NULL);
16970     tm = localtime(&clock);
16971     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16972             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16973     return StrSave(buf);
16974 }
16975
16976
16977 char *
16978 PositionToFEN (int move, char *overrideCastling)
16979 {
16980     int i, j, fromX, fromY, toX, toY;
16981     int whiteToPlay;
16982     char buf[MSG_SIZ];
16983     char *p, *q;
16984     int emptycount;
16985     ChessSquare piece;
16986
16987     whiteToPlay = (gameMode == EditPosition) ?
16988       !blackPlaysFirst : (move % 2 == 0);
16989     p = buf;
16990
16991     /* Piece placement data */
16992     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16993         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16994         emptycount = 0;
16995         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16996             if (boards[move][i][j] == EmptySquare) {
16997                 emptycount++;
16998             } else { ChessSquare piece = boards[move][i][j];
16999                 if (emptycount > 0) {
17000                     if(emptycount<10) /* [HGM] can be >= 10 */
17001                         *p++ = '0' + emptycount;
17002                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17003                     emptycount = 0;
17004                 }
17005                 if(PieceToChar(piece) == '+') {
17006                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17007                     *p++ = '+';
17008                     piece = (ChessSquare)(DEMOTED piece);
17009                 }
17010                 *p++ = PieceToChar(piece);
17011                 if(p[-1] == '~') {
17012                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17013                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17014                     *p++ = '~';
17015                 }
17016             }
17017         }
17018         if (emptycount > 0) {
17019             if(emptycount<10) /* [HGM] can be >= 10 */
17020                 *p++ = '0' + emptycount;
17021             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17022             emptycount = 0;
17023         }
17024         *p++ = '/';
17025     }
17026     *(p - 1) = ' ';
17027
17028     /* [HGM] print Crazyhouse or Shogi holdings */
17029     if( gameInfo.holdingsWidth ) {
17030         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17031         q = p;
17032         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17033             piece = boards[move][i][BOARD_WIDTH-1];
17034             if( piece != EmptySquare )
17035               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17036                   *p++ = PieceToChar(piece);
17037         }
17038         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17039             piece = boards[move][BOARD_HEIGHT-i-1][0];
17040             if( piece != EmptySquare )
17041               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17042                   *p++ = PieceToChar(piece);
17043         }
17044
17045         if( q == p ) *p++ = '-';
17046         *p++ = ']';
17047         *p++ = ' ';
17048     }
17049
17050     /* Active color */
17051     *p++ = whiteToPlay ? 'w' : 'b';
17052     *p++ = ' ';
17053
17054   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17055     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17056   } else {
17057   if(nrCastlingRights) {
17058      q = p;
17059      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17060        /* [HGM] write directly from rights */
17061            if(boards[move][CASTLING][2] != NoRights &&
17062               boards[move][CASTLING][0] != NoRights   )
17063                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17064            if(boards[move][CASTLING][2] != NoRights &&
17065               boards[move][CASTLING][1] != NoRights   )
17066                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17067            if(boards[move][CASTLING][5] != NoRights &&
17068               boards[move][CASTLING][3] != NoRights   )
17069                 *p++ = boards[move][CASTLING][3] + AAA;
17070            if(boards[move][CASTLING][5] != NoRights &&
17071               boards[move][CASTLING][4] != NoRights   )
17072                 *p++ = boards[move][CASTLING][4] + AAA;
17073      } else {
17074
17075         /* [HGM] write true castling rights */
17076         if( nrCastlingRights == 6 ) {
17077             int q, k=0;
17078             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17079                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17080             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17081                  boards[move][CASTLING][2] != NoRights  );
17082             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17083                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17084                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17085                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17086                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17087             }
17088             if(q) *p++ = 'Q';
17089             k = 0;
17090             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17091                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17092             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17093                  boards[move][CASTLING][5] != NoRights  );
17094             if(gameInfo.variant == VariantSChess) {
17095                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17096                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17097                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17098                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17099             }
17100             if(q) *p++ = 'q';
17101         }
17102      }
17103      if (q == p) *p++ = '-'; /* No castling rights */
17104      *p++ = ' ';
17105   }
17106
17107   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17108      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17109     /* En passant target square */
17110     if (move > backwardMostMove) {
17111         fromX = moveList[move - 1][0] - AAA;
17112         fromY = moveList[move - 1][1] - ONE;
17113         toX = moveList[move - 1][2] - AAA;
17114         toY = moveList[move - 1][3] - ONE;
17115         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17116             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17117             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17118             fromX == toX) {
17119             /* 2-square pawn move just happened */
17120             *p++ = toX + AAA;
17121             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17122         } else {
17123             *p++ = '-';
17124         }
17125     } else if(move == backwardMostMove) {
17126         // [HGM] perhaps we should always do it like this, and forget the above?
17127         if((signed char)boards[move][EP_STATUS] >= 0) {
17128             *p++ = boards[move][EP_STATUS] + AAA;
17129             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17130         } else {
17131             *p++ = '-';
17132         }
17133     } else {
17134         *p++ = '-';
17135     }
17136     *p++ = ' ';
17137   }
17138   }
17139
17140     /* [HGM] find reversible plies */
17141     {   int i = 0, j=move;
17142
17143         if (appData.debugMode) { int k;
17144             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17145             for(k=backwardMostMove; k<=forwardMostMove; k++)
17146                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17147
17148         }
17149
17150         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17151         if( j == backwardMostMove ) i += initialRulePlies;
17152         sprintf(p, "%d ", i);
17153         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17154     }
17155     /* Fullmove number */
17156     sprintf(p, "%d", (move / 2) + 1);
17157
17158     return StrSave(buf);
17159 }
17160
17161 Boolean
17162 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17163 {
17164     int i, j;
17165     char *p, c;
17166     int emptycount, virgin[BOARD_FILES];
17167     ChessSquare piece;
17168
17169     p = fen;
17170
17171     /* [HGM] by default clear Crazyhouse holdings, if present */
17172     if(gameInfo.holdingsWidth) {
17173        for(i=0; i<BOARD_HEIGHT; i++) {
17174            board[i][0]             = EmptySquare; /* black holdings */
17175            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17176            board[i][1]             = (ChessSquare) 0; /* black counts */
17177            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17178        }
17179     }
17180
17181     /* Piece placement data */
17182     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17183         j = 0;
17184         for (;;) {
17185             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17186                 if (*p == '/') p++;
17187                 emptycount = gameInfo.boardWidth - j;
17188                 while (emptycount--)
17189                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17190                 break;
17191 #if(BOARD_FILES >= 10)
17192             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17193                 p++; emptycount=10;
17194                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17195                 while (emptycount--)
17196                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17197 #endif
17198             } else if (isdigit(*p)) {
17199                 emptycount = *p++ - '0';
17200                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17201                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17202                 while (emptycount--)
17203                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17204             } else if (*p == '+' || isalpha(*p)) {
17205                 if (j >= gameInfo.boardWidth) return FALSE;
17206                 if(*p=='+') {
17207                     piece = CharToPiece(*++p);
17208                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17209                     piece = (ChessSquare) (PROMOTED piece ); p++;
17210                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17211                 } else piece = CharToPiece(*p++);
17212
17213                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17214                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17215                     piece = (ChessSquare) (PROMOTED piece);
17216                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17217                     p++;
17218                 }
17219                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17220             } else {
17221                 return FALSE;
17222             }
17223         }
17224     }
17225     while (*p == '/' || *p == ' ') p++;
17226
17227     /* [HGM] look for Crazyhouse holdings here */
17228     while(*p==' ') p++;
17229     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17230         if(*p == '[') p++;
17231         if(*p == '-' ) p++; /* empty holdings */ else {
17232             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17233             /* if we would allow FEN reading to set board size, we would   */
17234             /* have to add holdings and shift the board read so far here   */
17235             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17236                 p++;
17237                 if((int) piece >= (int) BlackPawn ) {
17238                     i = (int)piece - (int)BlackPawn;
17239                     i = PieceToNumber((ChessSquare)i);
17240                     if( i >= gameInfo.holdingsSize ) return FALSE;
17241                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17242                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17243                 } else {
17244                     i = (int)piece - (int)WhitePawn;
17245                     i = PieceToNumber((ChessSquare)i);
17246                     if( i >= gameInfo.holdingsSize ) return FALSE;
17247                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17248                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17249                 }
17250             }
17251         }
17252         if(*p == ']') p++;
17253     }
17254
17255     while(*p == ' ') p++;
17256
17257     /* Active color */
17258     c = *p++;
17259     if(appData.colorNickNames) {
17260       if( c == appData.colorNickNames[0] ) c = 'w'; else
17261       if( c == appData.colorNickNames[1] ) c = 'b';
17262     }
17263     switch (c) {
17264       case 'w':
17265         *blackPlaysFirst = FALSE;
17266         break;
17267       case 'b':
17268         *blackPlaysFirst = TRUE;
17269         break;
17270       default:
17271         return FALSE;
17272     }
17273
17274     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17275     /* return the extra info in global variiables             */
17276
17277     /* set defaults in case FEN is incomplete */
17278     board[EP_STATUS] = EP_UNKNOWN;
17279     for(i=0; i<nrCastlingRights; i++ ) {
17280         board[CASTLING][i] =
17281             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17282     }   /* assume possible unless obviously impossible */
17283     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17284     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17285     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17286                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17287     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17288     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17289     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17290                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17291     FENrulePlies = 0;
17292
17293     while(*p==' ') p++;
17294     if(nrCastlingRights) {
17295       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17296       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17297           /* castling indicator present, so default becomes no castlings */
17298           for(i=0; i<nrCastlingRights; i++ ) {
17299                  board[CASTLING][i] = NoRights;
17300           }
17301       }
17302       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17303              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17304              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17305              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17306         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17307
17308         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17309             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17310             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17311         }
17312         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17313             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17314         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17315                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17316         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17317                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17318         switch(c) {
17319           case'K':
17320               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17321               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17322               board[CASTLING][2] = whiteKingFile;
17323               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17324               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17325               break;
17326           case'Q':
17327               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17328               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17329               board[CASTLING][2] = whiteKingFile;
17330               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17331               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17332               break;
17333           case'k':
17334               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17335               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17336               board[CASTLING][5] = blackKingFile;
17337               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17338               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17339               break;
17340           case'q':
17341               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17342               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17343               board[CASTLING][5] = blackKingFile;
17344               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17345               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17346           case '-':
17347               break;
17348           default: /* FRC castlings */
17349               if(c >= 'a') { /* black rights */
17350                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17351                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17352                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17353                   if(i == BOARD_RGHT) break;
17354                   board[CASTLING][5] = i;
17355                   c -= AAA;
17356                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17357                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17358                   if(c > i)
17359                       board[CASTLING][3] = c;
17360                   else
17361                       board[CASTLING][4] = c;
17362               } else { /* white rights */
17363                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17364                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17365                     if(board[0][i] == WhiteKing) break;
17366                   if(i == BOARD_RGHT) break;
17367                   board[CASTLING][2] = i;
17368                   c -= AAA - 'a' + 'A';
17369                   if(board[0][c] >= WhiteKing) break;
17370                   if(c > i)
17371                       board[CASTLING][0] = c;
17372                   else
17373                       board[CASTLING][1] = c;
17374               }
17375         }
17376       }
17377       for(i=0; i<nrCastlingRights; i++)
17378         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17379       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17380     if (appData.debugMode) {
17381         fprintf(debugFP, "FEN castling rights:");
17382         for(i=0; i<nrCastlingRights; i++)
17383         fprintf(debugFP, " %d", board[CASTLING][i]);
17384         fprintf(debugFP, "\n");
17385     }
17386
17387       while(*p==' ') p++;
17388     }
17389
17390     /* read e.p. field in games that know e.p. capture */
17391     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17392        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17393       if(*p=='-') {
17394         p++; board[EP_STATUS] = EP_NONE;
17395       } else {
17396          char c = *p++ - AAA;
17397
17398          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17399          if(*p >= '0' && *p <='9') p++;
17400          board[EP_STATUS] = c;
17401       }
17402     }
17403
17404
17405     if(sscanf(p, "%d", &i) == 1) {
17406         FENrulePlies = i; /* 50-move ply counter */
17407         /* (The move number is still ignored)    */
17408     }
17409
17410     return TRUE;
17411 }
17412
17413 void
17414 EditPositionPasteFEN (char *fen)
17415 {
17416   if (fen != NULL) {
17417     Board initial_position;
17418
17419     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17420       DisplayError(_("Bad FEN position in clipboard"), 0);
17421       return ;
17422     } else {
17423       int savedBlackPlaysFirst = blackPlaysFirst;
17424       EditPositionEvent();
17425       blackPlaysFirst = savedBlackPlaysFirst;
17426       CopyBoard(boards[0], initial_position);
17427       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17428       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17429       DisplayBothClocks();
17430       DrawPosition(FALSE, boards[currentMove]);
17431     }
17432   }
17433 }
17434
17435 static char cseq[12] = "\\   ";
17436
17437 Boolean
17438 set_cont_sequence (char *new_seq)
17439 {
17440     int len;
17441     Boolean ret;
17442
17443     // handle bad attempts to set the sequence
17444         if (!new_seq)
17445                 return 0; // acceptable error - no debug
17446
17447     len = strlen(new_seq);
17448     ret = (len > 0) && (len < sizeof(cseq));
17449     if (ret)
17450       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17451     else if (appData.debugMode)
17452       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17453     return ret;
17454 }
17455
17456 /*
17457     reformat a source message so words don't cross the width boundary.  internal
17458     newlines are not removed.  returns the wrapped size (no null character unless
17459     included in source message).  If dest is NULL, only calculate the size required
17460     for the dest buffer.  lp argument indicats line position upon entry, and it's
17461     passed back upon exit.
17462 */
17463 int
17464 wrap (char *dest, char *src, int count, int width, int *lp)
17465 {
17466     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17467
17468     cseq_len = strlen(cseq);
17469     old_line = line = *lp;
17470     ansi = len = clen = 0;
17471
17472     for (i=0; i < count; i++)
17473     {
17474         if (src[i] == '\033')
17475             ansi = 1;
17476
17477         // if we hit the width, back up
17478         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17479         {
17480             // store i & len in case the word is too long
17481             old_i = i, old_len = len;
17482
17483             // find the end of the last word
17484             while (i && src[i] != ' ' && src[i] != '\n')
17485             {
17486                 i--;
17487                 len--;
17488             }
17489
17490             // word too long?  restore i & len before splitting it
17491             if ((old_i-i+clen) >= width)
17492             {
17493                 i = old_i;
17494                 len = old_len;
17495             }
17496
17497             // extra space?
17498             if (i && src[i-1] == ' ')
17499                 len--;
17500
17501             if (src[i] != ' ' && src[i] != '\n')
17502             {
17503                 i--;
17504                 if (len)
17505                     len--;
17506             }
17507
17508             // now append the newline and continuation sequence
17509             if (dest)
17510                 dest[len] = '\n';
17511             len++;
17512             if (dest)
17513                 strncpy(dest+len, cseq, cseq_len);
17514             len += cseq_len;
17515             line = cseq_len;
17516             clen = cseq_len;
17517             continue;
17518         }
17519
17520         if (dest)
17521             dest[len] = src[i];
17522         len++;
17523         if (!ansi)
17524             line++;
17525         if (src[i] == '\n')
17526             line = 0;
17527         if (src[i] == 'm')
17528             ansi = 0;
17529     }
17530     if (dest && appData.debugMode)
17531     {
17532         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17533             count, width, line, len, *lp);
17534         show_bytes(debugFP, src, count);
17535         fprintf(debugFP, "\ndest: ");
17536         show_bytes(debugFP, dest, len);
17537         fprintf(debugFP, "\n");
17538     }
17539     *lp = dest ? line : old_line;
17540
17541     return len;
17542 }
17543
17544 // [HGM] vari: routines for shelving variations
17545 Boolean modeRestore = FALSE;
17546
17547 void
17548 PushInner (int firstMove, int lastMove)
17549 {
17550         int i, j, nrMoves = lastMove - firstMove;
17551
17552         // push current tail of game on stack
17553         savedResult[storedGames] = gameInfo.result;
17554         savedDetails[storedGames] = gameInfo.resultDetails;
17555         gameInfo.resultDetails = NULL;
17556         savedFirst[storedGames] = firstMove;
17557         savedLast [storedGames] = lastMove;
17558         savedFramePtr[storedGames] = framePtr;
17559         framePtr -= nrMoves; // reserve space for the boards
17560         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17561             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17562             for(j=0; j<MOVE_LEN; j++)
17563                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17564             for(j=0; j<2*MOVE_LEN; j++)
17565                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17566             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17567             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17568             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17569             pvInfoList[firstMove+i-1].depth = 0;
17570             commentList[framePtr+i] = commentList[firstMove+i];
17571             commentList[firstMove+i] = NULL;
17572         }
17573
17574         storedGames++;
17575         forwardMostMove = firstMove; // truncate game so we can start variation
17576 }
17577
17578 void
17579 PushTail (int firstMove, int lastMove)
17580 {
17581         if(appData.icsActive) { // only in local mode
17582                 forwardMostMove = currentMove; // mimic old ICS behavior
17583                 return;
17584         }
17585         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17586
17587         PushInner(firstMove, lastMove);
17588         if(storedGames == 1) GreyRevert(FALSE);
17589         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17590 }
17591
17592 void
17593 PopInner (Boolean annotate)
17594 {
17595         int i, j, nrMoves;
17596         char buf[8000], moveBuf[20];
17597
17598         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17599         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17600         nrMoves = savedLast[storedGames] - currentMove;
17601         if(annotate) {
17602                 int cnt = 10;
17603                 if(!WhiteOnMove(currentMove))
17604                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17605                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17606                 for(i=currentMove; i<forwardMostMove; i++) {
17607                         if(WhiteOnMove(i))
17608                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17609                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17610                         strcat(buf, moveBuf);
17611                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17612                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17613                 }
17614                 strcat(buf, ")");
17615         }
17616         for(i=1; i<=nrMoves; i++) { // copy last variation back
17617             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17618             for(j=0; j<MOVE_LEN; j++)
17619                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17620             for(j=0; j<2*MOVE_LEN; j++)
17621                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17622             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17623             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17624             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17625             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17626             commentList[currentMove+i] = commentList[framePtr+i];
17627             commentList[framePtr+i] = NULL;
17628         }
17629         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17630         framePtr = savedFramePtr[storedGames];
17631         gameInfo.result = savedResult[storedGames];
17632         if(gameInfo.resultDetails != NULL) {
17633             free(gameInfo.resultDetails);
17634       }
17635         gameInfo.resultDetails = savedDetails[storedGames];
17636         forwardMostMove = currentMove + nrMoves;
17637 }
17638
17639 Boolean
17640 PopTail (Boolean annotate)
17641 {
17642         if(appData.icsActive) return FALSE; // only in local mode
17643         if(!storedGames) return FALSE; // sanity
17644         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17645
17646         PopInner(annotate);
17647         if(currentMove < forwardMostMove) ForwardEvent(); else
17648         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17649
17650         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17651         return TRUE;
17652 }
17653
17654 void
17655 CleanupTail ()
17656 {       // remove all shelved variations
17657         int i;
17658         for(i=0; i<storedGames; i++) {
17659             if(savedDetails[i])
17660                 free(savedDetails[i]);
17661             savedDetails[i] = NULL;
17662         }
17663         for(i=framePtr; i<MAX_MOVES; i++) {
17664                 if(commentList[i]) free(commentList[i]);
17665                 commentList[i] = NULL;
17666         }
17667         framePtr = MAX_MOVES-1;
17668         storedGames = 0;
17669 }
17670
17671 void
17672 LoadVariation (int index, char *text)
17673 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17674         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17675         int level = 0, move;
17676
17677         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17678         // first find outermost bracketing variation
17679         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17680             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17681                 if(*p == '{') wait = '}'; else
17682                 if(*p == '[') wait = ']'; else
17683                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17684                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17685             }
17686             if(*p == wait) wait = NULLCHAR; // closing ]} found
17687             p++;
17688         }
17689         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17690         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17691         end[1] = NULLCHAR; // clip off comment beyond variation
17692         ToNrEvent(currentMove-1);
17693         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17694         // kludge: use ParsePV() to append variation to game
17695         move = currentMove;
17696         ParsePV(start, TRUE, TRUE);
17697         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17698         ClearPremoveHighlights();
17699         CommentPopDown();
17700         ToNrEvent(currentMove+1);
17701 }
17702
17703 void
17704 LoadTheme ()
17705 {
17706     char *p, *q, buf[MSG_SIZ];
17707     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17708         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17709         ParseArgsFromString(buf);
17710         ActivateTheme(TRUE); // also redo colors
17711         return;
17712     }
17713     p = nickName;
17714     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17715     {
17716         int len;
17717         q = appData.themeNames;
17718         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17719       if(appData.useBitmaps) {
17720         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17721                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17722                 appData.liteBackTextureMode,
17723                 appData.darkBackTextureMode );
17724       } else {
17725         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17726                 Col2Text(2),   // lightSquareColor
17727                 Col2Text(3) ); // darkSquareColor
17728       }
17729       if(appData.useBorder) {
17730         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17731                 appData.border);
17732       } else {
17733         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17734       }
17735       if(appData.useFont) {
17736         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17737                 appData.renderPiecesWithFont,
17738                 appData.fontToPieceTable,
17739                 Col2Text(9),    // appData.fontBackColorWhite
17740                 Col2Text(10) ); // appData.fontForeColorBlack
17741       } else {
17742         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17743                 appData.pieceDirectory);
17744         if(!appData.pieceDirectory[0])
17745           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17746                 Col2Text(0),   // whitePieceColor
17747                 Col2Text(1) ); // blackPieceColor
17748       }
17749       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17750                 Col2Text(4),   // highlightSquareColor
17751                 Col2Text(5) ); // premoveHighlightColor
17752         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17753         if(insert != q) insert[-1] = NULLCHAR;
17754         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17755         if(q)   free(q);
17756     }
17757     ActivateTheme(FALSE);
17758 }